├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── README.md
├── docs
├── app.min.js
├── images
│ ├── maps.webp
│ └── maps
│ │ ├── mp_rr_canyonlands_mu3.webp
│ │ ├── mp_rr_desertlands_mu3.webp
│ │ ├── mp_rr_olympus_mu2.webp
│ │ └── mp_rr_tropic_island_mu1.webp
└── index.html
├── package-lock.json
├── package.json
├── src
├── Runner.ts
├── app.tsx
├── lib
│ ├── api
│ │ ├── Channel.ts
│ │ ├── Process.ts
│ │ ├── Region.ts
│ │ ├── Server.ts
│ │ ├── abstracts
│ │ │ ├── Adapter.ts
│ │ │ ├── enums
│ │ │ │ ├── DeltaType.ts
│ │ │ │ └── PacketType.ts
│ │ │ └── interfaces
│ │ │ │ ├── IPacketProvider.ts
│ │ │ │ └── IPacketWriter.ts
│ │ ├── classes
│ │ │ ├── Entity.ts
│ │ │ └── EntityMember.ts
│ │ ├── index.ts
│ │ ├── packets
│ │ │ ├── BasicAlive.ts
│ │ │ ├── BasicSync.ts
│ │ │ ├── EntityChange.ts
│ │ │ ├── EntityChangeMember.ts
│ │ │ ├── EntityChangeMemberDelta.ts
│ │ │ ├── EntityCreate.ts
│ │ │ ├── EntityCreateMember.ts
│ │ │ ├── EntityDelete.ts
│ │ │ ├── EntityUpdate.ts
│ │ │ ├── EntityUpdateEntity.ts
│ │ │ └── EntityUpdateEntityMember.ts
│ │ ├── providers
│ │ │ ├── AliveProvider.ts
│ │ │ └── EntityProvider.ts
│ │ └── streams
│ │ │ ├── BinaryReader.ts
│ │ │ └── BinaryWriter.ts
│ ├── core
│ │ ├── Core.ts
│ │ ├── classes
│ │ │ ├── ButtonList.ts
│ │ │ ├── Entity.ts
│ │ │ ├── EntityList.ts
│ │ │ ├── EntityListFilter.ts
│ │ │ ├── LevelName.ts
│ │ │ ├── LocalPlayer.ts
│ │ │ ├── Signifier.ts
│ │ │ └── SignifierList.ts
│ │ ├── entities
│ │ │ ├── Item.ts
│ │ │ ├── NPC.ts
│ │ │ └── Player.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ ├── models
│ │ │ ├── GlowData.ts
│ │ │ └── VectorData.ts
│ │ ├── offsets.ts
│ │ └── types
│ │ │ ├── CString.ts
│ │ │ ├── Float32.ts
│ │ │ ├── Glow.ts
│ │ │ ├── UInt32.ts
│ │ │ ├── UInt64.ts
│ │ │ ├── UInt8.ts
│ │ │ └── Vector.ts
│ ├── features
│ │ ├── Map.ts
│ │ ├── Radar.ts
│ │ ├── Recoil.ts
│ │ ├── Sense.ts
│ │ └── index.ts
│ ├── index.ts
│ └── items
│ │ ├── index.ts
│ │ ├── list.ts
│ │ └── types
│ │ ├── All.ts
│ │ ├── Ammo.ts
│ │ ├── AmmoType.ts
│ │ ├── Attachment.ts
│ │ ├── AttachmentType.ts
│ │ ├── Gear.ts
│ │ ├── GearType.ts
│ │ ├── Grenade.ts
│ │ ├── Regen.ts
│ │ ├── Weapon.ts
│ │ └── WeaponType.ts
└── ui
│ ├── MainView.tsx
│ ├── MainViewModel.ts
│ ├── classes
│ ├── BoolStorage.ts
│ ├── NumberStorage.ts
│ └── StringStorage.ts
│ ├── index.ts
│ ├── language.ts
│ ├── main
│ ├── MainView.tsx
│ └── index.ts
│ ├── settings
│ ├── MainView.tsx
│ ├── MainViewModel.ts
│ ├── general
│ │ ├── MainView.tsx
│ │ ├── MainViewModel.ts
│ │ ├── enums
│ │ │ └── MainType.ts
│ │ ├── index.ts
│ │ ├── language.ts
│ │ ├── viewModels
│ │ │ ├── MapViewModel.ts
│ │ │ ├── RadarViewModel.ts
│ │ │ └── SenseViewModel.ts
│ │ └── views
│ │ │ ├── MapView.tsx
│ │ │ ├── RadarView.tsx
│ │ │ └── SenseView.tsx
│ ├── index.ts
│ ├── items
│ │ ├── MainView.tsx
│ │ ├── MainViewModel.ts
│ │ ├── index.ts
│ │ ├── language.ts
│ │ ├── viewModels
│ │ │ ├── AreaViewModel.ts
│ │ │ └── ItemViewModel.ts
│ │ └── views
│ │ │ ├── AreaView.tsx
│ │ │ └── ItemView.tsx
│ ├── language.ts
│ └── research
│ │ ├── MainView.tsx
│ │ ├── MainViewModel.ts
│ │ ├── index.ts
│ │ ├── language.ts
│ │ ├── viewModels
│ │ └── RecoilViewModel.ts
│ │ └── views
│ │ └── RecoilView.tsx
│ └── utilities.ts
├── tsconfig.json
├── webpack.common.js
├── webpack.development.js
└── webpack.production.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | /docs/*.min.js linguist-generated
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /node_modules
3 | /res/mp_*
4 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [{
4 | "name": "Client",
5 | "outFiles": ["${workspaceFolder}/dist/**/*"],
6 | "outputCapture": "std",
7 | "preLaunchTask": "Webpack",
8 | "request": "launch",
9 | "type": "pwa-chrome",
10 | "url": "http://localhost:3000/"
11 | }]
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.tabSize": 2,
3 | "typescript.tsdk": "node_modules/typescript/lib",
4 | "files.exclude": {
5 | "dist": true,
6 | "docs/*.min.js": true,
7 | "node_modules": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [{
4 | "group": {"isDefault": true, "kind": "build"},
5 | "isBackground": true,
6 | "label": "TypeScript",
7 | "option": "watch",
8 | "problemMatcher": ["$tsc-watch"],
9 | "tsconfig": "tsconfig.json",
10 | "type": "typescript"
11 | }, {
12 | "dependsOn": ["TypeScript"],
13 | "isBackground": true,
14 | "label": "Webpack",
15 | "script": "webpack:serve",
16 | "type": "npm",
17 | "problemMatcher": [{
18 | "background": {
19 | "activeOnStart": true,
20 | "beginsPattern": ".",
21 | "endsPattern": "compiled (with|successfully)"
22 | },
23 | "pattern": [{
24 | "regexp": ".",
25 | "file": 1,
26 | "location": 2,
27 | "message": 3
28 | }]
29 | }]
30 | }]
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # http-game-apex
2 |
3 | Linux *Apex Legends* connector powered by [http-driver](https://github.com/XRadius/http-driver). **This connector won't work without `http-driver`**.
4 |
5 | # Quick Start
6 |
7 | The easiest way to use `http-game-apex` is to navigate to your `http-driver` and load this connector:
8 |
9 | ```
10 | https://xradius.github.io/http-game-apex/
11 | ```
12 |
13 | This is the *precompiled production connector*, which [updates automatically](https://github.com/XRadius/http-game-apex/deployments/).
14 |
15 | # Installation
16 |
17 | We'll setup a development environment to compile and run from source.
18 |
19 | 1. Install *Google Chrome*, *NodeJS* and *Visual Studio Code*:
20 |
21 | * Follow the instructions at https://www.google.com/intl/en_us/chrome/
22 | * Follow the instructions at http://nodejs.org/ (`node` >= 16, `npm` >= 7)
23 | * Follow the instructions at https://code.visualstudio.com/
24 |
25 | 2. Clone this repository:
26 |
27 | ```
28 | git clone https://github.com/XRadius/http-game-apex/
29 | ```
30 |
31 | 3. Open the `http-game-apex` directory:
32 |
33 | ```
34 | cd http-game-apex
35 | ```
36 |
37 | 4. Install dependencies:
38 |
39 | ```
40 | npm install
41 | ```
42 |
43 | 5. Start debugging:
44 |
45 | * Open the `http-game-apex` directory with *Visual Studio Code*.
46 | * Press `F5`, wait for compilation, and eventually *Google Chrome* will launch.
47 |
48 | 6. Load as a *connector* in your `http-driver`:
49 |
50 | * Use the *Google Chrome* window to navigate to your `http-driver`.
51 | * Load *http://0.0.0.0:3000/* as the *connector*. Replace `0.0.0.0` for your *network-resolvable* IP.
52 |
53 | # Troubleshooting
54 |
55 | These are the most common problems when using `http-game-apex`.
56 |
57 | ## "Load this connector in your http-driver."
58 |
59 | You've loaded this *connector* in your browser without using `http-driver`. Because of [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content#warnings_in_firefox_web_console) and [private network CORS](https://web.dev/cors-rfc1918-feedback/) policies, your browser will prevent the *connector* from reaching your `http-driver`. To prevent this, `http-driver` loads *connectors* through a [reverse proxy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling) mechanism to map the *connector* into its private network space. To solve this issue, navigate to your `http-driver` and enter the *connector* URL there.
60 |
61 | ## "Your http-driver is outdated."
62 |
63 | As it says on the box, your `http-driver` is outdated! Follow the [update instructions](https://github.com/XRadius/http-driver#updating) and try again.
64 |
--------------------------------------------------------------------------------
/docs/images/maps.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XRadius/http-game-apex/f0e9db33e8b28ec722aec547bd1455c8895e9181/docs/images/maps.webp
--------------------------------------------------------------------------------
/docs/images/maps/mp_rr_canyonlands_mu3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XRadius/http-game-apex/f0e9db33e8b28ec722aec547bd1455c8895e9181/docs/images/maps/mp_rr_canyonlands_mu3.webp
--------------------------------------------------------------------------------
/docs/images/maps/mp_rr_desertlands_mu3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XRadius/http-game-apex/f0e9db33e8b28ec722aec547bd1455c8895e9181/docs/images/maps/mp_rr_desertlands_mu3.webp
--------------------------------------------------------------------------------
/docs/images/maps/mp_rr_olympus_mu2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XRadius/http-game-apex/f0e9db33e8b28ec722aec547bd1455c8895e9181/docs/images/maps/mp_rr_olympus_mu2.webp
--------------------------------------------------------------------------------
/docs/images/maps/mp_rr_tropic_island_mu1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XRadius/http-game-apex/f0e9db33e8b28ec722aec547bd1455c8895e9181/docs/images/maps/mp_rr_tropic_island_mu1.webp
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http-game-apex
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "http-game-apex",
4 | "version": "1.0.0",
5 | "devDependencies": {
6 | "@emotion/react": "11.10.0",
7 | "@emotion/styled": "11.10.0",
8 | "@mui/icons-material": "5.8.4",
9 | "@mui/material": "5.10.0",
10 | "mobx": "6.6.1",
11 | "mobx-react": "7.5.2",
12 | "react": "17.0.2",
13 | "react-dom": "17.0.2",
14 | "reflect-metadata": "0.1.13",
15 | "@types/react": "17.0.19",
16 | "@types/react-dom": "17.0.9",
17 | "pre-commit": "1.2.2",
18 | "source-map-loader": "3.0.1",
19 | "typescript": "4.6.2",
20 | "webpack": "5.70.0",
21 | "webpack-cli": "4.9.2",
22 | "webpack-dev-server": "4.7.4"
23 | },
24 | "engines": {
25 | "node": "18.x",
26 | "npm": "8.x"
27 | },
28 | "pre-commit": [
29 | "prepare"
30 | ],
31 | "scripts": {
32 | "prepare": "tsc && npm run webpack:build && git add docs/*.min.js",
33 | "webpack:build": "webpack build --config webpack.production.js",
34 | "webpack:serve": "webpack serve --config webpack.development.js"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Runner.ts:
--------------------------------------------------------------------------------
1 | import * as app from 'lib';
2 | import * as ui from 'ui';
3 |
4 | export class Runner {
5 | private constructor(
6 | private readonly canvas = document.getElementById('canvas') as HTMLCanvasElement,
7 | private readonly map = new app.features.Map(canvas),
8 | private readonly radar = new app.features.Radar(canvas),
9 | private readonly recoil = new app.features.Recoil(),
10 | private readonly sense = new app.features.Sense()) {}
11 |
12 | static create() {
13 | const source = new Runner();
14 | source.attach();
15 | return source.run.bind(source);
16 | }
17 |
18 | run(core: app.core.Core, vm: ui.MainViewModel) {
19 | const localPlayer = core.playerList.get(core.localPlayer.value);
20 | this.updateResearch(core, vm, localPlayer);
21 | this.updateSense(core, vm, localPlayer);
22 | this.canvas.height = window.innerHeight;
23 | this.canvas.width = window.innerWidth;
24 | this.update(core, vm, localPlayer);
25 | }
26 |
27 | private attach() {
28 | this.canvas.addEventListener('dblclick', () => {
29 | (document.fullscreenElement
30 | ? document.exitFullscreen()
31 | : document.body.requestFullscreen()).catch();
32 | });
33 | }
34 |
35 | private update(core: app.core.Core, vm: ui.MainViewModel, localPlayer?: app.core.Player) {
36 | switch (vm.settings.general.viewType.value) {
37 | case ui.settings.general.MainType.Map:
38 | this.map.refresh(core.levelName.value);
39 | this.updateMap(core, vm, localPlayer);
40 | break;
41 | case ui.settings.general.MainType.Radar:
42 | this.radar.refresh();
43 | this.updateRadar(core, vm, localPlayer);
44 | break;
45 | }
46 | }
47 |
48 | private updateMap(core: app.core.Core, vm: ui.MainViewModel, localPlayer?: app.core.Player) {
49 | if (vm.settings.general.map.showItems.value)
50 | this.map.renderItems(core.itemList.values(), vm.settings.itemSet);
51 | if (vm.settings.general.map.showPlayers.value && localPlayer)
52 | this.map.renderPlayers(localPlayer, core.playerList.values());
53 | }
54 |
55 | private updateRadar(core: app.core.Core, vm: ui.MainViewModel, localPlayer?: app.core.Player) {
56 | if (vm.settings.general.radar.showItems.value && localPlayer)
57 | this.radar.renderItems(localPlayer, core.itemList.values(), vm.settings.itemSet);
58 | if (vm.settings.general.radar.showPlayers.value && localPlayer)
59 | this.radar.renderNpcs(localPlayer, core.npcList.values());
60 | if (vm.settings.general.radar.showPlayers.value && localPlayer)
61 | this.radar.renderPlayers(localPlayer, core.playerList.values());
62 | }
63 |
64 | private updateResearch(core: app.core.Core, vm: ui.MainViewModel, localPlayer?: app.core.Player) {
65 | if (vm.settings.research.recoil.enable.value && localPlayer && vm.settings.research.recoil.options)
66 | this.recoil.update(core.buttonList, localPlayer, vm.settings.research.recoil.options);
67 | }
68 |
69 | private updateSense(core: app.core.Core, vm: ui.MainViewModel, localPlayer?: app.core.Player) {
70 | const itemsFn = vm.settings.general.sense.highlightItems.value
71 | ? this.sense.updateItems.bind(this.sense)
72 | : this.sense.resetItems.bind(this.sense);
73 | const playersFn = vm.settings.general.sense.highlightPlayers.value
74 | ? this.sense.updatePlayers.bind(this.sense)
75 | : this.sense.resetPlayers.bind(this.sense);
76 | if (localPlayer) {
77 | itemsFn(localPlayer, core.itemList.values(), vm.settings.itemSet);
78 | playersFn(localPlayer, core.playerList.values());
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 | import * as mobx from 'mobx';
3 | import * as React from 'react';
4 | import * as ReactDOM from 'react-dom';
5 | import * as ui from 'ui';
6 | import {Runner} from './Runner';
7 | const packageData = require('../package');
8 |
9 | function App() {
10 | return (
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | const theme = ui.material.createTheme({
19 | palette: {
20 | mode: 'dark',
21 | primary: {main: '#4CAF50'}
22 | },
23 | components: {
24 | MuiCssBaseline: {
25 | styleOverrides: {
26 | html: {
27 | backgroundColor: '#333'
28 | },
29 | body: {
30 | backgroundColor: '#333',
31 | },
32 | canvas: {
33 | display: 'none',
34 | position: 'fixed'
35 | },
36 | '::-webkit-scrollbar': {
37 | width: '12px'
38 | },
39 | '::-webkit-scrollbar-track': {
40 | backgroundColor: '#333'
41 | },
42 | '::-webkit-scrollbar-thumb': {
43 | backgroundColor: '#4CAF50',
44 | border: '4px solid #333',
45 | borderRadius: '12px'
46 | }
47 | }
48 | }
49 | }
50 | });
51 |
52 | (function() {
53 | document.title = `${packageData.name} (${packageData.version})`;
54 | mobx.configure({enforceActions: 'never'});
55 | ReactDOM.render(, document.getElementById('container'));
56 | })();
57 |
--------------------------------------------------------------------------------
/src/lib/api/Channel.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Channel {
4 | private readonly alive: app.AliveProvider;
5 | private readonly entities: app.EntityProvider;
6 | private readonly socket: WebSocket;
7 | private syncId = 0;
8 |
9 | constructor(pid: number) {
10 | this.alive = new app.AliveProvider();
11 | this.entities = new app.EntityProvider();
12 | this.socket = new WebSocket(`ws://${location.host}/ws/direct/${pid}`);
13 | this.socket.binaryType = 'arraybuffer';
14 | this.socket.addEventListener('message', x => this.receive(x));
15 | }
16 |
17 | create(adapter: app.Adapter) {
18 | this.entities.create(adapter.source);
19 | }
20 |
21 | delete(adapter: app.Adapter) {
22 | this.entities.delete(adapter.source);
23 | }
24 |
25 | async runAsync(renderFrame: () => void) {
26 | while (true) {
27 | switch (this.socket.readyState) {
28 | case this.socket.CONNECTING:
29 | await new Promise(requestAnimationFrame);
30 | break;
31 | case this.socket.OPEN:
32 | renderFrame();
33 | this.update();
34 | await new Promise(requestAnimationFrame);
35 | break;
36 | default:
37 | throw new Error('Invalid channel state!');
38 | }
39 | }
40 | }
41 |
42 | private receive(ev: MessageEvent) {
43 | if (ev.data instanceof ArrayBuffer) {
44 | const stream = new app.BinaryReader(ev.data);
45 | while (stream.hasBytes()) {
46 | switch (stream.readUInt8() as app.PacketType) {
47 | case app.PacketType.BasicSync:
48 | this.entities.receive(app.BasicSync.create(stream));
49 | break;
50 | case app.PacketType.EntityUpdate:
51 | this.entities.receive(app.EntityUpdate.create(stream));
52 | break;
53 | }
54 | }
55 | }
56 | }
57 |
58 | private update() {
59 | const stream = new app.BinaryWriter();
60 | this.alive.update(stream);
61 | this.entities.update(stream, this.syncId);
62 | if (stream.hasBytes()) {
63 | new app.BasicSync(this.syncId).write(stream);
64 | this.socket.send(stream.toBuffer());
65 | this.syncId = this.syncId !== 255 ? this.syncId + 1 : 1;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/lib/api/Process.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Process {
4 | constructor(value: app.IProcess,
5 | readonly args = value.args,
6 | readonly command = value.command,
7 | readonly pid = value.pid) {}
8 |
9 | async regionsAsync() {
10 | const response = await fetch(`/api/proc/${this.pid}/maps`);
11 | const result = await response.json() as Array;
12 | return result.map(x => new app.Region(x));
13 | }
14 | }
15 |
16 | export type IProcess = {
17 | args: Array,
18 | command: string,
19 | pid: number
20 | };
21 |
--------------------------------------------------------------------------------
/src/lib/api/Region.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Region {
4 | constructor(value: app.IRegion,
5 | readonly start = parseHex(value.start),
6 | readonly end = parseHex(value.end),
7 | readonly perms = value.perms,
8 | readonly offset = parseHex(value.offset),
9 | readonly devMajor = value.devMajor,
10 | readonly devMinor = value.devMinor,
11 | readonly inode = parseHex(value.inode),
12 | readonly pathname = value.pathname) {}
13 | }
14 |
15 | function parseHex(value: string) {
16 | return /^0x/i.test(value)
17 | ? BigInt(value)
18 | : BigInt(`0x${value}`);
19 | }
20 |
21 | export type IRegion = {
22 | devMajor: number,
23 | devMinor: number,
24 | end: string,
25 | inode: string,
26 | offset: string,
27 | pathname: string,
28 | perms: number,
29 | start: string
30 | };
31 |
--------------------------------------------------------------------------------
/src/lib/api/Server.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Server {
4 | async processesAsync() {
5 | const response = await fetch('/api/proc');
6 | const result = await response.json() as Array;
7 | return result.map(x => new app.Process(x));
8 | }
9 |
10 | async versionAsync() {
11 | const response = await fetch('/api/version');
12 | const result = await response.json().catch(() => 0) as Number;
13 | return result;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/api/abstracts/Adapter.ts:
--------------------------------------------------------------------------------
1 | export abstract class Adapter {
2 | constructor(
3 | readonly source: T) {}
4 | }
5 |
--------------------------------------------------------------------------------
/src/lib/api/abstracts/enums/DeltaType.ts:
--------------------------------------------------------------------------------
1 | export enum DeltaType {
2 | Byte,
3 | Int16,
4 | Int32,
5 | Int64,
6 | Float32,
7 | Float64,
8 | UInt16,
9 | UInt32,
10 | UInt64
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib/api/abstracts/enums/PacketType.ts:
--------------------------------------------------------------------------------
1 | export enum PacketType {
2 | BasicAlive,
3 | BasicSync,
4 | EntityChange,
5 | EntityCreate,
6 | EntityDelete,
7 | EntityUpdate
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/api/abstracts/interfaces/IPacketProvider.ts:
--------------------------------------------------------------------------------
1 | import * as app from '../..';
2 |
3 | export interface IPacketProvider {
4 | update(stream: app.BinaryWriter, syncId: number): void;
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/api/abstracts/interfaces/IPacketWriter.ts:
--------------------------------------------------------------------------------
1 | import * as app from '../..';
2 |
3 | export interface IPacketWriter {
4 | write(stream: app.BinaryWriter): void;
5 | }
6 |
--------------------------------------------------------------------------------
/src/lib/api/classes/Entity.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Entity {
4 | readonly address: bigint;
5 | readonly members: Map;
6 | readonly options?: IOptions;
7 |
8 | constructor(address: bigint, members: Iterable>, options?: IOptions) {
9 | this.address = address;
10 | this.members = convertMembers(members);
11 | this.options = options;
12 | }
13 |
14 | receive(packet: app.BasicSync | app.EntityUpdateEntity) {
15 | if (packet instanceof app.EntityUpdateEntity) {
16 | this.receiveUpdate(packet);
17 | } else {
18 | this.receiveSync(packet);
19 | }
20 | }
21 |
22 | update(id: number, syncId: number) {
23 | const packets: Array = [];
24 | this.createUpdate(packets, syncId);
25 | return packets.length ? new app.EntityChange(id, packets) : undefined;
26 | }
27 |
28 | private createUpdate(packets: Array, syncId: number) {
29 | for (const member of this.members.values()) {
30 | const packet = member.update(syncId);
31 | if (!packet) continue;
32 | packets.push(packet);
33 | }
34 | }
35 |
36 | private receiveUpdate(packet: app.EntityUpdateEntity) {
37 | for (const child of packet.members) {
38 | const member = this.members.get(child.offset);
39 | if (!member) continue;
40 | member.receive(child);
41 | }
42 | }
43 |
44 | private receiveSync(packet: app.BasicSync) {
45 | for (const member of this.members.values()) {
46 | member.receive(packet);
47 | }
48 | }
49 | }
50 |
51 | function convertMembers(members: Iterable>) {
52 | const result = new Map();
53 | for (const member of members) result.set(member.source.offset, member.source);
54 | return result;
55 | }
56 |
57 | type IOptions = {
58 | enableUpdate?: boolean;
59 | requestBatch?: boolean;
60 | }
61 |
--------------------------------------------------------------------------------
/src/lib/api/classes/EntityMember.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityMember {
4 | readonly buffer: DataView;
5 | readonly interval: number;
6 | readonly offset: number;
7 | deltas?: Array;
8 | send?: boolean;
9 | syncId?: number;
10 |
11 | constructor(offset: number, interval: number, size: number) {
12 | this.buffer = new DataView(new ArrayBuffer(size));
13 | this.interval = interval;
14 | this.offset = offset;
15 | }
16 |
17 | receive(packet: app.BasicSync | app.EntityUpdateEntityMember) {
18 | if (packet instanceof app.EntityUpdateEntityMember) {
19 | this.receiveUpdate(packet);
20 | } else {
21 | this.receiveSync(packet);
22 | }
23 | }
24 |
25 | update(syncId: number) {
26 | if (!this.send) return;
27 | const packet = new app.EntityChangeMember(this.offset, this.buffer, this.deltas);
28 | this.deltas = undefined;
29 | this.send = false;
30 | this.syncId = syncId;
31 | return packet;
32 | }
33 |
34 | private receiveUpdate(packet: app.EntityUpdateEntityMember) {
35 | if (typeof this.syncId !== 'undefined') return;
36 | if (packet.buffer.byteLength !== this.buffer.byteLength) return;
37 | for (let i = 0; i < packet.buffer.byteLength; i++) this.buffer.setUint8(i, packet.buffer.getUint8(i));
38 | }
39 |
40 | private receiveSync(packet: app.BasicSync) {
41 | if (this.syncId !== packet.id) return;
42 | this.syncId = undefined;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/api/index.ts:
--------------------------------------------------------------------------------
1 | export const VERSION = 6;
2 | export * from './abstracts/enums/DeltaType';
3 | export * from './abstracts/enums/PacketType';
4 | export * from './abstracts/interfaces/IPacketProvider';
5 | export * from './abstracts/interfaces/IPacketWriter';
6 | export * from './abstracts/Adapter';
7 | export * from './classes/Entity';
8 | export * from './classes/EntityMember';
9 | export * from './packets/BasicAlive';
10 | export * from './packets/BasicSync';
11 | export * from './packets/EntityChange';
12 | export * from './packets/EntityChangeMember';
13 | export * from './packets/EntityChangeMemberDelta';
14 | export * from './packets/EntityCreate';
15 | export * from './packets/EntityCreateMember';
16 | export * from './packets/EntityDelete';
17 | export * from './packets/EntityUpdate';
18 | export * from './packets/EntityUpdateEntity';
19 | export * from './packets/EntityUpdateEntityMember';
20 | export * from './providers/AliveProvider';
21 | export * from './providers/EntityProvider';
22 | export * from './streams/BinaryReader';
23 | export * from './streams/BinaryWriter';
24 | export * from './Channel';
25 | export * from './Process';
26 | export * from './Region';
27 | export * from './Server';
28 |
--------------------------------------------------------------------------------
/src/lib/api/packets/BasicAlive.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class BasicAlive implements app.IPacketWriter {
4 | write(stream: app.BinaryWriter) {
5 | stream.writeUInt8(app.PacketType.BasicAlive);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/lib/api/packets/BasicSync.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class BasicSync implements app.IPacketWriter {
4 | constructor(
5 | readonly id: number) {}
6 |
7 | static create(stream: app.BinaryReader) {
8 | const id = stream.readUInt8();
9 | return new BasicSync(id);
10 | }
11 |
12 | write(stream: app.BinaryWriter) {
13 | stream.writeUInt8(app.PacketType.BasicSync);
14 | stream.writeUInt8(this.id);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityChange.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityChange implements app.IPacketWriter {
4 | constructor(
5 | readonly id: number,
6 | readonly changes: Array) {}
7 |
8 | write(stream: app.BinaryWriter) {
9 | stream.writeUInt8(app.PacketType.EntityChange);
10 | stream.writeVariableLength(this.id);
11 | stream.writeVariableLength(this.changes.length);
12 | this.changes.forEach(x => x.write(stream));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityChangeMember.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityChangeMember implements app.IPacketWriter {
4 | constructor(
5 | readonly offset: number,
6 | readonly buffer: DataView,
7 | readonly deltas?: Array) {}
8 |
9 | write(stream: app.BinaryWriter) {
10 | stream.writeVariableLength(this.offset);
11 | if (this.deltas) {
12 | stream.writeVariableLength(this.deltas.length);
13 | this.deltas.forEach(x => x.write(stream));
14 | } else {
15 | stream.writeUInt8(0);
16 | stream.writeVariableLength(this.buffer.byteLength);
17 | stream.writeBytes(this.buffer);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityChangeMemberDelta.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityChangeMemberDelta implements app.IPacketWriter {
4 | constructor(
5 | readonly offset: number,
6 | readonly type: app.DeltaType,
7 | readonly buffer: DataView) {}
8 |
9 | write(stream: app.BinaryWriter) {
10 | stream.writeVariableLength(this.offset);
11 | stream.writeUInt8(this.type);
12 | stream.writeBytes(this.buffer);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityCreate.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityCreate implements app.IPacketWriter {
4 | constructor(
5 | readonly id: number,
6 | readonly address: bigint,
7 | readonly members: Array,
8 | readonly requestBatch: boolean) {}
9 |
10 | write(stream: app.BinaryWriter) {
11 | stream.writeUInt8(app.PacketType.EntityCreate);
12 | stream.writeVariableLength(this.id);
13 | stream.writeUInt64(this.address);
14 | stream.writeVariableLength(this.members.length);
15 | this.members.forEach(x => x.write(stream));
16 | stream.writeUInt8(Number(this.requestBatch));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityCreateMember.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityCreateMember implements app.IPacketWriter {
4 | constructor(
5 | readonly offset: number,
6 | readonly interval: number,
7 | readonly size: number) {}
8 |
9 | write(stream: app.BinaryWriter) {
10 | stream.writeVariableLength(this.offset);
11 | stream.writeVariableLength(this.interval);
12 | stream.writeVariableLength(this.size);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityDelete.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityDelete implements app.IPacketWriter {
4 | constructor(
5 | readonly id: number) {}
6 |
7 | write(stream: app.BinaryWriter) {
8 | stream.writeUInt8(app.PacketType.EntityDelete);
9 | stream.writeVariableLength(this.id);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityUpdate.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityUpdate {
4 | private constructor(
5 | readonly entities: Array) {}
6 |
7 | static create(stream: app.BinaryReader) {
8 | const membersSize = stream.readVariableLength();
9 | const members = process(stream, membersSize);
10 | return new EntityUpdate(members);
11 | }
12 | }
13 |
14 | function process(stream: app.BinaryReader, size: number) {
15 | const result = new Array(size);
16 | for (let i = 0; i < size; i++) result[i] = app.EntityUpdateEntity.create(stream);
17 | return result;
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityUpdateEntity.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityUpdateEntity {
4 | private constructor(
5 | readonly id: number,
6 | readonly members: Array) {}
7 |
8 | static create(stream: app.BinaryReader) {
9 | const id = stream.readVariableLength();
10 | const membersSize = stream.readVariableLength();
11 | const members = process(stream, membersSize);
12 | return new EntityUpdateEntity(id, members);
13 | }
14 | }
15 |
16 | function process(stream: app.BinaryReader, size: number) {
17 | const result = new Array(size);
18 | for (let i = 0; i < size; i++) result[i] = app.EntityUpdateEntityMember.create(stream);
19 | return result;
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/api/packets/EntityUpdateEntityMember.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityUpdateEntityMember {
4 | private constructor(
5 | readonly offset: number,
6 | readonly buffer: DataView) {}
7 |
8 | static create(stream: app.BinaryReader) {
9 | const offset = stream.readVariableLength();
10 | const bufferSize = stream.readVariableLength();
11 | const buffer = stream.readBytes(bufferSize);
12 | return new EntityUpdateEntityMember(offset, buffer);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/api/providers/AliveProvider.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class AliveProvider implements app.IPacketProvider {
4 | private nextActiveTime = this.getNextTime();
5 |
6 | update(stream: app.BinaryWriter) {
7 | if (this.nextActiveTime >= Date.now()) return;
8 | new app.BasicAlive().write(stream);
9 | this.nextActiveTime = this.getNextTime();
10 | }
11 |
12 | private getNextTime() {
13 | return Date.now() + 10000;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/api/providers/EntityProvider.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityProvider implements app.IPacketProvider {
4 | private readonly createEntities = new Map();
5 | private readonly deleteEntities = new Map();
6 | private readonly livingEntities = new Map();
7 | private readonly lookupEntities = new Map();
8 | private readonly releasedIds: Array = [];
9 | private nextId = 0;
10 |
11 | create(entity: app.Entity) {
12 | const id = this.releasedIds.pop();
13 | if (typeof id !== 'undefined') {
14 | this.createEntities.set(id, entity);
15 | this.lookupEntities.set(entity, id);
16 | } else {
17 | const id = this.nextId;
18 | this.createEntities.set(id, entity);
19 | this.lookupEntities.set(entity, id);
20 | this.nextId++;
21 | }
22 | }
23 |
24 | delete(entity: app.Entity) {
25 | const id = this.lookupEntities.get(entity);
26 | if (typeof id === 'undefined') {
27 | throw new Error();
28 | } else if (this.createEntities.has(id)) {
29 | this.createEntities.delete(id);
30 | this.lookupEntities.delete(entity);
31 | } else if (this.livingEntities.has(id)) {
32 | this.deleteEntities.set(id, entity);
33 | this.livingEntities.delete(id);
34 | this.lookupEntities.delete(entity);
35 | }
36 | }
37 |
38 | receive(packet: app.BasicSync | app.EntityUpdate) {
39 | if (packet instanceof app.EntityUpdate) {
40 | this.receiveUpdate(packet);
41 | } else {
42 | this.receiveSync(packet);
43 | }
44 | }
45 |
46 | update(stream: app.BinaryWriter, syncId: number) {
47 | for (const id of this.deleteEntities.keys()) {
48 | const packet = new app.EntityDelete(id);
49 | this.deleteEntities.delete(id);
50 | this.releasedIds.push(id);
51 | packet.write(stream);
52 | }
53 | for (const [id, entity] of this.createEntities) {
54 | const packet = new app.EntityCreate(id, entity.address, convertMembers(entity.members), Boolean(entity.options?.requestBatch));
55 | this.createEntities.delete(id);
56 | this.livingEntities.set(id, entity);
57 | packet.write(stream);
58 | }
59 | for (const [id, entity] of this.livingEntities) {
60 | if (!entity.options || !entity.options.enableUpdate) continue;
61 | const packet = entity.update(id, syncId);
62 | if (!packet) continue;
63 | packet.write(stream);
64 | }
65 | }
66 |
67 | private receiveUpdate(packet: app.EntityUpdate) {
68 | for (const child of packet.entities) {
69 | const entity = this.livingEntities.get(child.id);
70 | if (!entity) continue;
71 | entity.receive(child);
72 | }
73 | }
74 |
75 | private receiveSync(packet: app.BasicSync) {
76 | for (const entity of this.livingEntities.values()) {
77 | if (!entity.options || !entity.options.enableUpdate) continue;
78 | entity.receive(packet);
79 | }
80 | }
81 | }
82 |
83 | function convertMembers(members: Map) {
84 | const result: Array = [];
85 | for (const [id, member] of members) result.push(new app.EntityCreateMember(id, member.interval, member.buffer.byteLength));
86 | return result;
87 | }
88 |
--------------------------------------------------------------------------------
/src/lib/api/streams/BinaryReader.ts:
--------------------------------------------------------------------------------
1 | export class BinaryReader {
2 | private readonly buffer: DataView;
3 | private offset: number;
4 |
5 | constructor(buffer: ArrayBuffer) {
6 | this.buffer = new DataView(buffer);
7 | this.offset = 0;
8 | }
9 |
10 | hasBytes() {
11 | return this.offset < this.buffer.byteLength - this.buffer.byteOffset;
12 | }
13 |
14 | readBytes(size: number) {
15 | const result = new DataView(this.buffer.buffer, this.offset, size);
16 | this.offset += size;
17 | return result;
18 | }
19 |
20 | readUInt8() {
21 | const result = this.buffer.getUint8(this.offset);
22 | this.offset += 1;
23 | return result;
24 | }
25 |
26 | readUInt64() {
27 | const result = this.buffer.getBigUint64(this.offset, true);
28 | this.offset += 8;
29 | return result;
30 | }
31 |
32 | readVariableLength() {
33 | var more = true;
34 | var value = 0;
35 | var shift = 0;
36 | while (more) {
37 | var b = this.readUInt8();
38 | more = (b & 0x80) !== 0;
39 | value |= (b & 0x7F) << shift;
40 | shift += 7;
41 | }
42 | return value;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/api/streams/BinaryWriter.ts:
--------------------------------------------------------------------------------
1 | export class BinaryWriter {
2 | private buffer: DataView;
3 | private offset: number;
4 |
5 | constructor() {
6 | this.buffer = new DataView(new ArrayBuffer(1024));
7 | this.offset = 0;
8 | }
9 |
10 | hasBytes() {
11 | return Boolean(this.offset);
12 | }
13 |
14 | writeBytes(value: DataView) {
15 | for (let i = 0; i < value.byteLength; i++) {
16 | this.writeUInt8(value.getUint8(i));
17 | }
18 | }
19 |
20 | writeUInt8(value: number) {
21 | this.prepare(1);
22 | this.buffer.setUint8(this.offset, value);
23 | this.offset += 1;
24 | }
25 |
26 | writeUInt64(value: bigint) {
27 | this.prepare(8);
28 | this.buffer.setBigUint64(this.offset, value, true);
29 | this.offset += 8;
30 | }
31 |
32 | writeVariableLength(value: number) {
33 | let more = true;
34 | while (more) {
35 | let chunk = value & 0x7F;
36 | value >>= 7;
37 | more = value != 0;
38 | chunk |= more ? 0x80 : 0;
39 | this.writeUInt8(chunk);
40 | }
41 | }
42 |
43 | toBuffer() {
44 | return new DataView(this.buffer.buffer, 0, this.offset);
45 | }
46 |
47 | private prepare(count: number) {
48 | while (this.offset + count > this.buffer.byteLength) {
49 | const result = new DataView(new ArrayBuffer(this.buffer.byteLength * 2));
50 | for (let i = 0; i < this.offset; i++) result.setUint8(i, this.buffer.getUint8(i));
51 | this.buffer = result;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/lib/core/Core.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Core {
4 | readonly buttonList = new app.ButtonList(this.address);
5 | readonly levelName = new app.LevelName(this.address + app.offsets.core.levelName);
6 | readonly localPlayer = new app.LocalPlayer(this.address + app.offsets.core.localPlayer);
7 | readonly itemList = this.itemFilter.map;
8 | readonly npcList = this.npcFilter.map;
9 | readonly playerList = this.playerFilter.map;
10 |
11 | private constructor(
12 | private readonly address: bigint,
13 | private readonly channel: app.api.Channel,
14 | private readonly entityList = new app.EntityList(address + app.offsets.core.clEntityList),
15 | private readonly itemFilter = new app.EntityListFilter(app.Item, 'prop_survival'),
16 | private readonly npcFilter = new app.EntityListFilter(app.NPC, 'npc_dummie'),
17 | private readonly playerFilter = new app.EntityListFilter(app.Player, 'player'),
18 | private readonly signifierList = new app.SignifierList(channel)) {
19 | this.channel.create(this.buttonList);
20 | this.channel.create(this.entityList);
21 | this.channel.create(this.levelName);
22 | this.channel.create(this.localPlayer);
23 | }
24 |
25 | static async createAsync(server: app.api.Server) {
26 | const processes = await server.processesAsync();
27 | const targetProcess = processes.find(x => x.command.toLowerCase().endsWith('r5apex.exe'));
28 | if (!targetProcess) throw new Error('Invalid process!');
29 | const regions = await targetProcess.regionsAsync();
30 | const targetRegion = regions.find(x => x.pathname.toLowerCase().endsWith('r5apex.exe'))
31 | ?? regions.find(x => x.perms == 1 && x.pathname.startsWith('/memfd'))
32 | ?? regions.find(x => x.start === BigInt(0x140000000));
33 | if (!targetRegion) throw new Error('Invalid region!');
34 | return new Core(targetRegion.start, new app.api.Channel(targetProcess.pid));
35 | }
36 |
37 | async runAsync(renderFrame: () => void) {
38 | const filters = [this.itemFilter, this.npcFilter, this.playerFilter];
39 | await this.channel.runAsync(() => {
40 | this.entityList.update(this.channel);
41 | filters.forEach(x => x.update(this.channel, this.entityList, this.signifierList));
42 | renderFrame();
43 | });
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/lib/core/classes/ButtonList.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class ButtonList extends app.api.Adapter {
4 | constructor(address: bigint,
5 | readonly inSpeed = new app.UInt8(app.offsets.button.inSpeed),
6 | readonly inAttack = new app.UInt8(app.offsets.button.inAttack)) {
7 | super(new app.api.Entity(address, [inSpeed, inAttack]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/core/classes/Entity.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Entity extends app.api.Adapter {
4 | constructor(address: bigint,
5 | private readonly signifierName = new app.UInt64(app.offsets.entity.iSignifierName, 60000)) {
6 | super(new app.api.Entity(address, [signifierName]));
7 | }
8 |
9 | get value() {
10 | return this.signifierName.value;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/core/classes/EntityList.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | const maxEntities = 0x10000;
3 |
4 | export class EntityList extends app.api.Adapter {
5 | private readonly entities = new Map();
6 | private nextTime = 0;
7 |
8 | constructor(address: bigint,
9 | private readonly pointers = Array(maxEntities).fill(0).map((_, i) => new app.UInt64(i << 5, 1000))) {
10 | super(new app.api.Entity(address, pointers, {requestBatch: true}));
11 | }
12 |
13 | get map(): ReadonlyMap {
14 | return this.entities;
15 | }
16 |
17 | update(channel: app.api.Channel) {
18 | if (!this.nextTime || this.nextTime < Date.now()) {
19 | this.onUpdate(channel);
20 | this.nextTime = Date.now() + 1000;
21 | }
22 | }
23 |
24 | private checkCreate(address: bigint, channel: app.api.Channel, knownSet: Set) {
25 | if (!this.entities.has(address)) {
26 | const entity = new app.Entity(address);
27 | this.entities.set(address, entity);
28 | channel.create(entity);
29 | knownSet.add(address);
30 | } else {
31 | knownSet.add(address);
32 | }
33 | }
34 |
35 | private onUpdate(channel: app.api.Channel) {
36 | const knownSet: Set = new Set();
37 | for (const pointer of this.pointers) {
38 | const address = pointer.value;
39 | if (!address) continue;
40 | this.checkCreate(address, channel, knownSet);
41 | }
42 | for (const [k, v] of this.entities) {
43 | if (knownSet.has(k)) continue;
44 | this.entities.delete(k);
45 | channel.delete(v);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/lib/core/classes/EntityListFilter.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class EntityListFilter> {
4 | private readonly entities = new Map();
5 | private nextTime = 0;
6 |
7 | constructor(
8 | private readonly Constructor: new (address: bigint) => T,
9 | private readonly signifier: string) {}
10 |
11 | get map(): ReadonlyMap {
12 | return this.entities;
13 | }
14 |
15 | update(channel: app.api.Channel, entityList: app.EntityList, signifierList: app.SignifierList) {
16 | if (!this.nextTime || this.nextTime < Date.now()) {
17 | this.onUpdate(channel, entityList, signifierList);
18 | this.nextTime = Date.now() + 1000;
19 | }
20 | }
21 |
22 | private checkCreate(address: bigint, channel: app.api.Channel, knownSet: Set) {
23 | if (!this.entities.has(address)) {
24 | const entity = new this.Constructor(address);
25 | this.entities.set(address, entity);
26 | channel.create(entity);
27 | knownSet.add(address);
28 | } else {
29 | knownSet.add(address);
30 | }
31 | }
32 |
33 | private onUpdate(channel: app.api.Channel, entityList: app.EntityList, signifierList: app.SignifierList) {
34 | const knownSet = new Set();
35 | for (const x of entityList.map.values()) {
36 | const signifier = signifierList.get(x.value);
37 | if (signifier.value !== this.signifier) continue;
38 | this.checkCreate(x.source.address, channel, knownSet);
39 | }
40 | for (const [k, v] of this.entities) {
41 | if (knownSet.has(k)) continue;
42 | this.entities.delete(k);
43 | channel.delete(v);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/lib/core/classes/LevelName.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class LevelName extends app.api.Adapter {
4 | constructor(address: bigint,
5 | private readonly levelName = new app.CString(0, 1000, 32)) {
6 | super(new app.api.Entity(address, [levelName]));
7 | }
8 |
9 | get value() {
10 | return this.levelName.value;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/core/classes/LocalPlayer.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class LocalPlayer extends app.api.Adapter {
4 | constructor(address: bigint,
5 | private readonly localPlayer = new app.UInt64(0, 1000)) {
6 | super(new app.api.Entity(address, [localPlayer]));
7 | }
8 |
9 | get value() {
10 | return this.localPlayer.value;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/core/classes/Signifier.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Signifier extends app.api.Adapter {
4 | constructor(address: bigint,
5 | private readonly signifierName = new app.CString(0, 60000, 32)) {
6 | super(new app.api.Entity(address, [signifierName]));
7 | }
8 |
9 | get value() {
10 | return this.signifierName.value;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/core/classes/SignifierList.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class SignifierList {
4 | constructor(
5 | private readonly channel: app.api.Channel,
6 | private readonly signifiers: Map = new Map()) {}
7 |
8 | get(address: bigint) {
9 | return this.signifiers.get(address) ?? this.create(address);
10 | }
11 |
12 | private create(address: bigint) {
13 | const signifier = new app.Signifier(address);
14 | this.signifiers.set(address, signifier);
15 | this.channel.create(signifier);
16 | return signifier;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/core/entities/Item.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Item extends app.api.Adapter {
4 | constructor(
5 | readonly address: bigint,
6 | readonly localOrigin = new app.Vector(app.offsets.entity.localOrigin, 60000),
7 | readonly highlightFunctionBits = new app.Glow(app.offsets.item.highlightFunctionBits, 1000),
8 | readonly customScriptInt = new app.UInt32(app.offsets.item.customScriptInt, 60000)) {
9 | super(new app.api.Entity(address, [localOrigin, highlightFunctionBits, customScriptInt], {enableUpdate: true}));
10 | }
11 |
12 | toString() {
13 | return app.serialize(this);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/core/entities/NPC.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class NPC extends app.api.Adapter {
4 | constructor(
5 | readonly address: bigint,
6 | readonly localOrigin = new app.Vector(app.offsets.entity.localOrigin),
7 | readonly lastVisibleTime = new app.Float32(app.offsets.entity.lastVisibleTime)) {
8 | super(new app.api.Entity(address, [localOrigin, lastVisibleTime], {enableUpdate: true}));
9 | }
10 |
11 | createColor() {
12 | return '#FFF';
13 | }
14 |
15 | toString() {
16 | return app.serialize(this);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/core/entities/Player.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Player extends app.api.Adapter {
4 | constructor(
5 | readonly address: bigint,
6 | readonly localOrigin = new app.Vector(app.offsets.entity.localOrigin),
7 | readonly glowEnable = new app.UInt8(app.offsets.player.glowEnable),
8 | readonly glowThroughWalls = new app.UInt8(app.offsets.player.glowThroughWall),
9 | readonly teamNum = new app.UInt8(app.offsets.player.iTeamNum, 1000),
10 | readonly name = new app.UInt64(app.offsets.player.iName),
11 | readonly lifeState = new app.UInt8(app.offsets.player.lifeState),
12 | readonly lastVisibleTime = new app.Float32(app.offsets.entity.lastVisibleTime),
13 | readonly vecPunchWeaponAngle = new app.Vector(app.offsets.player.vecPunchWeaponAngle),
14 | readonly viewAngle = new app.Vector(app.offsets.player.viewAngle),
15 | readonly bleedoutState = new app.UInt8(app.offsets.player.bleedoutState)) {
16 | super(new app.api.Entity(address, [localOrigin, glowEnable, glowThroughWalls, teamNum, name, lifeState, lastVisibleTime, vecPunchWeaponAngle, viewAngle, bleedoutState], {enableUpdate: true}));
17 | }
18 |
19 | get isValid() {
20 | return !this.lifeState.value
21 | && this.name.value
22 | && this.glowEnable.value !== 0
23 | && this.glowEnable.value !== 255;
24 | }
25 |
26 | createColor(otherPlayer: app.Player) {
27 | return this.isSameTeam(otherPlayer)
28 | ? (this.bleedoutState.value ? '#FFFF00' : '#00FF00')
29 | : (this.bleedoutState.value ? '#FFA500' : '#FF0000');
30 | }
31 |
32 | isSameTeam(otherPlayer: app.Player) {
33 | return this.teamNum.value === otherPlayer.teamNum.value;
34 | }
35 |
36 | toString() {
37 | return app.serialize(this);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/lib/core/helpers.ts:
--------------------------------------------------------------------------------
1 | export function serialize(item: T) {
2 | return `(${Object.entries(item)
3 | .sort(([a], [b]) => a.localeCompare(b))
4 | .map(([k, v]) => `${k}=${v}`)
5 | .join(',')})`;
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/core/index.ts:
--------------------------------------------------------------------------------
1 | export {api} from '..';
2 | export * from './helpers';
3 | export * from './offsets';
4 | export * from './classes/ButtonList';
5 | export * from './classes/Entity';
6 | export * from './classes/EntityList';
7 | export * from './classes/EntityListFilter';
8 | export * from './classes/LevelName';
9 | export * from './classes/LocalPlayer';
10 | export * from './classes/Signifier';
11 | export * from './classes/SignifierList';
12 | export * from './entities/Item';
13 | export * from './entities/NPC';
14 | export * from './entities/Player';
15 | export * from './models/GlowData';
16 | export * from './models/VectorData';
17 | export * from './types/CString';
18 | export * from './types/Float32';
19 | export * from './types/Glow';
20 | export * from './types/UInt8';
21 | export * from './types/UInt32';
22 | export * from './types/UInt64';
23 | export * from './types/Vector';
24 | export * from './Core';
25 |
--------------------------------------------------------------------------------
/src/lib/core/models/GlowData.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class GlowData {
4 | constructor(
5 | readonly inside: number,
6 | readonly outline: number,
7 | readonly outlineRadius: number,
8 | readonly customState: number,
9 | readonly isDrawn: boolean,
10 | readonly isPostProcess: boolean) {}
11 |
12 | static fromUInt32(value: number) {
13 | const inside = value & 0xFF;
14 | const outline = value >> 8 & 0xFF;
15 | const outlineRadius = value >> 16 & 0xFF;
16 | const customState = value >> 24 & 0x3F;
17 | const isDrawn = Boolean(value >> 24 & 0x40);
18 | const isPostProcess = Boolean(value >> 24 & 0x80);
19 | return new GlowData(inside, outline, outlineRadius, customState, isDrawn, isPostProcess);
20 | }
21 |
22 | isSame(otherGlow: GlowData) {
23 | return this.inside === otherGlow.inside
24 | && this.outline === otherGlow.outline
25 | && this.outlineRadius === otherGlow.outlineRadius
26 | && this.customState === otherGlow.customState
27 | && this.isDrawn === otherGlow.isDrawn
28 | && this.isPostProcess === otherGlow.isPostProcess;
29 | }
30 |
31 | toUInt32() {
32 | let result = 0;
33 | result |= Math.min(0xFF, this.inside);
34 | result |= Math.min(0xFF, this.outline) << 8;
35 | result |= Math.min(0xFF, this.outlineRadius) << 16;
36 | result |= Math.min(0x3F, this.customState) << 24;
37 | result |= this.isDrawn ? 0x40 << 24 : 0;
38 | result |= this.isPostProcess ? 0x80 << 24 : 0;
39 | return result;
40 | }
41 |
42 | toString() {
43 | return app.serialize(this);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/lib/core/models/VectorData.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class VectorData {
4 | static readonly none = new VectorData(0, 0, 0);
5 |
6 | constructor(
7 | readonly x: number,
8 | readonly y: number,
9 | readonly z: number) {}
10 |
11 | isSame(otherVector: VectorData) {
12 | return this.x === otherVector.x
13 | && this.y === otherVector.y
14 | && this.z === otherVector.z;
15 | }
16 |
17 | subtract(otherVector: VectorData) {
18 | return new VectorData(
19 | this.x - otherVector.x,
20 | this.y - otherVector.y,
21 | this.z - otherVector.z);
22 | }
23 |
24 | toString() {
25 | return app.serialize(this);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/lib/core/offsets.ts:
--------------------------------------------------------------------------------
1 | export const offsets = {
2 | button: {
3 | inSpeed: 0x759bfa0, // [Buttons] -> in_speed
4 | inAttack: 0x759bff8 // [Buttons] -> in_attack
5 | },
6 | core: {
7 | levelName: BigInt(0x13a17b8), // [Miscellaneous] -> LevelName
8 | clEntityList: BigInt(0x1a75038), // [Miscellaneous] -> cl_entitylist
9 | localPlayer: BigInt(0x1e25418) // [Miscellaneous] -> LocalPlayer
10 | },
11 | entity: {
12 | localOrigin: 0x158, // [DataMap.CBaseViewModel] -> m_localOrigin
13 | iSignifierName: 0x580, // [RecvTable.DT_BaseEntity] -> m_iSignifierName
14 | lastVisibleTime: 0x1a44 // [Miscellaneous] -> CPlayer!lastVisibleTime
15 | },
16 | item: {
17 | highlightFunctionBits: 0x2c0, // [RecvTable.DT_HighlightSettings] -> m_highlightFunctionBits
18 | customScriptInt: 0x1628 // [RecvTable.DT_PropSurvival] -> m_customScriptInt
19 | },
20 | player: {
21 | glowEnable: 0x3c8, // [RecvTable.DT_HighlightSettings] -> m_highlightServerContextID + 0x8
22 | glowThroughWall: 0x3d0, // [RecvTable.DT_HighlightSettings] -> m_highlightServerContextID + 0x10
23 | iTeamNum: 0x448, // [RecvTable.DT_BaseEntity] -> m_iTeamNum
24 | iName: 0x589, // [RecvTable.DT_BaseEntity] -> m_iName
25 | lifeState: 0x798, // [RecvTable.DT_Player] -> m_lifeState
26 | vecPunchWeaponAngle: 0x23f8, // [DataMap.C_Player] -> m_currentFrameLocalPlayer.m_vecPunchWeapon_Angle
27 | viewAngle: 0x24e0, // [DataMap.C_Player] -> m_ammoPoolCapacity - 0x14
28 | bleedoutState: 0x2688 // [RecvTable.DT_Player] -> m_bleedoutState
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/lib/core/types/CString.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class CString extends app.api.Adapter {
4 | constructor(offset: number, interval: number, size: number) {
5 | super(new app.api.EntityMember(offset, interval, size));
6 | }
7 |
8 | get value() {
9 | let result = '';
10 | iterateBytes(this.source.buffer, x => result += x);
11 | return result;
12 | }
13 |
14 | toString() {
15 | return this.value.toString();
16 | }
17 | }
18 |
19 | function iterateBytes(buffer: DataView, add: (value: string) => void) {
20 | for (let i = 0; i < buffer.byteLength; i++) {
21 | const charCode = buffer.getUint8(i);
22 | if (charCode === 0) break;
23 | add(String.fromCharCode(charCode));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/lib/core/types/Float32.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Float32 extends app.api.Adapter {
4 | constructor(offset: number, interval = 0) {
5 | super(new app.api.EntityMember(offset, interval, 4));
6 | }
7 |
8 | get value() {
9 | return this.source.buffer.getFloat32(0, true);
10 | }
11 |
12 | set value(value: number) {
13 | if (value === this.value) return;
14 | this.source.buffer.setFloat32(0, value, true);
15 | this.source.send = true;
16 | }
17 |
18 | toString() {
19 | return this.value.toString();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/core/types/Glow.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Glow extends app.api.Adapter {
4 | constructor(offset: number, interval = 0) {
5 | super(new app.api.EntityMember(offset, interval, 4));
6 | }
7 |
8 | get value() {
9 | const value = this.source.buffer.getUint32(0, true);
10 | return app.GlowData.fromUInt32(value);
11 | }
12 |
13 | set value(value: app.GlowData) {
14 | if (this.value.isSame(value)) return;
15 | this.source.buffer.setUint32(0, value.toUInt32(), true);
16 | this.source.send = true;
17 | }
18 |
19 | toString() {
20 | return app.serialize(this.value);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/lib/core/types/UInt32.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class UInt32 extends app.api.Adapter {
4 | constructor(offset: number, interval = 0) {
5 | super(new app.api.EntityMember(offset, interval, 4));
6 | }
7 |
8 | get value() {
9 | return this.source.buffer.getUint32(0, true);
10 | }
11 |
12 | set value(value: number) {
13 | if (value === this.value) return;
14 | this.source.buffer.setUint32(0, value, true);
15 | this.source.send = true;
16 | }
17 |
18 | toString() {
19 | return this.value.toString();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/core/types/UInt64.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class UInt64 extends app.api.Adapter {
4 | constructor(offset: number, interval = 0) {
5 | super(new app.api.EntityMember(offset, interval, 8));
6 | }
7 |
8 | get value() {
9 | return this.source.buffer.getBigUint64(0, true);
10 | }
11 |
12 | set value(value: bigint) {
13 | if (value === this.value) return;
14 | this.source.buffer.setBigUint64(0, value, true);
15 | this.source.send = true;
16 | }
17 |
18 | toString() {
19 | return this.value.toString();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/core/types/UInt8.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class UInt8 extends app.api.Adapter {
4 | constructor(offset: number, interval = 0) {
5 | super(new app.api.EntityMember(offset, interval, 1));
6 | }
7 |
8 | get value() {
9 | return this.source.buffer.getUint8(0);
10 | }
11 |
12 | set value(value: number) {
13 | if (value === this.value) return;
14 | this.source.buffer.setUint8(0, value);
15 | this.source.send = true;
16 | }
17 |
18 | toString() {
19 | return this.value.toString();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/core/types/Vector.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export class Vector extends app.api.Adapter {
4 | constructor(offset: number, interval = 0) {
5 | super(new app.api.EntityMember(offset, interval, 12));
6 | }
7 |
8 | delta(value: app.VectorData) {
9 | if (this.value.isSame(value)) return;
10 | const {x, y, z} = value.subtract(this.value);
11 | this.source.buffer.setFloat32(0, value.x, true);
12 | this.source.buffer.setFloat32(4, value.y, true);
13 | this.source.buffer.setFloat32(8, value.z, true);
14 | this.source.deltas = [delta(0, x), delta(4, y), delta(8, z)];
15 | this.source.send = true;
16 | }
17 |
18 | get value() {
19 | const x = this.source.buffer.getFloat32(0, true);
20 | const y = this.source.buffer.getFloat32(4, true);
21 | const z = this.source.buffer.getFloat32(8, true);
22 | return new app.VectorData(x, y, z);
23 | }
24 |
25 | set value(value: app.VectorData) {
26 | if (this.value.isSame(value)) return;
27 | this.source.buffer.setFloat32(0, value.x, true);
28 | this.source.buffer.setFloat32(4, value.y, true);
29 | this.source.buffer.setFloat32(8, value.z, true);
30 | this.source.send = true;
31 | }
32 |
33 | toString() {
34 | return app.serialize(this.value);
35 | }
36 | }
37 |
38 | function delta(offset: number, value: number) {
39 | const data = new DataView(new ArrayBuffer(4));
40 | data.setFloat32(0, value, true);
41 | return new app.api.EntityChangeMemberDelta(offset, app.api.DeltaType.Float32, data);
42 | }
43 |
--------------------------------------------------------------------------------
/src/lib/features/Map.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Map {
4 | private readonly context: CanvasRenderingContext2D;
5 | private readonly image = new Image();
6 | private map?: ReturnType;
7 | private ratioX = 0;
8 | private ratioY = 0;
9 | private scaleR = 0;
10 | private scaleX = 0;
11 | private scaleY = 0;
12 | private shiftX = 0;
13 | private shiftY = 0;
14 |
15 | constructor(
16 | private readonly canvas: HTMLCanvasElement) {
17 | this.context = canvas.getContext('2d')!;
18 | }
19 |
20 | refresh(levelName: string) {
21 | this.prepare(levelName);
22 | this.update();
23 | this.renderBackground();
24 | }
25 |
26 | renderItems(items: Iterable, itemSet: Set) {
27 | for (const item of items) {
28 | if (!itemSet.has(item.customScriptInt.value)) continue;
29 | const position = this.calculatePosition(item.localOrigin);
30 | if (position) {
31 | this.context.beginPath();
32 | this.context.arc(position.x, position.y, this.scaleR * 4, 0, Math.PI * 2);
33 | this.context.fillStyle = '#FFF';
34 | this.context.fill();
35 | }
36 | }
37 | }
38 |
39 | renderPlayers(localPlayer: app.core.Player, players: Iterable) {
40 | for (const player of players) {
41 | if (!player.isValid) continue;
42 | const position = this.calculatePosition(player.localOrigin);
43 | if (position) {
44 | this.context.beginPath();
45 | this.context.arc(position.x, position.y, this.scaleR * 8, 0, Math.PI * 2);
46 | this.context.fillStyle = player.createColor(localPlayer);
47 | this.context.fill();
48 | }
49 | }
50 | }
51 |
52 | private calculatePosition(localOrigin: app.core.Vector) {
53 | if (this.map) {
54 | const x = this.shiftX + (1 / this.image.width * this.scaleX) * (localOrigin.value.x - this.map.x) / this.ratioX;
55 | const y = this.shiftY + (1 / this.image.height * this.scaleY) * (localOrigin.value.y - this.map.y) / -this.ratioY;
56 | return {x, y};
57 | } else {
58 | return;
59 | }
60 | }
61 |
62 | private prepare(levelName: string) {
63 | this.map = getDataByLevelName(levelName);
64 | this.image.src = this.map ? `images/maps/${levelName}.webp` : 'images/maps.webp';
65 | }
66 |
67 | private renderBackground() {
68 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
69 | this.context.drawImage(this.image, 0, 0, this.image.width, this.image.height, this.shiftX, this.shiftY, this.scaleX, this.scaleY);
70 | }
71 |
72 | private update() {
73 | this.ratioX = this.map ? (this.map.y - this.map.x) / this.image.width : 0;
74 | this.ratioY = this.map ? (this.map.y - this.map.x) / this.image.height : 0;
75 | this.scaleR = Math.min(this.canvas.width / this.image.width, this.canvas.height / this.image.height);
76 | this.scaleX = this.image.width * this.scaleR;
77 | this.scaleY = this.image.height * this.scaleR;
78 | this.shiftX = (this.canvas.width - this.scaleX) / 2;
79 | this.shiftY = (this.canvas.height - this.scaleY) / 2;
80 | }
81 | }
82 |
83 | function getDataByLevelName(levelName: string) {
84 | switch (levelName) {
85 | case 'mp_rr_canyonlands_mu3':
86 | return {x: -37541, y: 43886};
87 | case 'mp_rr_desertlands_mu3':
88 | return {x: -45056, y: 45055};
89 | case 'mp_rr_olympus_mu2':
90 | return {x: -52024, y: 48025};
91 | case 'mp_rr_tropic_island_mu1':
92 | return {x: -50606, y: 52139};
93 | default:
94 | return;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/lib/features/Radar.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Radar {
4 | private readonly context: CanvasRenderingContext2D;
5 | private centerX = 0;
6 | private centerY = 0;
7 | private outerRadius = 0;
8 |
9 | constructor(
10 | private readonly canvas: HTMLCanvasElement,
11 | private readonly maximumDistance = 200,
12 | private readonly numberOfRings = 8) {
13 | this.context = canvas.getContext('2d')!;
14 | }
15 |
16 | refresh() {
17 | this.update();
18 | this.renderBackground();
19 | this.renderLines();
20 | this.renderRings();
21 | }
22 |
23 | renderItems(localPlayer: app.core.Player, items: Iterable, itemSet: Set) {
24 | for (const item of items) {
25 | if (!itemSet.has(item.customScriptInt.value)) continue;
26 | const position = this.calculatePosition(localPlayer, item.localOrigin);
27 | if (position) {
28 | this.context.beginPath();
29 | this.context.arc(position.x, position.y, this.outerRadius / 80, 0, Math.PI * 2);
30 | this.context.fillStyle = '#FFF';
31 | this.context.fill();
32 | }
33 | }
34 | }
35 |
36 | renderNpcs(localPlayer: app.core.Player, npcs: Iterable) {
37 | for (const npc of npcs) {
38 | const position = this.calculatePosition(localPlayer, npc.localOrigin);
39 | if (position) {
40 | this.context.beginPath();
41 | this.context.arc(position.x, position.y, this.outerRadius / 40, 0, Math.PI * 2);
42 | this.context.fillStyle = npc.createColor();
43 | this.context.fill();
44 | }
45 | }
46 | }
47 |
48 | renderPlayers(localPlayer: app.core.Player, players: Iterable) {
49 | for (const player of players) {
50 | if (!player.isValid || player === localPlayer) continue;
51 | const position = this.calculatePosition(localPlayer, player.localOrigin);
52 | if (position) {
53 | this.context.beginPath();
54 | this.context.arc(position.x, position.y, this.outerRadius / 40, 0, Math.PI * 2);
55 | this.context.fillStyle = player.createColor(localPlayer);
56 | this.context.fill();
57 | }
58 | }
59 | }
60 |
61 | private calculatePosition(localPlayer: app.core.Player, origin: app.core.Vector) {
62 | const dx = (localPlayer.localOrigin.value.x - origin.value.x) * 0.0254;
63 | const dy = (localPlayer.localOrigin.value.y - origin.value.y) * 0.0254;
64 | const r = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
65 | if (r < this.maximumDistance) {
66 | const s = this.outerRadius / this.maximumDistance;
67 | const a = Math.sign(dy) * Math.acos(dx / r) - localPlayer.viewAngle.value.y * Math.PI / 180;
68 | const x = this.centerX + Math.sin(a) * r * s;
69 | const y = this.centerY + Math.cos(a) * r * s;
70 | return {x, y};
71 | } else {
72 | return;
73 | }
74 | }
75 |
76 | private renderBackground() {
77 | this.context.beginPath();
78 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
79 | this.context.arc(this.centerX, this.centerY, this.outerRadius, 0, Math.PI * 2);
80 | this.context.fillStyle = '#000';
81 | this.context.fill();
82 | }
83 |
84 | private renderLines() {
85 | this.context.strokeStyle = '#FFF';
86 | for (let i = 0; i < 8; i++) {
87 | const x = this.centerX + this.outerRadius * Math.cos(i * Math.PI * 0.25);
88 | const y = this.centerY + this.outerRadius * Math.sin(i * Math.PI * 0.25);
89 | this.context.beginPath();
90 | this.context.moveTo(this.centerX, this.centerY);
91 | this.context.lineTo(x, y);
92 | this.context.stroke();
93 | }
94 | }
95 |
96 | private renderRings() {
97 | this.context.strokeStyle = '#FFF';
98 | for (let i = 1; i <= this.numberOfRings; i++) {
99 | this.context.beginPath();
100 | this.context.arc(this.centerX, this.centerY, this.outerRadius * i / this.numberOfRings, 0, Math.PI * 2);
101 | this.context.stroke();
102 | }
103 | }
104 |
105 | private update() {
106 | this.centerX = this.canvas.width / 2;
107 | this.centerY = this.canvas.height / 2;
108 | this.outerRadius = (this.canvas.width > this.canvas.height ? this.canvas.height : this.canvas.width) / 2;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/lib/features/Recoil.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Recoil {
4 | private vecPunchWeaponAngle = app.core.VectorData.none;
5 |
6 | update(buttonList: app.core.ButtonList, localPlayer: app.core.Player, options: IRecoilOptions) {
7 | if (localPlayer.viewAngle.source.syncId) {
8 | /* We sent an update, but did not receive confirmation! */
9 | } else if (!buttonList.inAttack.value) {
10 | this.vecPunchWeaponAngle = app.core.VectorData.none;
11 | } else {
12 | const vecPunchWeaponAngle = localPlayer.vecPunchWeaponAngle.value;
13 | const viewAngle = localPlayer.viewAngle.value;
14 | if (Math.abs(vecPunchWeaponAngle.x) >= 0.5 || Math.abs(vecPunchWeaponAngle.y) >= 0.5 || Math.abs(vecPunchWeaponAngle.z) >= 0.5) {
15 | const x = viewAngle.x + (this.vecPunchWeaponAngle.x - vecPunchWeaponAngle.x) * options.percentage;
16 | const y = viewAngle.y + (this.vecPunchWeaponAngle.y - vecPunchWeaponAngle.y) * options.percentage;
17 | localPlayer.viewAngle.delta(new app.core.VectorData(x, y, viewAngle.z));
18 | this.vecPunchWeaponAngle = vecPunchWeaponAngle;
19 | }
20 | }
21 | }
22 | }
23 |
24 | export type IRecoilOptions = {
25 | percentage: number // 0 <-> 1
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/features/Sense.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class Sense {
4 | constructor(
5 | private readonly itemDefault = new app.core.GlowData(0, 110, 225, 25, true, false),
6 | private readonly itemHighlight = new app.core.GlowData(137, 108, 64, 0, true, false),
7 | private readonly maximumDistance = 200) {}
8 |
9 | resetItems(_: app.core.Player, items: Iterable) {
10 | for (const item of items) {
11 | if (item.highlightFunctionBits.value.isSame(this.itemHighlight)) {
12 | item.highlightFunctionBits.value = this.itemDefault;
13 | }
14 | }
15 | }
16 |
17 | resetPlayers(localPlayer: app.core.Player, players: Iterable) {
18 | for (const player of players) {
19 | if (player.isValid && !player.isSameTeam(localPlayer)) {
20 | if (this.inRange(localPlayer, player.localOrigin)) {
21 | /* This could be a Bloodhound scan! */
22 | } else if (player.glowEnable.value === 7) {
23 | player.glowEnable.value = 2;
24 | player.glowThroughWalls.value = 5;
25 | }
26 | }
27 | }
28 | }
29 |
30 | updateItems(localPlayer: app.core.Player, items: Iterable, itemSet: Set) {
31 | for (const item of items) {
32 | if (itemSet.has(item.customScriptInt.value) && this.inRange(localPlayer, item.localOrigin)) {
33 | item.highlightFunctionBits.value = this.itemHighlight;
34 | } else if (item.highlightFunctionBits.value.isSame(this.itemHighlight)) {
35 | item.highlightFunctionBits.value = this.itemDefault;
36 | }
37 | }
38 | }
39 |
40 | updatePlayers(localPlayer: app.core.Player, players: Iterable) {
41 | for (const player of players) {
42 | if (player.isValid && !player.isSameTeam(localPlayer)) {
43 | if (this.inRange(localPlayer, player.localOrigin)) {
44 | player.glowEnable.value = 7;
45 | player.glowThroughWalls.value = 2;
46 | } else if (player.glowEnable.value === 7) {
47 | player.glowEnable.value = 2;
48 | player.glowThroughWalls.value = 5;
49 | }
50 | }
51 | }
52 | }
53 |
54 | private inRange(localPlayer: app.core.Player, origin: app.core.Vector) {
55 | const dx = (localPlayer.localOrigin.value.x - origin.value.x) * 0.0254;
56 | const dy = (localPlayer.localOrigin.value.y - origin.value.y) * 0.0254;
57 | return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < this.maximumDistance;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/lib/features/index.ts:
--------------------------------------------------------------------------------
1 | export {core} from '..';
2 | export * from './Map';
3 | export * from './Radar';
4 | export * from './Recoil';
5 | export * from './Sense';
6 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * as api from './api';
2 | export * as core from './core';
3 | export * as features from './features';
4 | export * as items from './items';
5 |
--------------------------------------------------------------------------------
/src/lib/items/index.ts:
--------------------------------------------------------------------------------
1 | export * from './list';
2 | export * from './types/All';
3 | export * from './types/Ammo';
4 | export * from './types/AmmoType';
5 | export * from './types/Attachment';
6 | export * from './types/AttachmentType';
7 | export * from './types/Gear';
8 | export * from './types/GearType';
9 | export * from './types/Grenade';
10 | export * from './types/Regen';
11 | export * from './types/Weapon';
12 | export * from './types/WeaponType';
13 |
--------------------------------------------------------------------------------
/src/lib/items/list.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | // TODO: Missing Heat Shield & Mobile Respawn Beacon
4 | export const list: Array = [{
5 | itemId: 1,
6 | itemType: 'Weapon',
7 | name: 'Kraber .50-Cal Sniper',
8 | ammoType: 'Special',
9 | weaponType: 'Sniper',
10 | slots: []
11 | }, {
12 | itemId: 2,
13 | itemType: 'Weapon',
14 | name: 'Mastiff Shotgun',
15 | ammoType: 'Special',
16 | weaponType: 'Shotgun',
17 | slots: ['Optics']
18 | }, {
19 | itemId: 7,
20 | itemType: 'Weapon',
21 | name: 'L-STAR EMG',
22 | ammoType: 'Energy',
23 | weaponType: 'Marksman',
24 | slots: ['BarrelStabilizer', 'ExtendedEnergyMag', 'Optics', 'StandardStock']
25 | }, {
26 | itemId: 12,
27 | itemType: 'Weapon',
28 | name: 'HAVOC Rifle',
29 | ammoType: 'Energy',
30 | weaponType: 'AR',
31 | slots: ['ExtendedEnergyMag', 'Optics', 'StandardStock', 'Turbocharger']
32 | }, {
33 | itemId: 17,
34 | itemType: 'Weapon',
35 | name: 'Devotion LMG',
36 | ammoType: 'Energy',
37 | weaponType: 'LMG',
38 | slots: ['BarrelStabilizer', 'ExtendedEnergyMag', 'Optics', 'StandardStock', 'Turbocharger']
39 | }, {
40 | itemId: 22,
41 | itemType: 'Weapon',
42 | name: 'Triple Take',
43 | ammoType: 'Energy',
44 | weaponType: 'Marksman',
45 | slots: ['ExtendedEnergyMag', 'Optics', 'SniperStock', 'KineticFeeder']
46 | }, {
47 | itemId: 27,
48 | itemType: 'Weapon',
49 | name: 'VK-47 Flatline',
50 | ammoType: 'Heavy',
51 | weaponType: 'AR',
52 | slots: ['ExtendedHeavyMag', 'Optics', 'StandardStock']
53 | }, {
54 | itemId: 32,
55 | itemType: 'Weapon',
56 | name: 'Hemlok Burst AR',
57 | ammoType: 'Heavy',
58 | weaponType: 'AR',
59 | slots: ['BarrelStabilizer', 'ExtendedHeavyMag', 'Optics', 'StandardStock', 'BoostedLoader']
60 | }, {
61 | itemId: 37,
62 | itemType: 'Weapon',
63 | name: 'G7 Scout',
64 | ammoType: 'Light',
65 | weaponType: 'Marksman',
66 | slots: ['BarrelStabilizer', 'ExtendedLightMag', 'Optics', 'SniperStock', 'DoubleTapTrigger']
67 | }, {
68 | itemId: 42,
69 | itemType: 'Weapon',
70 | name: 'Alternator SMG',
71 | ammoType: 'Light',
72 | weaponType: 'SMG',
73 | slots: ['LaserLight', 'ExtendedLightMag', 'Optics', 'StandardStock']
74 | }, {
75 | itemId: 47,
76 | itemType: 'Weapon',
77 | name: 'R-99 SMG',
78 | ammoType: 'Light',
79 | weaponType: 'SMG',
80 | slots: ['LaserLight', 'ExtendedLightMag', 'Optics', 'StandardStock']
81 | }, {
82 | itemId: 52,
83 | itemType: 'Weapon',
84 | name: 'Prowler Burst PDW',
85 | ammoType: 'Heavy',
86 | weaponType: 'SMG',
87 | slots: ['LaserLight', 'ExtendedHeavyMag', 'Optics', 'StandardStock']
88 | }, {
89 | itemId: 57,
90 | itemType: 'Weapon',
91 | name: 'Volt SMG',
92 | ammoType: 'Energy',
93 | weaponType: 'SMG',
94 | slots: ['LaserLight', 'ExtendedEnergyMag', 'Optics', 'StandardStock']
95 | }, {
96 | itemId: 62,
97 | itemType: 'Weapon',
98 | name: 'Longbow DMR',
99 | ammoType: 'Sniper',
100 | weaponType: 'Sniper',
101 | slots: ['BarrelStabilizer', 'ExtendedSniperMag', 'Optics', 'SniperStock', 'SkullpiercerRifling']
102 | }, {
103 | itemId: 67,
104 | itemType: 'Weapon',
105 | name: 'Charge Rifle',
106 | ammoType: 'Sniper',
107 | weaponType: 'Sniper',
108 | slots: ['Optics', 'SniperStock']
109 | }, {
110 | itemId: 72,
111 | itemType: 'Weapon',
112 | name: 'M600 Spitfire',
113 | ammoType: 'Light',
114 | weaponType: 'LMG',
115 | slots: ['ExtendedLightMag', 'Optics', 'StandardStock']
116 | }, {
117 | itemId: 77,
118 | itemType: 'Weapon',
119 | name: 'R-301 Carbine',
120 | ammoType: 'Light',
121 | weaponType: 'AR',
122 | slots: ['BarrelStabilizer', 'ExtendedLightMag', 'Optics', 'StandardStock']
123 | }, {
124 | itemId: 82,
125 | itemType: 'Weapon',
126 | name: 'EVA-8 Auto',
127 | ammoType: 'Shotgun',
128 | weaponType: 'Shotgun',
129 | slots: ['ShotgunBolt', 'Optics', 'StandardStock', 'DoubleTapTrigger']
130 | }, {
131 | itemId: 87,
132 | itemType: 'Weapon',
133 | name: 'Peacekeeper',
134 | ammoType: 'Shotgun',
135 | weaponType: 'Shotgun',
136 | slots: ['ShotgunBolt', 'Optics', 'KineticFeeder']
137 | }, {
138 | itemId: 92,
139 | itemType: 'Weapon',
140 | name: 'Mozambique Shotgun',
141 | ammoType: 'Shotgun',
142 | weaponType: 'Shotgun',
143 | slots: ['ShotgunBolt', 'Optics', 'HammerpointRounds']
144 | }, {
145 | itemId: 97,
146 | itemType: 'Weapon',
147 | name: 'Wingman',
148 | ammoType: 'Sniper',
149 | weaponType: 'Pistol',
150 | slots: ['ExtendedSniperMag', 'Optics', 'BoostedLoader']
151 | }, {
152 | itemId: 102,
153 | itemType: 'Weapon',
154 | name: 'P2020',
155 | ammoType: 'Light',
156 | weaponType: 'Pistol',
157 | slots: ['LaserLight', 'ExtendedLightMag', 'Optics', 'HammerpointRounds']
158 | }, {
159 | itemId: 107,
160 | itemType: 'Weapon',
161 | name: 'RE-45 Auto',
162 | ammoType: 'Light',
163 | weaponType: 'Pistol',
164 | slots: ['LaserLight', 'ExtendedLightMag', 'Optics', 'HammerpointRounds']
165 | }, {
166 | itemId: 112,
167 | itemType: 'Weapon',
168 | name: 'Sentinel',
169 | ammoType: 'Sniper',
170 | weaponType: 'Sniper',
171 | slots: ['ExtendedSniperMag', 'Optics', 'SniperStock']
172 | }, {
173 | itemId: 117,
174 | itemType: 'Weapon',
175 | name: 'Bocek Compound Bow',
176 | ammoType: 'Special',
177 | weaponType: 'Marksman',
178 | slots: ['Optics']
179 | }, {
180 | itemId: 118,
181 | itemType: 'Weapon',
182 | name: '30-30 Repeater',
183 | ammoType: 'Heavy',
184 | weaponType: 'Marksman',
185 | slots: ['ExtendedHeavyMag', 'Optics', 'SniperStock', 'SkullpiercerRifling']
186 | }, {
187 | itemId: 124,
188 | itemType: 'Ammo',
189 | name: 'Light Rounds',
190 | ammoType: 'Light'
191 | }, {
192 | itemId: 125,
193 | itemType: 'Ammo',
194 | name: 'Energy Ammo',
195 | ammoType: 'Energy'
196 | }, {
197 | itemId: 126,
198 | itemType: 'Ammo',
199 | name: 'Shotgun Shells',
200 | ammoType: 'Shotgun'
201 | }, {
202 | itemId: 127,
203 | itemType: 'Ammo',
204 | name: 'Heavy Rounds',
205 | ammoType: 'Heavy'
206 | }, {
207 | itemId: 128,
208 | itemType: 'Ammo',
209 | name: 'Sniper Ammo',
210 | ammoType: 'Sniper'
211 | }, {
212 | itemId: 130,
213 | itemType: 'Weapon',
214 | name: 'Rampage LMG',
215 | ammoType: 'Special',
216 | weaponType: 'LMG',
217 | slots: ['Optics']
218 | }, {
219 | itemId: 131,
220 | itemType: 'Weapon',
221 | name: 'C.A.R. SMG',
222 | ammoType: 'Heavy', // Or Light
223 | weaponType: 'SMG',
224 | slots: ['ExtendedHeavyMag', 'Optics', 'StandardStock']
225 | }, {
226 | itemId: 160,
227 | itemType: 'Regen',
228 | name: 'Ultimate Accelerant'
229 | }, {
230 | itemId: 161,
231 | itemType: 'Regen',
232 | name: 'Phoenix Kit'
233 | }, {
234 | itemId: 162,
235 | itemType: 'Regen',
236 | name: 'Med Kit'
237 | }, {
238 | itemId: 163,
239 | itemType: 'Regen',
240 | name: 'Syringe'
241 | }, {
242 | itemId: 164,
243 | itemType: 'Regen',
244 | name: 'Shield Battery'
245 | }, {
246 | itemId: 165,
247 | itemType: 'Regen',
248 | name: 'Shield Cell'
249 | }, {
250 | itemId: 166,
251 | itemType: 'Gear',
252 | name: 'Helmet (Level 1 / White)',
253 | gearType: 'Helmet',
254 | level: 1
255 | }, {
256 | itemId: 167,
257 | itemType: 'Gear',
258 | name: 'Helmet (Level 2 / Blue)',
259 | gearType: 'Helmet',
260 | level: 2
261 | }, {
262 | itemId: 168,
263 | itemType: 'Gear',
264 | name: 'Helmet (Level 3 / Purple)',
265 | gearType: 'Helmet',
266 | level: 3
267 | }, {
268 | itemId: 169,
269 | itemType: 'Gear',
270 | name: 'Helmet (Level 4 / Gold)',
271 | gearType: 'Helmet',
272 | level: 4
273 | }, {
274 | itemId: 170,
275 | itemType: 'Gear',
276 | name: 'Body Shield (Level 1 / White)',
277 | gearType: 'BodyShield',
278 | level: 1
279 | }, {
280 | itemId: 171,
281 | itemType: 'Gear',
282 | name: 'Body Shield (Level 2 / Blue)',
283 | gearType: 'BodyShield',
284 | level: 2
285 | }, {
286 | itemId: 172,
287 | itemType: 'Gear',
288 | name: 'Body Shield (Level 3 / Purple)',
289 | gearType: 'BodyShield',
290 | level: 3
291 | }, {
292 | itemId: 173,
293 | itemType: 'Gear',
294 | name: 'Body Shield (Level 4 / Gold)',
295 | gearType: 'BodyShield',
296 | level: 4
297 | }, {
298 | itemId: 175,
299 | itemType: 'Gear',
300 | name: 'Evo Shield (Level 1 / White)',
301 | gearType: 'EvoShield',
302 | level: 1
303 | }, {
304 | itemId: 176,
305 | itemType: 'Gear',
306 | name: 'Evo Shield (Level 2 / Blue)',
307 | gearType: 'EvoShield',
308 | level: 2
309 | }, {
310 | itemId: 177,
311 | itemType: 'Gear',
312 | name: 'Evo Shield (Level 3 / Purple)',
313 | gearType: 'EvoShield',
314 | level: 3
315 | }, {
316 | itemId: 178,
317 | itemType: 'Gear',
318 | name: 'Evo Shield (Level 4 / Red)',
319 | gearType: 'EvoShield',
320 | level: 4
321 | }, {
322 | itemId: 180,
323 | itemType: 'Gear',
324 | name: 'Knockdown Shield (Level 1 / White)',
325 | gearType: 'KnockdownShield',
326 | level: 1
327 | }, {
328 | itemId: 181,
329 | itemType: 'Gear',
330 | name: 'Knockdown Shield (Level 2 / Blue)',
331 | gearType: 'KnockdownShield',
332 | level: 2
333 | }, {
334 | itemId: 182,
335 | itemType: 'Gear',
336 | name: 'Knockdown Shield (Level 3 / Purple)',
337 | gearType: 'KnockdownShield',
338 | level: 3
339 | }, {
340 | itemId: 183,
341 | itemType: 'Gear',
342 | name: 'Knockdown Shield (Level 4 / Gold)',
343 | gearType: 'KnockdownShield',
344 | level: 4
345 | }, {
346 | itemId: 184,
347 | itemType: 'Gear',
348 | name: 'Backpack (Level 1 / White)',
349 | gearType: 'Backpack',
350 | level: 1
351 | }, {
352 | itemId: 185,
353 | itemType: 'Gear',
354 | name: 'Backpack (Level 2 / Blue)',
355 | gearType: 'Backpack',
356 | level: 2
357 | }, {
358 | itemId: 186,
359 | itemType: 'Gear',
360 | name: 'Backpack (Level 3 / Purple)',
361 | gearType: 'Backpack',
362 | level: 3
363 | }, {
364 | itemId: 187,
365 | itemType: 'Gear',
366 | name: 'Backpack (Level 4 / Gold)',
367 | gearType: 'Backpack',
368 | level: 4
369 | }, {
370 | itemId: 188,
371 | itemType: 'Grenade',
372 | name: 'Thermite Grenade'
373 | }, {
374 | itemId: 189,
375 | itemType: 'Grenade',
376 | name: 'Frag Grenade'
377 | }, {
378 | itemId: 190,
379 | itemType: 'Grenade',
380 | name: 'Arc Star'
381 | }, {
382 | itemId: 191,
383 | itemType: 'Attachment',
384 | name: '1x HCOG "Classic"',
385 | attachmentType: 'Optics',
386 | level: 1
387 | }, {
388 | itemId: 192,
389 | itemType: 'Attachment',
390 | name: '2x HCOG "Bruiser"',
391 | attachmentType: 'Optics',
392 | level: 2
393 | }, {
394 | itemId: 193,
395 | itemType: 'Attachment',
396 | name: '1x Holo',
397 | attachmentType: 'Optics',
398 | level: 1
399 | }, {
400 | itemId: 194,
401 | itemType: 'Attachment',
402 | name: '1x-2x Variable Holo',
403 | attachmentType: 'Optics',
404 | level: 2
405 | }, {
406 | itemId: 195,
407 | itemType: 'Attachment',
408 | name: '1x Digital Threat',
409 | attachmentType: 'Optics',
410 | weaponTypes: ['Pistol', 'Shotgun', 'SMG'],
411 | level: 4
412 | }, {
413 | itemId: 196,
414 | itemType: 'Attachment',
415 | name: '3x HCOG "Ranger"',
416 | attachmentType: 'Optics',
417 | weaponTypes: ['AR', 'LMG', 'Marksman', 'Sniper'], // Except Bocek
418 | level: 3
419 | }, {
420 | itemId: 197,
421 | itemType: 'Attachment',
422 | name: '2x-4x Variable AOG',
423 | attachmentType: 'Optics',
424 | weaponTypes: ['AR', 'LMG', 'Marksman', 'Sniper'],
425 | level: 3
426 | }, {
427 | itemId: 198,
428 | itemType: 'Attachment',
429 | name: '6x Sniper',
430 | attachmentType: 'Optics',
431 | weaponTypes: ['Sniper'],
432 | level: 2
433 | }, {
434 | itemId: 199,
435 | itemType: 'Attachment',
436 | name: '4x-8x Variable Sniper',
437 | attachmentType: 'Optics',
438 | weaponTypes: ['Sniper'],
439 | level: 3
440 | }, {
441 | itemId: 200,
442 | itemType: 'Attachment',
443 | name: '4x-10x Digital Sniper Threat',
444 | attachmentType: 'Optics',
445 | weaponTypes: ['Sniper'],
446 | level: 4
447 | }, {
448 | itemId: 201,
449 | itemType: 'Attachment',
450 | name: 'Barrel Stabilizer (Level 1 / White)',
451 | attachmentType: 'BarrelStabilizer',
452 | level: 1
453 | }, {
454 | itemId: 202,
455 | itemType: 'Attachment',
456 | name: 'Barrel Stabilizer (Level 2 / Blue)',
457 | attachmentType: 'BarrelStabilizer',
458 | level: 2
459 | }, {
460 | itemId: 203,
461 | itemType: 'Attachment',
462 | name: 'Barrel Stabilizer (Level 3 / Purple)',
463 | attachmentType: 'BarrelStabilizer',
464 | level: 3
465 | }, {
466 | itemId: 205,
467 | itemType: 'Attachment',
468 | name: 'Laser Sight (Level 1 / White)',
469 | attachmentType: 'LaserLight',
470 | level: 1
471 | }, {
472 | itemId: 206,
473 | itemType: 'Attachment',
474 | name: 'Laser Sight (Level 2 / Blue)',
475 | attachmentType: 'LaserLight',
476 | level: 2
477 | }, {
478 | itemId: 207,
479 | itemType: 'Attachment',
480 | name: 'Laser Sight (Level 3 / Purple)',
481 | attachmentType: 'LaserLight',
482 | level: 3
483 | }, {
484 | itemId: 208,
485 | itemType: 'Attachment',
486 | name: 'Extended Light Mag (Level 1 / White)',
487 | attachmentType: 'ExtendedLightMag',
488 | level: 1
489 | }, {
490 | itemId: 209,
491 | itemType: 'Attachment',
492 | name: 'Extended Light Mag (Level 2 / Blue)',
493 | attachmentType: 'ExtendedLightMag',
494 | level: 2
495 | }, {
496 | itemId: 210,
497 | itemType: 'Attachment',
498 | name: 'Extended Light Mag (Level 3 / Purple)',
499 | attachmentType: 'ExtendedLightMag',
500 | level: 3
501 | }, {
502 | itemId: 211,
503 | itemType: 'Attachment',
504 | name: 'Extended Light Mag (Level 4 / Gold)',
505 | attachmentType: 'ExtendedLightMag',
506 | level: 4
507 | }, {
508 | itemId: 212,
509 | itemType: 'Attachment',
510 | name: 'Extended Heavy Mag (Level 1 / White)',
511 | attachmentType: 'ExtendedHeavyMag',
512 | level: 1
513 | }, {
514 | itemId: 213,
515 | itemType: 'Attachment',
516 | name: 'Extended Heavy Mag (Level 2 / Blue)',
517 | attachmentType: 'ExtendedHeavyMag',
518 | level: 2
519 | }, {
520 | itemId: 214,
521 | itemType: 'Attachment',
522 | name: 'Extended Heavy Mag (Level 3 / Purple)',
523 | attachmentType: 'ExtendedHeavyMag',
524 | level: 3
525 | }, {
526 | itemId: 215,
527 | itemType: 'Attachment',
528 | name: 'Extended Heavy Mag (Level 4 / Gold)',
529 | attachmentType: 'ExtendedHeavyMag',
530 | level: 4
531 | }, {
532 | itemId: 216,
533 | itemType: 'Attachment',
534 | name: 'Extended Energy Mag (Level 1 / White)',
535 | attachmentType: 'ExtendedEnergyMag',
536 | level: 1
537 | }, {
538 | itemId: 217,
539 | itemType: 'Attachment',
540 | name: 'Extended Energy Mag (Level 2 / Blue)',
541 | attachmentType: 'ExtendedEnergyMag',
542 | level: 2
543 | }, {
544 | itemId: 218,
545 | itemType: 'Attachment',
546 | name: 'Extended Energy Mag (Level 3 / Purple)',
547 | attachmentType: 'ExtendedEnergyMag',
548 | level: 3
549 | }, {
550 | itemId: 219,
551 | itemType: 'Attachment',
552 | name: 'Extended Energy Mag (Level 4 / Gold)',
553 | attachmentType: 'ExtendedEnergyMag',
554 | level: 4
555 | }, {
556 | itemId: 220,
557 | itemType: 'Attachment',
558 | name: 'Extended Sniper Mag (Level 1 / White)',
559 | attachmentType: 'ExtendedSniperMag',
560 | level: 1
561 | }, {
562 | itemId: 221,
563 | itemType: 'Attachment',
564 | name: 'Extended Sniper Mag (Level 2 / Blue)',
565 | attachmentType: 'ExtendedSniperMag',
566 | level: 2
567 | }, {
568 | itemId: 222,
569 | itemType: 'Attachment',
570 | name: 'Extended Sniper Mag (Level 3 / Purple)',
571 | attachmentType: 'ExtendedSniperMag',
572 | level: 3
573 | }, {
574 | itemId: 223,
575 | itemType: 'Attachment',
576 | name: 'Extended Sniper Mag (Level 4 / Gold)',
577 | attachmentType: 'ExtendedSniperMag',
578 | level: 4
579 | }, {
580 | itemId: 224,
581 | itemType: 'Attachment',
582 | name: 'Shotgun Bolt (Level 1 / White)',
583 | attachmentType: 'ShotgunBolt',
584 | level: 1
585 | }, {
586 | itemId: 225,
587 | itemType: 'Attachment',
588 | name: 'Shotgun Bolt (Level 2 / Blue)',
589 | attachmentType: 'ShotgunBolt',
590 | level: 2
591 | }, {
592 | itemId: 226,
593 | itemType: 'Attachment',
594 | name: 'Shotgun Bolt (Level 3 / Purple)',
595 | attachmentType: 'ShotgunBolt',
596 | level: 3
597 | }, {
598 | itemId: 227,
599 | itemType: 'Attachment',
600 | name: 'Standard Stock (Level 1 / White)',
601 | attachmentType: 'StandardStock',
602 | level: 1
603 | }, {
604 | itemId: 228,
605 | itemType: 'Attachment',
606 | name: 'Standard Stock (Level 2 / Blue)',
607 | attachmentType: 'StandardStock',
608 | level: 2
609 | }, {
610 | itemId: 229,
611 | itemType: 'Attachment',
612 | name: 'Standard Stock (Level 3 / Purple)',
613 | attachmentType: 'StandardStock',
614 | level: 3
615 | }, {
616 | itemId: 230,
617 | itemType: 'Attachment',
618 | name: 'Sniper Stock (Level 1 / White)',
619 | attachmentType: 'SniperStock',
620 | level: 1
621 | }, {
622 | itemId: 231,
623 | itemType: 'Attachment',
624 | name: 'Sniper Stock (Level 2 / Blue)',
625 | attachmentType: 'SniperStock',
626 | level: 2
627 | }, {
628 | itemId: 232,
629 | itemType: 'Attachment',
630 | name: 'Sniper Stock (Level 3 / Purple)',
631 | attachmentType: 'SniperStock',
632 | level: 3
633 | }, {
634 | itemId: 233,
635 | itemType: 'Attachment',
636 | name: 'Turbocharger',
637 | attachmentType: 'Turbocharger',
638 | level: 4
639 | }, {
640 | itemId: 235,
641 | itemType: 'Attachment',
642 | name: 'Skullpiercer Rifling',
643 | attachmentType: 'SkullpiercerRifling',
644 | level: 4
645 | }, {
646 | itemId: 237,
647 | itemType: 'Attachment',
648 | name: 'Hammerpoint Rounds',
649 | attachmentType: 'HammerpointRounds',
650 | level: 3
651 | }, {
652 | itemId: 239,
653 | itemType: 'Attachment',
654 | name: 'Double Tap Trigger',
655 | attachmentType: 'DoubleTapTrigger',
656 | level: 3
657 | }, {
658 | itemId: 246,
659 | itemType: 'Attachment',
660 | name: 'Kinetic Feeder',
661 | attachmentType: 'KineticFeeder',
662 | level: 3
663 | }, {
664 | itemId: 247,
665 | itemType: 'Attachment',
666 | name: 'Boosted Loader',
667 | attachmentType: 'BoostedLoader',
668 | level: 3
669 | }];
670 |
--------------------------------------------------------------------------------
/src/lib/items/types/All.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export type All =
4 | app.Ammo |
5 | app.Attachment |
6 | app.Gear |
7 | app.Grenade |
8 | app.Regen |
9 | app.Weapon;
10 |
--------------------------------------------------------------------------------
/src/lib/items/types/Ammo.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export type Ammo = {
4 | itemId: number;
5 | itemType: 'Ammo';
6 | name: string;
7 | ammoType: app.AmmoType;
8 | };
9 |
--------------------------------------------------------------------------------
/src/lib/items/types/AmmoType.ts:
--------------------------------------------------------------------------------
1 | export type AmmoType =
2 | 'Energy' |
3 | 'Heavy' |
4 | 'Light' |
5 | 'Shotgun' |
6 | 'Sniper' |
7 | 'Special';
8 |
--------------------------------------------------------------------------------
/src/lib/items/types/Attachment.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export type Attachment = {
4 | itemId: number;
5 | itemType: 'Attachment';
6 | name: string;
7 | attachmentType: app.AttachmentType,
8 | weaponTypes?: Array;
9 | level: 1 | 2 | 3 | 4;
10 | };
11 |
--------------------------------------------------------------------------------
/src/lib/items/types/AttachmentType.ts:
--------------------------------------------------------------------------------
1 | export type AttachmentType =
2 | 'BarrelStabilizer' |
3 | 'ExtendedEnergyMag' |
4 | 'ExtendedHeavyMag' |
5 | 'ExtendedLightMag' |
6 | 'ExtendedSniperMag' |
7 | 'LaserLight' |
8 | 'Optics' |
9 | 'ShotgunBolt' |
10 | 'StandardStock' |
11 | 'SniperStock' |
12 | 'BoostedLoader' |
13 | 'DoubleTapTrigger' |
14 | 'HammerpointRounds' |
15 | 'KineticFeeder' |
16 | 'SkullpiercerRifling' |
17 | 'Turbocharger';
18 |
--------------------------------------------------------------------------------
/src/lib/items/types/Gear.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export type Gear = {
4 | itemId: number;
5 | itemType: 'Gear';
6 | name: string;
7 | gearType: app.GearType;
8 | level: 1 | 2 | 3 | 4;
9 | };
10 |
--------------------------------------------------------------------------------
/src/lib/items/types/GearType.ts:
--------------------------------------------------------------------------------
1 | export type GearType =
2 | 'Backpack' |
3 | 'BodyShield' |
4 | 'EvoShield' |
5 | 'Helmet' |
6 | 'KnockdownShield';
7 |
--------------------------------------------------------------------------------
/src/lib/items/types/Grenade.ts:
--------------------------------------------------------------------------------
1 | export type Grenade = {
2 | itemId: number;
3 | itemType: 'Grenade';
4 | name: string;
5 | };
6 |
--------------------------------------------------------------------------------
/src/lib/items/types/Regen.ts:
--------------------------------------------------------------------------------
1 | export type Regen = {
2 | itemId: number;
3 | itemType: 'Regen';
4 | name: string;
5 | };
6 |
--------------------------------------------------------------------------------
/src/lib/items/types/Weapon.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 |
3 | export type Weapon = {
4 | itemId: number;
5 | itemType: 'Weapon';
6 | name: string;
7 | ammoType: app.AmmoType;
8 | weaponType: app.WeaponType;
9 | slots: Array;
10 | };
11 |
--------------------------------------------------------------------------------
/src/lib/items/types/WeaponType.ts:
--------------------------------------------------------------------------------
1 | export type WeaponType =
2 | 'AR' |
3 | 'LMG' |
4 | 'Marksman' |
5 | 'Pistol' |
6 | 'SMG' |
7 | 'Sniper' |
8 | 'Shotgun';
9 |
--------------------------------------------------------------------------------
/src/ui/MainView.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ui from 'ui';
3 | import Close from '@mui/icons-material/Close';
4 | import Settings from '@mui/icons-material/Settings';
5 |
6 | export const MainView = ui.createView<{vm: ui.MainViewModel}>(({vm}) => (
7 |
8 | {vm.hasError &&
9 |
10 | {vm.errorMessage}
11 |
12 | vm.connectAsync()}>
13 | {ui.language.reconnect}
14 |
15 | }
16 | {vm.isLoading &&
17 |
18 | }
19 | {!vm.hasError && !vm.isLoading &&
20 | {vm.showSettings
21 | ?
22 | : }
23 | vm.toggleSettings()}>
24 | {vm.showSettings ? : }
25 |
26 | }
27 |
28 | ));
29 |
30 | const styles = {
31 | container: {
32 | height: '100vh',
33 | overflowX: 'hidden',
34 | overflowY: 'scroll',
35 | userSelect: 'none'
36 | },
37 | error: {
38 | textAlign: 'center',
39 | width: '100%',
40 | position: 'fixed',
41 | left: '50%',
42 | top: '50%',
43 | transform: 'translate(-50%, -50%)'
44 | },
45 | progress: {
46 | position: 'fixed',
47 | left: 'calc(50% - 20px)',
48 | top: 'calc(50% - 20px)'
49 | },
50 | toggle: {
51 | position: 'fixed',
52 | right: '12px',
53 | top: '12px'
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/src/ui/MainViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as app from 'lib';
2 | import * as mobx from 'mobx';
3 | import * as ui from 'ui';
4 |
5 | export class MainViewModel {
6 | private constructor(
7 | private readonly renderFrame: (core: app.core.Core, vm: ui.MainViewModel) => void) {
8 | mobx.makeObservable(this);
9 | }
10 |
11 | static create(renderFrame: (core: app.core.Core, vm: ui.MainViewModel) => void) {
12 | const vm = new MainViewModel(renderFrame);
13 | vm.connectAsync();
14 | return vm;
15 | }
16 |
17 | @mobx.action
18 | async connectAsync() {
19 | try {
20 | this.isLoading = true;
21 | const server = new app.api.Server();
22 | const version = await server.versionAsync();
23 | if (!version) {
24 | this.errorMessage = ui.language.errorDriver;
25 | } else if (version !== app.api.VERSION) {
26 | this.errorMessage = ui.language.errorVersion;
27 | } else if (!await this.startAsync(server)) {
28 | this.errorMessage = ui.language.errorProcess;
29 | } else {
30 | this.errorMessage = '';
31 | }
32 | } catch (err) {
33 | this.disconnect(err);
34 | } finally {
35 | this.isLoading = false;
36 | }
37 | }
38 |
39 | @mobx.action
40 | toggleSettings() {
41 | this.showSettings = !this.showSettings;
42 | }
43 |
44 | @mobx.computed
45 | get hasError() {
46 | return Boolean(this.errorMessage);
47 | }
48 |
49 | @mobx.observable
50 | isLoading = true;
51 |
52 | @mobx.observable
53 | errorMessage = '';
54 |
55 | @mobx.observable
56 | showSettings = false;
57 |
58 | @mobx.observable
59 | readonly settings = new ui.settings.MainViewModel();
60 |
61 | @mobx.action
62 | private disconnect(reason: unknown) {
63 | this.errorMessage = ui.language.error;
64 | console.error(reason);
65 | }
66 |
67 | @mobx.action
68 | private async startAsync(server: app.api.Server) {
69 | const core = await app.core.Core
70 | .createAsync(server)
71 | .catch(() => {});
72 | if (core) {
73 | core.runAsync(() => this.renderFrame(core, this)).catch(this.disconnect.bind(this));
74 | return true;
75 | } else {
76 | return false;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/ui/classes/BoolStorage.ts:
--------------------------------------------------------------------------------
1 | import * as mobx from 'mobx';
2 |
3 | export class BoolStorage {
4 | constructor(private readonly key: string, defaultValue: boolean) {
5 | const value = localStorage.getItem(key);
6 | this.value = value ? /^true$/i.test(value) : defaultValue;
7 | mobx.makeObservable(this);
8 | }
9 |
10 | @mobx.action
11 | change(value: boolean) {
12 | this.value = value;
13 | localStorage.setItem(this.key, String(this.value));
14 | }
15 |
16 | @mobx.observable
17 | value: boolean;
18 | }
19 |
--------------------------------------------------------------------------------
/src/ui/classes/NumberStorage.ts:
--------------------------------------------------------------------------------
1 | import * as mobx from 'mobx';
2 |
3 | export class NumberStorage {
4 | constructor(private readonly key: string, defaultValue: number) {
5 | const value = localStorage.getItem(key);
6 | this.value = value ? Number(value) : defaultValue;
7 | mobx.makeObservable(this);
8 | }
9 |
10 | @mobx.action
11 | change(value: number) {
12 | this.value = value;
13 | localStorage.setItem(this.key, String(this.value));
14 | }
15 |
16 | @mobx.observable
17 | value: number;
18 | }
19 |
--------------------------------------------------------------------------------
/src/ui/classes/StringStorage.ts:
--------------------------------------------------------------------------------
1 | import * as mobx from 'mobx';
2 |
3 | export class StringStorage {
4 | constructor(private readonly key: string, defaultValue: T) {
5 | const value = localStorage.getItem(key);
6 | this.value = (value ? value : defaultValue) as T;
7 | mobx.makeObservable(this);
8 | }
9 |
10 | @mobx.action
11 | change(value: T) {
12 | this.value = value;
13 | localStorage.setItem(this.key, this.value);
14 | }
15 |
16 | @mobx.observable
17 | value: T;
18 | }
19 |
--------------------------------------------------------------------------------
/src/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utilities';
2 | export * as material from '@mui/material';
3 | export * as main from './main';
4 | export * as settings from './settings';
5 | export * from './classes/BoolStorage';
6 | export * from './classes/NumberStorage';
7 | export * from './classes/StringStorage';
8 | export * from './language';
9 | export * from './MainViewModel';
10 | export * from './MainView';
11 |
--------------------------------------------------------------------------------
/src/ui/language.ts:
--------------------------------------------------------------------------------
1 | export const language = {
2 | error: 'An error occurred. Check your console.',
3 | errorDriver: 'Load this connector in your http-driver.',
4 | errorProcess: 'Game not found. Did you run it?',
5 | errorVersion: 'Your http-driver is outdated.',
6 | reconnect: 'Reconnect'
7 | };
8 |
--------------------------------------------------------------------------------
/src/ui/main/MainView.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ui from 'ui';
3 |
4 | export const MainView = ui.createView(() => {
5 | React.useEffect(() => {
6 | const canvas = document.getElementById('canvas') as HTMLCanvasElement;
7 | canvas.style.display = 'inline';
8 | return () => onUnmount(canvas);
9 | }, []);
10 | return null;
11 | });
12 |
13 | function onUnmount(canvas: HTMLElement) {
14 | canvas.style.display = 'none';
15 | }
16 |
--------------------------------------------------------------------------------
/src/ui/main/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MainView';
2 |
--------------------------------------------------------------------------------
/src/ui/settings/MainView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 | import FilterList from '@mui/icons-material/FilterList';
5 | import Science from '@mui/icons-material/Science';
6 | import Settings from '@mui/icons-material/Settings';
7 |
8 | export const MainView = ui.createView<{vm: app.MainViewModel}>(({vm}) => (
9 |
10 |
11 |
12 |
13 |
14 | vm.changeView(0)}
16 | icon={}
17 | label={app.language.general} />
18 | vm.changeView(1)}
20 | icon={}
21 | label={app.language.items} />
22 | vm.changeView(2)}
24 | icon={}
25 | label={app.language.research} />
26 |
27 |
28 |
29 | ));
30 |
31 | const SwitchView = ui.createView<{vm: app.MainViewModel}>(({vm}) => {
32 | switch (vm.currentView) {
33 | case 1: return ;
34 | case 2: return ;
35 | default: return ;
36 | }
37 | });
38 |
39 | const styles = {
40 | container: {
41 | margin: '0 auto',
42 | maxWidth: '384px',
43 | padding: '16px',
44 | paddingBottom: '56px'
45 | },
46 | navigation: {
47 | position: 'fixed',
48 | bottom: 0,
49 | left: 0,
50 | right: 0
51 | },
52 | navigationAction: {
53 | maxWidth: '128px'
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/src/ui/settings/MainViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 | import * as mobx from 'mobx';
3 |
4 | export class MainViewModel {
5 | readonly general = new app.general.MainViewModel();
6 | readonly items = new app.items.MainViewModel();
7 | readonly research = new app.research.MainViewModel();
8 |
9 | constructor() {
10 | mobx.makeObservable(this);
11 | mobx.autorun(this.updateItemSet.bind(this));
12 | }
13 |
14 | @mobx.action
15 | changeView(view: number) {
16 | this.currentView = view;
17 | }
18 |
19 | @mobx.observable
20 | currentView = 0;
21 |
22 | @mobx.observable
23 | itemSet = new Set();
24 |
25 | private updateItemSet() {
26 | const value = new Set();
27 | this.items.toItemSet(value);
28 | this.itemSet = value;
29 | value.delete(0);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ui/settings/general/MainView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const MainView = ui.createView<{vm: app.MainViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.general}
9 |
10 |
11 | {app.language.generalViewType}
12 |
13 | vm.viewType.change(x.target.value as app.MainType)}
15 | value={vm.viewType.value}>
16 |
17 | {app.language.generalViewTypeMap}
18 |
19 |
20 | {app.language.generalViewTypeRadar}
21 |
22 |
23 |
24 |
25 |
26 |
27 | ));
28 |
--------------------------------------------------------------------------------
/src/ui/settings/general/MainViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 | import * as ui from 'ui';
3 |
4 | export class MainViewModel {
5 | readonly map = new app.MapViewModel();
6 | readonly radar = new app.RadarViewModel();
7 | readonly sense = new app.SenseViewModel();
8 | readonly viewType = new ui.StringStorage('settings.viewType', app.MainType.Radar);
9 | }
10 |
--------------------------------------------------------------------------------
/src/ui/settings/general/enums/MainType.ts:
--------------------------------------------------------------------------------
1 | export enum MainType {
2 | Map = 'map',
3 | Radar = 'radar'
4 | }
5 |
--------------------------------------------------------------------------------
/src/ui/settings/general/index.ts:
--------------------------------------------------------------------------------
1 | export * from './language';
2 | export * from './enums/MainType';
3 | export * from './MainView';
4 | export * from './MainViewModel';
5 | export * from './viewModels/MapViewModel';
6 | export * from './viewModels/RadarViewModel';
7 | export * from './viewModels/SenseViewModel';
8 | export * from './views/MapView';
9 | export * from './views/RadarView';
10 | export * from './views/SenseView';
11 |
--------------------------------------------------------------------------------
/src/ui/settings/general/language.ts:
--------------------------------------------------------------------------------
1 | export const language = {
2 | general: 'General Settings',
3 | generalMap: 'Map',
4 | generalMapItems: 'Show Items',
5 | generalMapPlayers: 'Show Players',
6 | generalRadar: 'Radar',
7 | generalRadarItems: 'Show Items',
8 | generalRadarPlayers: 'Show Players',
9 | generalSense: 'Sense',
10 | generalSenseItems: 'Highlight Items',
11 | generalSensePlayers: 'Highlight Players',
12 | generalViewType: 'Main',
13 | generalViewTypeMap: 'Show Map',
14 | generalViewTypeRadar: 'Show Radar'
15 | };
16 |
--------------------------------------------------------------------------------
/src/ui/settings/general/viewModels/MapViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as ui from 'ui';
2 |
3 | export class MapViewModel {
4 | readonly showItems = new ui.BoolStorage('settings.map.showItems', false);
5 | readonly showPlayers = new ui.BoolStorage('settings.map.showPlayers', true);
6 | }
7 |
--------------------------------------------------------------------------------
/src/ui/settings/general/viewModels/RadarViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as ui from 'ui';
2 |
3 | export class RadarViewModel {
4 | readonly showItems = new ui.BoolStorage('settings.radar.showItems', false);
5 | readonly showPlayers = new ui.BoolStorage('settings.radar.showPlayers', true);
6 | }
7 |
--------------------------------------------------------------------------------
/src/ui/settings/general/viewModels/SenseViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as ui from 'ui';
2 |
3 | export class SenseViewModel {
4 | readonly highlightItems = new ui.BoolStorage('settings.sense.highlightItems', false);
5 | readonly highlightPlayers = new ui.BoolStorage('settings.sense.highlightPlayers', false);
6 | }
7 |
--------------------------------------------------------------------------------
/src/ui/settings/general/views/MapView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const MapView = ui.createView<{vm: app.MapViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.generalMap}
9 |
10 | vm.showItems.change(x.target.checked)}
14 | checked={vm.showItems.value} />} />
15 | vm.showPlayers.change(x.target.checked)}
19 | checked={vm.showPlayers.value} />} />
20 |
21 | ));
22 |
--------------------------------------------------------------------------------
/src/ui/settings/general/views/RadarView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const RadarView = ui.createView<{vm: app.RadarViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.generalRadar}
9 |
10 | vm.showItems.change(x.target.checked)}
14 | checked={vm.showItems.value} />} />
15 | vm.showPlayers.change(x.target.checked)}
19 | checked={vm.showPlayers.value} />} />
20 |
21 | ));
22 |
--------------------------------------------------------------------------------
/src/ui/settings/general/views/SenseView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const SenseView = ui.createView<{vm: app.SenseViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.generalSense}
9 |
10 | vm.highlightItems.change(x.target.checked)}
14 | checked={vm.highlightItems.value} />} />
15 | vm.highlightPlayers.change(x.target.checked)}
19 | checked={vm.highlightPlayers.value} />} />
20 |
21 | ));
22 |
--------------------------------------------------------------------------------
/src/ui/settings/index.ts:
--------------------------------------------------------------------------------
1 | export * as general from './general';
2 | export * as items from './items';
3 | export * as research from './research';
4 | export * from './language';
5 | export * from './MainView';
6 | export * from './MainViewModel';
7 |
--------------------------------------------------------------------------------
/src/ui/settings/items/MainView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const MainView = ui.createView<{vm: app.MainViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.items}
9 |
10 | {vm.areas.map((x, i) => (
11 |
12 | ))}
13 |
14 | ));
15 |
--------------------------------------------------------------------------------
/src/ui/settings/items/MainViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class MainViewModel {
4 | readonly areas = [
5 | new app.AreaViewModel(app.language.itemsAmmo, x => x.itemType === 'Ammo'),
6 | new app.AreaViewModel(app.language.itemsGearBackpack, x => x.itemType === 'Gear' && x.gearType === 'Backpack'),
7 | new app.AreaViewModel(app.language.itemsGearBodyShield, x => x.itemType === 'Gear' && x.gearType === 'BodyShield'),
8 | new app.AreaViewModel(app.language.itemsGearEvoShield, x => x.itemType === 'Gear' && x.gearType === 'EvoShield'),
9 | new app.AreaViewModel(app.language.itemsGearHelmet, x => x.itemType === 'Gear' && x.gearType === 'Helmet'),
10 | new app.AreaViewModel(app.language.itemsGearKnockdownShield, x => x.itemType === 'Gear' && x.gearType === 'KnockdownShield'),
11 | new app.AreaViewModel(app.language.itemsGrenade, x => x.itemType === 'Grenade'),
12 | new app.AreaViewModel(app.language.itemsRegen, x => x.itemType === 'Regen'),
13 | new app.AreaViewModel(app.language.itemsWeaponsEnergy, x => x.itemType === 'Weapon' && x.ammoType === 'Energy'),
14 | new app.AreaViewModel(app.language.itemsWeaponsHeavy, x => x.itemType === 'Weapon' && x.ammoType === 'Heavy'),
15 | new app.AreaViewModel(app.language.itemsWeaponsLight, x => x.itemType === 'Weapon' && x.ammoType === 'Light'),
16 | new app.AreaViewModel(app.language.itemsWeaponShotgun, x => x.itemType === 'Weapon' && x.ammoType === 'Shotgun'),
17 | new app.AreaViewModel(app.language.itemsWeaponsSniper, x => x.itemType === 'Weapon' && x.ammoType === 'Sniper'),
18 | new app.AreaViewModel(app.language.itemsWeaponsSpecial, x => x.itemType === 'Weapon' && x.ammoType === 'Special'),
19 | new app.AreaViewModel(app.language.itemsWeaponAttachments, x => x.itemType === 'Attachment')
20 | ];
21 |
22 | toItemSet(value: Set) {
23 | for (const area of this.areas) {
24 | area.toItemSet(value);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/ui/settings/items/index.ts:
--------------------------------------------------------------------------------
1 | export * from './language';
2 | export * from './viewModels/AreaViewModel';
3 | export * from './viewModels/ItemViewModel';
4 | export * from './views/AreaView';
5 | export * from './views/ItemView';
6 | export * from './MainView';
7 | export * from './MainViewModel';
8 |
--------------------------------------------------------------------------------
/src/ui/settings/items/language.ts:
--------------------------------------------------------------------------------
1 | export const language = {
2 | items: 'Item Settings',
3 | itemsAmmo: 'Ammo',
4 | itemsGearBackpack: 'Gear / Backpack',
5 | itemsGearBodyShield: 'Gear / Body Shield',
6 | itemsGearEvoShield: 'Gear / Evo Shield',
7 | itemsGearHelmet: 'Gear / Helmet',
8 | itemsGearKnockdownShield: 'Gear / Knockdown Shield',
9 | itemsGrenade: 'Grenades',
10 | itemsRegen: 'Regen',
11 | itemsWeaponsEnergy: 'Weapons / Energy',
12 | itemsWeaponsHeavy: 'Weapons / Heavy',
13 | itemsWeaponsLight: 'Weapons / Light',
14 | itemsWeaponShotgun: 'Weapons / Shotguns',
15 | itemsWeaponsSniper: 'Weapons / Snipers',
16 | itemsWeaponsSpecial: 'Weapons / Special',
17 | itemsWeaponAttachments: 'Weapon Attachments'
18 | };
19 |
--------------------------------------------------------------------------------
/src/ui/settings/items/viewModels/AreaViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as lib from 'lib';
3 |
4 | export class AreaViewModel {
5 | readonly items: Array;
6 | readonly name: string;
7 |
8 | constructor(name: string, selector: (item: lib.items.All) => boolean) {
9 | this.items = Array.from(fetch(selector)).map(x => new app.ItemViewModel(x));
10 | this.name = name;
11 | }
12 |
13 | toItemSet(value: Set) {
14 | for (const entry of this.items) {
15 | if (!entry.store.value) continue;
16 | value.add(entry.value.itemId);
17 | }
18 | }
19 | }
20 |
21 | function *fetch(selector: (item: lib.items.All) => boolean) {
22 | for (const item of lib.items.list) {
23 | if (!selector(item)) continue;
24 | yield item;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/ui/settings/items/viewModels/ItemViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as lib from 'lib';
2 | import * as ui from 'ui';
3 |
4 | export class ItemViewModel {
5 | constructor(
6 | readonly value: lib.items.All,
7 | readonly store = new ui.BoolStorage(`Items[${value.name}]`, false)) {}
8 | }
9 |
--------------------------------------------------------------------------------
/src/ui/settings/items/views/AreaView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const AreaView = ui.createView<{vm: app.AreaViewModel}>(({vm}) => (
6 |
7 |
8 | {vm.name}
9 |
10 | {vm.items.map(x => (
11 |
12 | ))}
13 |
14 | ));
15 |
--------------------------------------------------------------------------------
/src/ui/settings/items/views/ItemView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const ItemView = ui.createView<{vm: app.ItemViewModel}>(({vm}) => (
6 |
7 | vm.store.change(y.target.checked)}
11 | checked={vm.store.value} />} />
12 |
13 | ));
14 |
15 | const styles = {
16 | label: {
17 | overflow: 'hidden',
18 | whiteSpace: 'nowrap',
19 | textOverflow: 'ellipsis'
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/src/ui/settings/language.ts:
--------------------------------------------------------------------------------
1 | export const language = {
2 | general: 'General',
3 | items: 'Items',
4 | research: 'Research'
5 | };
6 |
--------------------------------------------------------------------------------
/src/ui/settings/research/MainView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const MainView = ui.createView<{vm: app.MainViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.research}
9 |
10 |
11 | {app.language.researchWarning}
12 |
13 |
14 |
15 | ));
16 |
--------------------------------------------------------------------------------
/src/ui/settings/research/MainViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as app from '.';
2 |
3 | export class MainViewModel {
4 | readonly recoil = new app.RecoilViewModel();
5 | }
6 |
--------------------------------------------------------------------------------
/src/ui/settings/research/index.ts:
--------------------------------------------------------------------------------
1 | export * from './language';
2 | export * from './viewModels/RecoilViewModel';
3 | export * from './views/RecoilView';
4 | export * from './MainView';
5 | export * from './MainViewModel';
6 |
--------------------------------------------------------------------------------
/src/ui/settings/research/language.ts:
--------------------------------------------------------------------------------
1 | export const language = {
2 | research: 'Research Settings',
3 | researchRecoil: 'Recoil Compensation',
4 | researchRecoilEnable: 'Enable',
5 | researchRecoilPercentage: 'Recoil Compensation (%)',
6 | researchWarning: 'These features may be unstable and cause detection. Proceed with caution.'
7 | };
8 |
--------------------------------------------------------------------------------
/src/ui/settings/research/viewModels/RecoilViewModel.ts:
--------------------------------------------------------------------------------
1 | import * as lib from 'lib';
2 | import * as mobx from 'mobx';
3 | import * as ui from 'ui';
4 |
5 | export class RecoilViewModel {
6 | readonly enable = new ui.BoolStorage('settings.recoil.enable', false);
7 | readonly percentage = new ui.NumberStorage('settings.recoil.percentage', 0.75);
8 |
9 | constructor() {
10 | mobx.makeObservable(this);
11 | mobx.autorun(this.updateOptions.bind(this));
12 | }
13 |
14 | @mobx.observable
15 | options?: lib.features.IRecoilOptions;
16 |
17 | private updateOptions() {
18 | this.options = {
19 | percentage: this.percentage.value
20 | };
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/ui/settings/research/views/RecoilView.tsx:
--------------------------------------------------------------------------------
1 | import * as app from '..';
2 | import * as React from 'react';
3 | import * as ui from 'ui';
4 |
5 | export const RecoilView = ui.createView<{vm: app.RecoilViewModel}>(({vm}) => (
6 |
7 |
8 | {app.language.researchRecoil}
9 |
10 | vm.enable.change(x.target.checked)}
14 | checked={vm.enable.value} />} />
15 |
16 |
17 | {app.language.researchRecoilPercentage}
18 |
19 |
20 | {vm.percentage.value}
21 |
22 |
23 | vm.percentage.change(Number(x))}
25 | value={vm.percentage.value}
26 | min={0} max={1} step={0.01} />
27 |
28 | ));
29 |
30 | const styles = {
31 | sliderLabel: {
32 | position: 'relative'
33 | },
34 | sliderValue: {
35 | position: 'absolute',
36 | right: 0,
37 | top: 0
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/ui/utilities.ts:
--------------------------------------------------------------------------------
1 | import * as mobxReact from 'mobx-react';
2 |
3 | export function createView(fn: (props: T) => any) {
4 | return mobxReact.observer((props: T) => fn(props) || null);
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "alwaysStrict": true,
5 | "baseUrl": "src",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "inlineSourceMap": true,
10 | "inlineSources": true,
11 | "jsx": "react",
12 | "lib": ["DOM", "ES2021"],
13 | "module": "CommonJS",
14 | "noImplicitAny": true,
15 | "noImplicitThis": true,
16 | "noImplicitReturns": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "outDir": "dist",
21 | "skipLibCheck": true,
22 | "strict": true,
23 | "strictBindCallApply": true,
24 | "strictFunctionTypes": true,
25 | "strictNullChecks": true,
26 | "strictPropertyInitialization": true,
27 | "target": "ES2021"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/webpack.common.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: {
3 | app: './dist/app'
4 | },
5 | output: {
6 | filename: '[name].min.js',
7 | path: `${__dirname}/docs`
8 | },
9 | resolve: {
10 | alias: {
11 | 'lib': `${__dirname}/dist/lib`,
12 | 'ui': `${__dirname}/dist/ui`
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/webpack.development.js:
--------------------------------------------------------------------------------
1 | const common = require('./webpack.common');
2 |
3 | module.exports = Object.assign(common, {
4 | mode: 'development',
5 | devtool: 'eval-source-map',
6 | devServer: {
7 | client: false,
8 | port: 3000,
9 | static: {directory: common.output.path}
10 | },
11 | module: {
12 | rules: [{
13 | test: /\.js$/,
14 | enforce: 'pre',
15 | use: ['source-map-loader']
16 | }]
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/webpack.production.js:
--------------------------------------------------------------------------------
1 | const common = require('./webpack.common');
2 | const TerserPlugin = require('terser-webpack-plugin');
3 |
4 | module.exports = Object.assign(common, {
5 | mode: 'production',
6 | performance: {hints: false},
7 | optimization: {
8 | minimize: true,
9 | minimizer: [new TerserPlugin({
10 | extractComments: false,
11 | terserOptions: {format: {comments: false}}
12 | })]
13 | }
14 | });
15 |
--------------------------------------------------------------------------------