mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-27 00:17:32 +01:00
merge dev
This commit is contained in:
commit
d24a26a208
3
.github/PULL_REQUEST_TEMPLATE
vendored
3
.github/PULL_REQUEST_TEMPLATE
vendored
@ -9,3 +9,6 @@
|
|||||||
|
|
||||||
- Include how it was tested
|
- Include how it was tested
|
||||||
- Include screenshot / gif (if possible)
|
- Include screenshot / gif (if possible)
|
||||||
|
|
||||||
|
|
||||||
|
Make sure you run `npm run format` and `npm run lint` before pushing.
|
||||||
|
22
.github/workflows/bump-version.yml
vendored
22
.github/workflows/bump-version.yml
vendored
@ -4,28 +4,28 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: 'Version (format: x.y.z)'
|
description: "Version (format: x.y.z)"
|
||||||
required: true
|
required: true
|
||||||
versionNumber:
|
versionNumber:
|
||||||
description: 'Version Number (for saves migration)'
|
description: "Version Number (for saves migration)"
|
||||||
required: true
|
required: true
|
||||||
changelog:
|
changelog:
|
||||||
description: 'Changelog (url that points to RAW markdown)'
|
description: "Changelog (url that points to RAW markdown)"
|
||||||
default: ''
|
default: ""
|
||||||
buildApp:
|
buildApp:
|
||||||
description: 'Include Application Build'
|
description: "Include Application Build"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: 'true'
|
default: "true"
|
||||||
required: true
|
required: true
|
||||||
buildDoc:
|
buildDoc:
|
||||||
description: 'Include Documentation Build'
|
description: "Include Documentation Build"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: 'true'
|
default: "true"
|
||||||
required: true
|
required: true
|
||||||
prepareRelease:
|
prepareRelease:
|
||||||
description: 'Prepare Draft Release'
|
description: "Prepare Draft Release"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: 'true'
|
default: "true"
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -46,7 +46,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 16.13.1
|
node-version: 16.13.1
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
- name: Install NPM dependencies for version updater
|
- name: Install NPM dependencies for version updater
|
||||||
working-directory: ./tools/bump-version
|
working-directory: ./tools/bump-version
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Get Comment Body
|
- name: Get Comment Body
|
||||||
id: get-comment-body
|
id: get-comment-body
|
||||||
if : steps.get-warnings.outputs.has_warnings == 'true'
|
if: steps.get-warnings.outputs.has_warnings == 'true'
|
||||||
run: |
|
run: |
|
||||||
cat warnings.txt > comment.txt
|
cat warnings.txt > comment.txt
|
||||||
echo "" >> comment.txt
|
echo "" >> comment.txt
|
||||||
@ -73,12 +73,12 @@ jobs:
|
|||||||
|
|
||||||
echo ::set-output name=body::$body
|
echo ::set-output name=body::$body
|
||||||
- name: Add github comment on problem
|
- name: Add github comment on problem
|
||||||
if : steps.get-warnings.outputs.has_warnings == 'true'
|
if: steps.get-warnings.outputs.has_warnings == 'true'
|
||||||
uses: peter-evans/commit-comment@v1
|
uses: peter-evans/commit-comment@v1
|
||||||
with:
|
with:
|
||||||
body: ${{ steps.get-comment-body.outputs.body }}
|
body: ${{ steps.get-comment-body.outputs.body }}
|
||||||
- name: Flag as error
|
- name: Flag as error
|
||||||
if : steps.get-warnings.outputs.has_warnings == 'true'
|
if: steps.get-warnings.outputs.has_warnings == 'true'
|
||||||
run: |
|
run: |
|
||||||
COMMIT_WARNINGS=$(cat warnings.txt)
|
COMMIT_WARNINGS=$(cat warnings.txt)
|
||||||
echo "::warning:: $COMMIT_WARNINGS"
|
echo "::warning:: $COMMIT_WARNINGS"
|
||||||
|
6
.github/workflows/fetch-changes.yml
vendored
6
.github/workflows/fetch-changes.yml
vendored
@ -3,10 +3,10 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
fromCommit:
|
fromCommit:
|
||||||
description: 'From Commit SHA (full-length)'
|
description: "From Commit SHA (full-length)"
|
||||||
required: true
|
required: true
|
||||||
toCommit:
|
toCommit:
|
||||||
description: 'To Commit SHA (full-length, if omitted will use latest)'
|
description: "To Commit SHA (full-length, if omitted will use latest)"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fetchChangelog:
|
fetchChangelog:
|
||||||
@ -17,7 +17,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 16.13.1
|
node-version: 16.13.1
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
- name: Install NPM dependencies
|
- name: Install NPM dependencies
|
||||||
working-directory: ./tools/fetch-changelog
|
working-directory: ./tools/fetch-changelog
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
42
dist/vendor.bundle.js
vendored
42
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/vendor.bundle.js.map
vendored
2
dist/vendor.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
@ -25,8 +25,8 @@ I need equations that test many different aspect of "math culture", it can be ch
|
|||||||
|
|
||||||
All variable purchasing will be scriptable.
|
All variable purchasing will be scriptable.
|
||||||
|
|
||||||
|
|
||||||
All equation must have:
|
All equation must have:
|
||||||
|
|
||||||
- several variables that can be upgraded, at least 1 variable must be strategic in it's upgrading (upgrading too much can cause drop in performance)
|
- several variables that can be upgraded, at least 1 variable must be strategic in it's upgrading (upgrading too much can cause drop in performance)
|
||||||
- Some sort of math twist that requires some thinking, like (-2)^c1 alters between positive and negative.
|
- Some sort of math twist that requires some thinking, like (-2)^c1 alters between positive and negative.
|
||||||
|
|
||||||
|
@ -115,7 +115,6 @@ Fork and clone the repo
|
|||||||
- Regularly rebase your branch against `dev` to make sure you have the latest updates pulled.
|
- Regularly rebase your branch against `dev` to make sure you have the latest updates pulled.
|
||||||
- When merging, always merge your branch into `dev`. When releasing a new update, then merge `dev` into `master`
|
- When merging, always merge your branch into `dev`. When releasing a new update, then merge `dev` into `master`
|
||||||
|
|
||||||
|
|
||||||
## Running locally.
|
## Running locally.
|
||||||
|
|
||||||
Install
|
Install
|
||||||
@ -131,7 +130,6 @@ Inside the root of the repo run
|
|||||||
After that you can open any browser and navigate to `localhost:8000` and play the game.
|
After that you can open any browser and navigate to `localhost:8000` and play the game.
|
||||||
Saving a file will reload the game automatically.
|
Saving a file will reload the game automatically.
|
||||||
|
|
||||||
|
|
||||||
### How to build the electron app
|
### How to build the electron app
|
||||||
|
|
||||||
Tested on Node v16.13.1 (LTS) on Windows
|
Tested on Node v16.13.1 (LTS) on Windows
|
||||||
|
23
doc/FAQ.md
23
doc/FAQ.md
@ -23,10 +23,11 @@ Yes, just export the save file from the options menu & import it in the other pl
|
|||||||
## Game is stuck after running scripts!
|
## Game is stuck after running scripts!
|
||||||
|
|
||||||
You may have created an infinite loop with no sleep. You'll have to restart the game by killing all scripts.
|
You may have created an infinite loop with no sleep. You'll have to restart the game by killing all scripts.
|
||||||
* On Browser: Stick `?noScript` at the end of the URL
|
|
||||||
* On Steam:
|
- On Browser: Stick `?noScript` at the end of the URL
|
||||||
* In the menu, "Reloads" -> "Reload & Kill All Scripts".
|
- On Steam:
|
||||||
* If this does not work, when launching the game, use the kill all script options.
|
- In the menu, "Reloads" -> "Reload & Kill All Scripts".
|
||||||
|
- If this does not work, when launching the game, use the kill all script options.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -51,11 +52,13 @@ You can navigate to the game files by right-clicking the game in your library an
|
|||||||
## Steam: Game won't launch
|
## Steam: Game won't launch
|
||||||
|
|
||||||
### **On Windows**
|
### **On Windows**
|
||||||
|
|
||||||
If the game is installed on a network drive, it will fail to start due to a [limitation in Chromium](https://github.com/electron/electron/issues/27356).
|
If the game is installed on a network drive, it will fail to start due to a [limitation in Chromium](https://github.com/electron/electron/issues/27356).
|
||||||
|
|
||||||
If you cannot move the game to another drive, you'll have to add the `--no-sandbox` launch option. In your Steam Library, Right click the game and hit "Properties". You'll see the launch option section in the "General" window.
|
If you cannot move the game to another drive, you'll have to add the `--no-sandbox` launch option. In your Steam Library, Right click the game and hit "Properties". You'll see the launch option section in the "General" window.
|
||||||
|
|
||||||
### **On Linux**
|
### **On Linux**
|
||||||
|
|
||||||
The game is built natively, do not use Proton unless native does not work.
|
The game is built natively, do not use Proton unless native does not work.
|
||||||
|
|
||||||
When launching the game, you will be prompted with three options. If the standard launch does not work, you may attempt the `--disable-seccomp-filter-sandbox` or `--no-sandbox` launch option. If this still does not work, the game should be able to start by launching it directly or through the terminal. See [How do I get to the game files?](#game-files).
|
When launching the game, you will be prompted with three options. If the standard launch does not work, you may attempt the `--disable-seccomp-filter-sandbox` or `--no-sandbox` launch option. If this still does not work, the game should be able to start by launching it directly or through the terminal. See [How do I get to the game files?](#game-files).
|
||||||
@ -68,17 +71,17 @@ When launching the game, you will be prompted with three options. If the standar
|
|||||||
|
|
||||||
You may want access the logs to get information about crashes or such.
|
You may want access the logs to get information about crashes or such.
|
||||||
|
|
||||||
* on Linux: `~/.config/bitburner/logs/main.log`
|
- on Linux: `~/.config/bitburner/logs/main.log`
|
||||||
* on macOS: `~/Library/Logs/bitburner/main.log`
|
- on macOS: `~/Library/Logs/bitburner/main.log`
|
||||||
* on Windows: `%USERPROFILE%\AppData\Roaming\bitburner\logs\main.log`
|
- on Windows: `%USERPROFILE%\AppData\Roaming\bitburner\logs\main.log`
|
||||||
|
|
||||||
### Config (using [electron-store](https://github.com/sindresorhus/electron-store#readme))
|
### Config (using [electron-store](https://github.com/sindresorhus/electron-store#readme))
|
||||||
|
|
||||||
Configuration file will be written to disk in the application data directory.
|
Configuration file will be written to disk in the application data directory.
|
||||||
|
|
||||||
* on Linux: `~/.config/bitburner/config.json`
|
- on Linux: `~/.config/bitburner/config.json`
|
||||||
* on macOS: `~/Library/Application\ Support/bitburner/config.json`
|
- on macOS: `~/Library/Application\ Support/bitburner/config.json`
|
||||||
* on Windows: `%USERPROFILE%\AppData\Roaming\bitburner\config.json`
|
- on Windows: `%USERPROFILE%\AppData\Roaming\bitburner\config.json`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -3,6 +3,21 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
v1.6.3 - 2022-04-01 Few stanek fixes
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Stanek Gift
|
||||||
|
|
||||||
|
* Has a minimum size of 2x3
|
||||||
|
* Active Fragment property 'avgCharge' renamed to 'highestCharge'
|
||||||
|
* Formula for fragment effect updated to make 561% more sense.
|
||||||
|
Now you can charge to your heart content.
|
||||||
|
* Logs for the 'chargeFragment' function updated.
|
||||||
|
|
||||||
|
Misc.
|
||||||
|
|
||||||
|
* Nerf noodle bar.
|
||||||
|
|
||||||
v1.6.0 - 2022-03-29 Grafting
|
v1.6.0 - 2022-03-29 Grafting
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
|
|||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.6'
|
version = '1.6'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '1.6.0'
|
release = '1.6.3'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -6,18 +6,18 @@ async function enableAchievementsInterval(window) {
|
|||||||
// If the Steam API could not be initialized on game start, we'll abort this.
|
// If the Steam API could not be initialized on game start, we'll abort this.
|
||||||
if (global.greenworksError) return;
|
if (global.greenworksError) return;
|
||||||
|
|
||||||
// This is backward but the game fills in an array called `document.achievements` and we retrieve it from
|
// This is backward but the game fills in an array called `document.achievements` and we retrieve it from
|
||||||
// here. Hey if it works it works.
|
// here. Hey if it works it works.
|
||||||
const steamAchievements = greenworks.getAchievementNames();
|
const steamAchievements = greenworks.getAchievementNames();
|
||||||
log.silly(`All Steam achievements ${JSON.stringify(steamAchievements)}`);
|
log.silly(`All Steam achievements ${JSON.stringify(steamAchievements)}`);
|
||||||
const playerAchieved = (await Promise.all(steamAchievements.map(checkSteamAchievement))).filter(name => !!name);
|
const playerAchieved = (await Promise.all(steamAchievements.map(checkSteamAchievement))).filter((name) => !!name);
|
||||||
log.debug(`Player has Steam achievements ${JSON.stringify(playerAchieved)}`);
|
log.debug(`Player has Steam achievements ${JSON.stringify(playerAchieved)}`);
|
||||||
const intervalID = setInterval(async () => {
|
const intervalID = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const playerAchievements = await window.webContents.executeJavaScript("document.achievements");
|
const playerAchievements = await window.webContents.executeJavaScript("document.achievements");
|
||||||
for (const ach of playerAchievements) {
|
for (const ach of playerAchievements) {
|
||||||
if (!steamAchievements.includes(ach)) continue; // Don't try activating achievements that don't exist Steam-side
|
if (!steamAchievements.includes(ach)) continue; // Don't try activating achievements that don't exist Steam-side
|
||||||
if (playerAchieved.includes(ach)) continue; // Don't spam achievements that have already been recorded
|
if (playerAchieved.includes(ach)) continue; // Don't spam achievements that have already been recorded
|
||||||
log.info(`Granting Steam achievement ${ach}`);
|
log.info(`Granting Steam achievement ${ach}`);
|
||||||
greenworks.activateAchievement(ach, () => undefined);
|
greenworks.activateAchievement(ach, () => undefined);
|
||||||
playerAchieved.push(ach);
|
playerAchieved.push(ach);
|
||||||
@ -26,7 +26,7 @@ async function enableAchievementsInterval(window) {
|
|||||||
log.error(error);
|
log.error(error);
|
||||||
|
|
||||||
// The interval probably did not get cleared after a window kill
|
// The interval probably did not get cleared after a window kill
|
||||||
log.warn('Clearing achievements timer');
|
log.warn("Clearing achievements timer");
|
||||||
clearInterval(intervalID);
|
clearInterval(intervalID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -36,10 +36,14 @@ async function enableAchievementsInterval(window) {
|
|||||||
|
|
||||||
function checkSteamAchievement(name) {
|
function checkSteamAchievement(name) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
greenworks.getAchievement(name, playerHas => resolve(playerHas ? name : ""), err => {
|
greenworks.getAchievement(
|
||||||
log.warn(`Failed to get Steam achievement ${name} status: ${err}`);
|
name,
|
||||||
resolve("");
|
(playerHas) => resolve(playerHas ? name : ""),
|
||||||
});
|
(err) => {
|
||||||
|
log.warn(`Failed to get Steam achievement ${name} status: ${err}`);
|
||||||
|
resolve("");
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,5 +54,6 @@ function disableAchievementsInterval(window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
enableAchievementsInterval, disableAchievementsInterval
|
enableAchievementsInterval,
|
||||||
}
|
disableAchievementsInterval,
|
||||||
|
};
|
||||||
|
@ -12,25 +12,27 @@ async function initialize(win) {
|
|||||||
window = win;
|
window = win;
|
||||||
server = http.createServer(async function (req, res) {
|
server = http.createServer(async function (req, res) {
|
||||||
let body = "";
|
let body = "";
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
req.on("data", (chunk) => {
|
req.on("data", (chunk) => {
|
||||||
body += chunk.toString(); // convert Buffer to string
|
body += chunk.toString(); // convert Buffer to string
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on("end", async () => {
|
req.on("end", async () => {
|
||||||
const providedToken = req.headers?.authorization?.replace('Bearer ', '') ?? '';
|
const providedToken = req.headers?.authorization?.replace("Bearer ", "") ?? "";
|
||||||
const isValid = providedToken === getAuthenticationToken();
|
const isValid = providedToken === getAuthenticationToken();
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
log.debug('Valid authentication token');
|
log.debug("Valid authentication token");
|
||||||
} else {
|
} else {
|
||||||
log.log('Invalid authentication token');
|
log.log("Invalid authentication token");
|
||||||
res.writeHead(401);
|
res.writeHead(401);
|
||||||
|
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
success: false,
|
JSON.stringify({
|
||||||
msg: 'Invalid authentication token'
|
success: false,
|
||||||
}));
|
msg: "Invalid authentication token",
|
||||||
|
}),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,16 +42,18 @@ async function initialize(win) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.warn(`Invalid body data`);
|
log.warn(`Invalid body data`);
|
||||||
res.writeHead(400);
|
res.writeHead(400);
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
success: false,
|
JSON.stringify({
|
||||||
msg: 'Invalid body data'
|
success: false,
|
||||||
}));
|
msg: "Invalid body data",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
switch(req.method) {
|
switch (req.method) {
|
||||||
// Request files
|
// Request files
|
||||||
case "GET":
|
case "GET":
|
||||||
result = await window.webContents.executeJavaScript(`document.getFiles()`);
|
result = await window.webContents.executeJavaScript(`document.getFiles()`);
|
||||||
@ -62,10 +66,12 @@ async function initialize(win) {
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
log.warn(`Invalid script update request - No data`);
|
log.warn(`Invalid script update request - No data`);
|
||||||
res.writeHead(400);
|
res.writeHead(400);
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
success: false,
|
JSON.stringify({
|
||||||
msg: 'Invalid script update request - No data'
|
success: false,
|
||||||
}));
|
msg: "Invalid script update request - No data",
|
||||||
|
}),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,19 +90,20 @@ async function initialize(win) {
|
|||||||
log.warn(`Api Server Error`, result.msg);
|
log.warn(`Api Server Error`, result.msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end(JSON.stringify({
|
res.end(
|
||||||
success: result.res,
|
JSON.stringify({
|
||||||
msg: result.msg,
|
success: result.res,
|
||||||
data: result.data
|
msg: result.msg,
|
||||||
}));
|
data: result.data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const autostart = config.get('autostart', false);
|
const autostart = config.get("autostart", false);
|
||||||
if (autostart) {
|
if (autostart) {
|
||||||
try {
|
try {
|
||||||
await enable()
|
await enable();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
@ -105,15 +112,14 @@ async function initialize(win) {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function enable() {
|
function enable() {
|
||||||
if (isListening()) {
|
if (isListening()) {
|
||||||
log.warn('API server already listening');
|
log.warn("API server already listening");
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = config.get('port', 9990);
|
const port = config.get("port", 9990);
|
||||||
const host = config.get('host', '127.0.0.1');
|
const host = config.get("host", "127.0.0.1");
|
||||||
log.log(`Starting http server on port ${port} - listening on ${host}`);
|
log.log(`Starting http server on port ${port} - listening on ${host}`);
|
||||||
|
|
||||||
// https://stackoverflow.com/a/62289870
|
// https://stackoverflow.com/a/62289870
|
||||||
@ -125,13 +131,10 @@ function enable() {
|
|||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
server.once('error', (err) => {
|
server.once("error", (err) => {
|
||||||
if (!startFinished) {
|
if (!startFinished) {
|
||||||
startFinished = true;
|
startFinished = true;
|
||||||
console.log(
|
console.log("There was an error starting the server in the error listener:", err);
|
||||||
'There was an error starting the server in the error listener:',
|
|
||||||
err
|
|
||||||
);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -140,11 +143,11 @@ function enable() {
|
|||||||
|
|
||||||
function disable() {
|
function disable() {
|
||||||
if (!isListening()) {
|
if (!isListening()) {
|
||||||
log.warn('API server not listening');
|
log.warn("API server not listening");
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
log.log('Stopping http server');
|
log.log("Stopping http server");
|
||||||
return server.close();
|
return server.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,31 +165,35 @@ function isListening() {
|
|||||||
|
|
||||||
function toggleAutostart() {
|
function toggleAutostart() {
|
||||||
const newValue = !isAutostart();
|
const newValue = !isAutostart();
|
||||||
config.set('autostart', newValue);
|
config.set("autostart", newValue);
|
||||||
log.log(`New autostart value is '${newValue}'`);
|
log.log(`New autostart value is '${newValue}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAutostart() {
|
function isAutostart() {
|
||||||
return config.get('autostart');
|
return config.get("autostart");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAuthenticationToken() {
|
function getAuthenticationToken() {
|
||||||
const token = config.get('token');
|
const token = config.get("token");
|
||||||
if (token) return token;
|
if (token) return token;
|
||||||
|
|
||||||
const newToken = generateToken();
|
const newToken = generateToken();
|
||||||
config.set('token', newToken);
|
config.set("token", newToken);
|
||||||
return newToken;
|
return newToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateToken() {
|
function generateToken() {
|
||||||
const buffer = crypto.randomBytes(48);
|
const buffer = crypto.randomBytes(48);
|
||||||
return buffer.toString('base64')
|
return buffer.toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initialize,
|
initialize,
|
||||||
enable, disable, toggleServer,
|
enable,
|
||||||
toggleAutostart, isAutostart,
|
disable,
|
||||||
getAuthenticationToken, isListening,
|
toggleServer,
|
||||||
}
|
toggleAutostart,
|
||||||
|
isAutostart,
|
||||||
|
getAuthenticationToken,
|
||||||
|
isListening,
|
||||||
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8" />
|
||||||
<title>Bitburner</title>
|
<title>Bitburner</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8" />
|
||||||
<title>Bitburner</title>
|
<title>Bitburner</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
@ -15,19 +15,20 @@ const debug = process.argv.includes("--debug");
|
|||||||
|
|
||||||
async function createWindow(killall) {
|
async function createWindow(killall) {
|
||||||
const setStopProcessHandler = global.app_handlers.stopProcess;
|
const setStopProcessHandler = global.app_handlers.stopProcess;
|
||||||
|
app.setAppUserModelId("Bitburner");
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (process.platform == 'linux') {
|
if (process.platform == "linux") {
|
||||||
icon = path.join(__dirname, 'icon.png');
|
icon = path.join(__dirname, "icon.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
const tracker = windowTracker('main');
|
const tracker = windowTracker("main");
|
||||||
const window = new BrowserWindow({
|
const window = new BrowserWindow({
|
||||||
icon,
|
icon,
|
||||||
show: false,
|
show: false,
|
||||||
backgroundThrottling: false,
|
backgroundThrottling: false,
|
||||||
backgroundColor: "#000000",
|
backgroundColor: "#000000",
|
||||||
title: 'Bitburner',
|
title: "Bitburner",
|
||||||
x: tracker.state.x,
|
x: tracker.state.x,
|
||||||
y: tracker.state.y,
|
y: tracker.state.y,
|
||||||
width: tracker.state.width,
|
width: tracker.state.width,
|
||||||
@ -36,7 +37,7 @@ async function createWindow(killall) {
|
|||||||
minHeight: 400,
|
minHeight: 400,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: true,
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, "preload.js"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,12 +52,12 @@ async function createWindow(killall) {
|
|||||||
|
|
||||||
window.webContents.on("new-window", async function (e, url) {
|
window.webContents.on("new-window", async function (e, url) {
|
||||||
// Let's make sure sure we have a proper url
|
// Let's make sure sure we have a proper url
|
||||||
let parsedUrl
|
let parsedUrl;
|
||||||
try {
|
try {
|
||||||
parsedUrl = new URL(url);
|
parsedUrl = new URL(url);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// This is an invalid url, let's just do nothing
|
// This is an invalid url, let's just do nothing
|
||||||
log.warn(`Invalid url found: ${url}`)
|
log.warn(`Invalid url found: ${url}`);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -73,11 +74,13 @@ async function createWindow(killall) {
|
|||||||
|
|
||||||
if (!isChild) {
|
if (!isChild) {
|
||||||
// If we're not relative to our app's path let's abort
|
// If we're not relative to our app's path let's abort
|
||||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} is not relative to the app: ${appPath.dir}${path.sep}${appPath.base}`)
|
log.warn(
|
||||||
|
`Requested path ${filePath.dir}${path.sep}${filePath.base} is not relative to the app: ${appPath.dir}${path.sep}${appPath.base}`,
|
||||||
|
);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
} else if (!fileExists) {
|
} else if (!fileExists) {
|
||||||
// If the file does not exist let's abort
|
// If the file does not exist let's abort
|
||||||
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} does not exist`)
|
log.warn(`Requested path ${filePath.dir}${path.sep}${filePath.base} does not exist`);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +92,7 @@ async function createWindow(killall) {
|
|||||||
let urlToOpen = parsedUrl.toString();
|
let urlToOpen = parsedUrl.toString();
|
||||||
if (parsedUrl.search) {
|
if (parsedUrl.search) {
|
||||||
log.log(`Cannot open a path with parameters: ${parsedUrl.search}`);
|
log.log(`Cannot open a path with parameters: ${parsedUrl.search}`);
|
||||||
urlToOpen = urlToOpen.replace(parsedUrl.search, '');
|
urlToOpen = urlToOpen.replace(parsedUrl.search, "");
|
||||||
// It would be possible to launch an URL with parameter using this, but it would mess up the process again...
|
// It would be possible to launch an URL with parameter using this, but it would mess up the process again...
|
||||||
// const escapedUri = parsedUrl.href.replace('&', '^&');
|
// const escapedUri = parsedUrl.href.replace('&', '^&');
|
||||||
// cp.spawn("cmd.exe", ["/c", "start", escapedUri], { detached: true, stdio: "ignore" });
|
// cp.spawn("cmd.exe", ["/c", "start", escapedUri], { detached: true, stdio: "ignore" });
|
||||||
|
@ -3,83 +3,91 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// The source code can be found in https://github.com/greenheartgames/greenworks
|
// The source code can be found in https://github.com/greenheartgames/greenworks
|
||||||
var fs = require('fs');
|
var fs = require("fs");
|
||||||
|
|
||||||
var greenworks;
|
var greenworks;
|
||||||
|
|
||||||
if (process.platform == 'darwin') {
|
if (process.platform == "darwin") {
|
||||||
if (process.arch == 'x64')
|
if (process.arch == "x64") greenworks = require("./lib/greenworks-osx64");
|
||||||
greenworks = require('./lib/greenworks-osx64');
|
else if (process.arch == "ia32") greenworks = require("./lib/greenworks-osx32");
|
||||||
else if (process.arch == 'ia32')
|
} else if (process.platform == "win32") {
|
||||||
greenworks = require('./lib/greenworks-osx32');
|
if (process.arch == "x64") greenworks = require("./lib/greenworks-win64");
|
||||||
} else if (process.platform == 'win32') {
|
else if (process.arch == "ia32") greenworks = require("./lib/greenworks-win32");
|
||||||
if (process.arch == 'x64')
|
} else if (process.platform == "linux") {
|
||||||
greenworks = require('./lib/greenworks-win64');
|
if (process.arch == "x64") greenworks = require("./lib/greenworks-linux64");
|
||||||
else if (process.arch == 'ia32')
|
else if (process.arch == "ia32") greenworks = require("./lib/greenworks-linux32");
|
||||||
greenworks = require('./lib/greenworks-win32');
|
|
||||||
} else if (process.platform == 'linux') {
|
|
||||||
if (process.arch == 'x64')
|
|
||||||
greenworks = require('./lib/greenworks-linux64');
|
|
||||||
else if (process.arch == 'ia32')
|
|
||||||
greenworks = require('./lib/greenworks-linux32');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function error_process(err, error_callback) {
|
function error_process(err, error_callback) {
|
||||||
if (err && error_callback)
|
if (err && error_callback) error_callback(err);
|
||||||
error_callback(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
greenworks.ugcGetItems = function(options, ugc_matching_type, ugc_query_type,
|
greenworks.ugcGetItems = function (options, ugc_matching_type, ugc_query_type, success_callback, error_callback) {
|
||||||
success_callback, error_callback) {
|
if (typeof options !== "object") {
|
||||||
if (typeof options !== 'object') {
|
|
||||||
error_callback = success_callback;
|
error_callback = success_callback;
|
||||||
success_callback = ugc_query_type;
|
success_callback = ugc_query_type;
|
||||||
ugc_query_type = ugc_matching_type;
|
ugc_query_type = ugc_matching_type;
|
||||||
ugc_matching_type = options;
|
ugc_matching_type = options;
|
||||||
options = {
|
options = {
|
||||||
'app_id': greenworks.getAppId(),
|
app_id: greenworks.getAppId(),
|
||||||
'page_num': 1
|
page_num: 1,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
greenworks._ugcGetItems(options, ugc_matching_type, ugc_query_type,
|
greenworks._ugcGetItems(options, ugc_matching_type, ugc_query_type, success_callback, error_callback);
|
||||||
success_callback, error_callback);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
greenworks.ugcGetUserItems = function(options, ugc_matching_type,
|
greenworks.ugcGetUserItems = function (
|
||||||
ugc_list_sort_order, ugc_list, success_callback, error_callback) {
|
options,
|
||||||
if (typeof options !== 'object') {
|
ugc_matching_type,
|
||||||
|
ugc_list_sort_order,
|
||||||
|
ugc_list,
|
||||||
|
success_callback,
|
||||||
|
error_callback,
|
||||||
|
) {
|
||||||
|
if (typeof options !== "object") {
|
||||||
error_callback = success_callback;
|
error_callback = success_callback;
|
||||||
success_callback = ugc_list;
|
success_callback = ugc_list;
|
||||||
ugc_list = ugc_list_sort_order;
|
ugc_list = ugc_list_sort_order;
|
||||||
ugc_list_sort_order = ugc_matching_type;
|
ugc_list_sort_order = ugc_matching_type;
|
||||||
ugc_matching_type = options;
|
ugc_matching_type = options;
|
||||||
options = {
|
options = {
|
||||||
'app_id': greenworks.getAppId(),
|
app_id: greenworks.getAppId(),
|
||||||
'page_num': 1
|
page_num: 1,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
greenworks._ugcGetUserItems(options, ugc_matching_type, ugc_list_sort_order,
|
greenworks._ugcGetUserItems(
|
||||||
ugc_list, success_callback, error_callback);
|
options,
|
||||||
}
|
ugc_matching_type,
|
||||||
|
ugc_list_sort_order,
|
||||||
|
ugc_list,
|
||||||
|
success_callback,
|
||||||
|
error_callback,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
greenworks.ugcSynchronizeItems = function (options, sync_dir, success_callback,
|
greenworks.ugcSynchronizeItems = function (options, sync_dir, success_callback, error_callback) {
|
||||||
error_callback) {
|
if (typeof options !== "object") {
|
||||||
if (typeof options !== 'object') {
|
|
||||||
error_callback = success_callback;
|
error_callback = success_callback;
|
||||||
success_callback = sync_dir;
|
success_callback = sync_dir;
|
||||||
sync_dir = options;
|
sync_dir = options;
|
||||||
options = {
|
options = {
|
||||||
'app_id': greenworks.getAppId(),
|
app_id: greenworks.getAppId(),
|
||||||
'page_num': 1
|
page_num: 1,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
greenworks._ugcSynchronizeItems(options, sync_dir, success_callback,
|
greenworks._ugcSynchronizeItems(options, sync_dir, success_callback, error_callback);
|
||||||
error_callback);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
greenworks.publishWorkshopFile = function(options, file_path, image_path, title,
|
greenworks.publishWorkshopFile = function (
|
||||||
description, success_callback, error_callback) {
|
options,
|
||||||
if (typeof options !== 'object') {
|
file_path,
|
||||||
|
image_path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
success_callback,
|
||||||
|
error_callback,
|
||||||
|
) {
|
||||||
|
if (typeof options !== "object") {
|
||||||
error_callback = success_callback;
|
error_callback = success_callback;
|
||||||
success_callback = description;
|
success_callback = description;
|
||||||
description = title;
|
description = title;
|
||||||
@ -87,18 +95,24 @@ greenworks.publishWorkshopFile = function(options, file_path, image_path, title,
|
|||||||
image_path = file_path;
|
image_path = file_path;
|
||||||
file_path = options;
|
file_path = options;
|
||||||
options = {
|
options = {
|
||||||
'app_id': greenworks.getAppId(),
|
app_id: greenworks.getAppId(),
|
||||||
'tags': []
|
tags: [],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
greenworks._publishWorkshopFile(options, file_path, image_path, title,
|
greenworks._publishWorkshopFile(options, file_path, image_path, title, description, success_callback, error_callback);
|
||||||
description, success_callback, error_callback);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
greenworks.updatePublishedWorkshopFile = function(options,
|
greenworks.updatePublishedWorkshopFile = function (
|
||||||
published_file_handle, file_path, image_path, title, description,
|
options,
|
||||||
success_callback, error_callback) {
|
published_file_handle,
|
||||||
if (typeof options !== 'object') {
|
file_path,
|
||||||
|
image_path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
success_callback,
|
||||||
|
error_callback,
|
||||||
|
) {
|
||||||
|
if (typeof options !== "object") {
|
||||||
error_callback = success_callback;
|
error_callback = success_callback;
|
||||||
success_callback = description;
|
success_callback = description;
|
||||||
description = title;
|
description = title;
|
||||||
@ -107,104 +121,166 @@ greenworks.updatePublishedWorkshopFile = function(options,
|
|||||||
file_path = published_file_handle;
|
file_path = published_file_handle;
|
||||||
published_file_handle = options;
|
published_file_handle = options;
|
||||||
options = {
|
options = {
|
||||||
'tags': [] // No tags are set
|
tags: [], // No tags are set
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
greenworks._updatePublishedWorkshopFile(options, published_file_handle,
|
greenworks._updatePublishedWorkshopFile(
|
||||||
file_path, image_path, title, description, success_callback,
|
options,
|
||||||
error_callback);
|
published_file_handle,
|
||||||
}
|
file_path,
|
||||||
|
image_path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
success_callback,
|
||||||
|
error_callback,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// An utility function for publish related APIs.
|
// An utility function for publish related APIs.
|
||||||
// It processes remains steps after saving files to Steam Cloud.
|
// It processes remains steps after saving files to Steam Cloud.
|
||||||
function file_share_process(file_name, image_name, next_process_func,
|
function file_share_process(file_name, image_name, next_process_func, error_callback, progress_callback) {
|
||||||
error_callback, progress_callback) {
|
if (progress_callback) progress_callback("Completed on saving files on Steam Cloud.");
|
||||||
if (progress_callback)
|
greenworks.fileShare(
|
||||||
progress_callback("Completed on saving files on Steam Cloud.");
|
file_name,
|
||||||
greenworks.fileShare(file_name, function() {
|
function () {
|
||||||
greenworks.fileShare(image_name, function() {
|
greenworks.fileShare(
|
||||||
next_process_func();
|
image_name,
|
||||||
}, function(err) { error_process(err, error_callback); });
|
function () {
|
||||||
}, function(err) { error_process(err, error_callback); });
|
next_process_func();
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
error_process(err, error_callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
error_process(err, error_callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publishing user generated content(ugc) to Steam contains following steps:
|
// Publishing user generated content(ugc) to Steam contains following steps:
|
||||||
// 1. Save file and image to Steam Cloud.
|
// 1. Save file and image to Steam Cloud.
|
||||||
// 2. Share the file and image.
|
// 2. Share the file and image.
|
||||||
// 3. publish the file to workshop.
|
// 3. publish the file to workshop.
|
||||||
greenworks.ugcPublish = function(file_name, title, description, image_name,
|
greenworks.ugcPublish = function (
|
||||||
success_callback, error_callback, progress_callback) {
|
file_name,
|
||||||
var publish_file_process = function() {
|
title,
|
||||||
if (progress_callback)
|
description,
|
||||||
progress_callback("Completed on sharing files.");
|
image_name,
|
||||||
greenworks.publishWorkshopFile(file_name, image_name, title, description,
|
success_callback,
|
||||||
function(publish_file_id) { success_callback(publish_file_id); },
|
error_callback,
|
||||||
function(err) { error_process(err, error_callback); });
|
progress_callback,
|
||||||
|
) {
|
||||||
|
var publish_file_process = function () {
|
||||||
|
if (progress_callback) progress_callback("Completed on sharing files.");
|
||||||
|
greenworks.publishWorkshopFile(
|
||||||
|
file_name,
|
||||||
|
image_name,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
function (publish_file_id) {
|
||||||
|
success_callback(publish_file_id);
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
error_process(err, error_callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
greenworks.saveFilesToCloud([file_name, image_name], function() {
|
greenworks.saveFilesToCloud(
|
||||||
file_share_process(file_name, image_name, publish_file_process,
|
[file_name, image_name],
|
||||||
error_callback, progress_callback);
|
function () {
|
||||||
}, function(err) { error_process(err, error_callback); });
|
file_share_process(file_name, image_name, publish_file_process, error_callback, progress_callback);
|
||||||
}
|
},
|
||||||
|
function (err) {
|
||||||
|
error_process(err, error_callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Update publish ugc steps:
|
// Update publish ugc steps:
|
||||||
// 1. Save new file and image to Steam Cloud.
|
// 1. Save new file and image to Steam Cloud.
|
||||||
// 2. Share file and images.
|
// 2. Share file and images.
|
||||||
// 3. Update published file.
|
// 3. Update published file.
|
||||||
greenworks.ugcPublishUpdate = function(published_file_id, file_name, title,
|
greenworks.ugcPublishUpdate = function (
|
||||||
description, image_name, success_callback, error_callback,
|
published_file_id,
|
||||||
progress_callback) {
|
file_name,
|
||||||
var update_published_file_process = function() {
|
title,
|
||||||
if (progress_callback)
|
description,
|
||||||
progress_callback("Completed on sharing files.");
|
image_name,
|
||||||
greenworks.updatePublishedWorkshopFile(published_file_id,
|
success_callback,
|
||||||
file_name, image_name, title, description,
|
error_callback,
|
||||||
function() { success_callback(); },
|
progress_callback,
|
||||||
function(err) { error_process(err, error_callback); });
|
) {
|
||||||
|
var update_published_file_process = function () {
|
||||||
|
if (progress_callback) progress_callback("Completed on sharing files.");
|
||||||
|
greenworks.updatePublishedWorkshopFile(
|
||||||
|
published_file_id,
|
||||||
|
file_name,
|
||||||
|
image_name,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
function () {
|
||||||
|
success_callback();
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
error_process(err, error_callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
greenworks.saveFilesToCloud([file_name, image_name], function() {
|
greenworks.saveFilesToCloud(
|
||||||
file_share_process(file_name, image_name, update_published_file_process,
|
[file_name, image_name],
|
||||||
error_callback, progress_callback);
|
function () {
|
||||||
}, function(err) { error_process(err, error_callback); });
|
file_share_process(file_name, image_name, update_published_file_process, error_callback, progress_callback);
|
||||||
}
|
},
|
||||||
|
function (err) {
|
||||||
|
error_process(err, error_callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Greenworks Utils APIs implmentation.
|
// Greenworks Utils APIs implmentation.
|
||||||
greenworks.Utils.move = function(source_dir, target_dir, success_callback,
|
greenworks.Utils.move = function (source_dir, target_dir, success_callback, error_callback) {
|
||||||
error_callback) {
|
fs.rename(source_dir, target_dir, function (err) {
|
||||||
fs.rename(source_dir, target_dir, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
if (error_callback) error_callback(err);
|
if (error_callback) error_callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (success_callback)
|
if (success_callback) success_callback();
|
||||||
success_callback();
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
greenworks.init = function() {
|
greenworks.init = function () {
|
||||||
if (this.initAPI()) return true;
|
if (this.initAPI()) return true;
|
||||||
if (!this.isSteamRunning())
|
if (!this.isSteamRunning()) throw new Error("Steam initialization failed. Steam is not running.");
|
||||||
throw new Error("Steam initialization failed. Steam is not running.");
|
|
||||||
var appId;
|
var appId;
|
||||||
try {
|
try {
|
||||||
appId = fs.readFileSync('steam_appid.txt', 'utf8');
|
appId = fs.readFileSync("steam_appid.txt", "utf8");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("Steam initialization failed. Steam is running," +
|
throw new Error(
|
||||||
"but steam_appid.txt is missing. Expected to find it in: " +
|
"Steam initialization failed. Steam is running," +
|
||||||
require('path').resolve('steam_appid.txt'));
|
"but steam_appid.txt is missing. Expected to find it in: " +
|
||||||
|
require("path").resolve("steam_appid.txt"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!/^\d+ *\r?\n?$/.test(appId)) {
|
if (!/^\d+ *\r?\n?$/.test(appId)) {
|
||||||
throw new Error("Steam initialization failed. " +
|
throw new Error(
|
||||||
"steam_appid.txt appears to be invalid; " +
|
"Steam initialization failed. " +
|
||||||
"it should contain a numeric ID: " + appId);
|
"steam_appid.txt appears to be invalid; " +
|
||||||
|
"it should contain a numeric ID: " +
|
||||||
|
appId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw new Error("Steam initialization failed, but Steam is running, " +
|
throw new Error(
|
||||||
"and steam_appid.txt is present and valid." +
|
"Steam initialization failed, but Steam is running, " +
|
||||||
"Maybe that's not really YOUR app ID? " + appId.trim());
|
"and steam_appid.txt is present and valid." +
|
||||||
}
|
"Maybe that's not really YOUR app ID? " +
|
||||||
|
appId.trim(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require("events").EventEmitter;
|
||||||
greenworks.__proto__ = EventEmitter.prototype;
|
greenworks.__proto__ = EventEmitter.prototype;
|
||||||
EventEmitter.call(greenworks);
|
EventEmitter.call(greenworks);
|
||||||
|
|
||||||
@ -212,6 +288,6 @@ greenworks._steam_events.on = function () {
|
|||||||
greenworks.emit.apply(greenworks, arguments);
|
greenworks.emit.apply(greenworks, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
process.versions['greenworks'] = greenworks._version;
|
process.versions["greenworks"] = greenworks._version;
|
||||||
|
|
||||||
module.exports = greenworks;
|
module.exports = greenworks;
|
||||||
|
@ -18,7 +18,7 @@ log.transports.console.level = config.get("console-log-level", "debug");
|
|||||||
log.catchErrors();
|
log.catchErrors();
|
||||||
log.info(`Started app: ${JSON.stringify(process.argv)}`);
|
log.info(`Started app: ${JSON.stringify(process.argv)}`);
|
||||||
|
|
||||||
process.on('uncaughtException', function () {
|
process.on("uncaughtException", function () {
|
||||||
// The exception will already have been logged by electron-log
|
// The exception will already have been logged by electron-log
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
@ -67,42 +67,43 @@ function setStopProcessHandler(app, window, enabled) {
|
|||||||
// So we'll alert the player to close their browser.
|
// So we'll alert the player to close their browser.
|
||||||
if (global.app_playerOpenedExternalLink) {
|
if (global.app_playerOpenedExternalLink) {
|
||||||
await dialog.showMessageBox({
|
await dialog.showMessageBox({
|
||||||
title: 'Bitburner',
|
title: "Bitburner",
|
||||||
message: 'You may have to close your browser to properly exit the game.',
|
message: "You may have to close your browser to properly exit the game.",
|
||||||
detail: 'Steam will keep tracking Bitburner as "Running" if any process started within the game is still running.' +
|
detail:
|
||||||
' This includes launching an external link, which opens up your browser.',
|
'Steam will keep tracking Bitburner as "Running" if any process started within the game is still running.' +
|
||||||
type: 'warning', buttons: ['OK']
|
" This includes launching an external link, which opens up your browser.",
|
||||||
|
type: "warning",
|
||||||
|
buttons: ["OK"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// We'll try to execute javascript on the page to see if we're stuck
|
// We'll try to execute javascript on the page to see if we're stuck
|
||||||
let canRunJS = false;
|
let canRunJS = false;
|
||||||
window.webContents.executeJavaScript('window.stop(); document.close()', true)
|
window.webContents.executeJavaScript("window.stop(); document.close()", true).then(() => (canRunJS = true));
|
||||||
.then(() => canRunJS = true);
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Wait a few milliseconds to prevent a race condition before loading the exit screen
|
// Wait a few milliseconds to prevent a race condition before loading the exit screen
|
||||||
window.webContents.stop();
|
window.webContents.stop();
|
||||||
window.loadFile("exit.html")
|
window.loadFile("exit.html");
|
||||||
}, 20);
|
}, 20);
|
||||||
|
|
||||||
// Wait 200ms, if the promise has not yet resolved, let's crash the process since we're possibly in a stuck scenario
|
// Wait 200ms, if the promise has not yet resolved, let's crash the process since we're possibly in a stuck scenario
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!canRunJS) {
|
if (!canRunJS) {
|
||||||
// We're stuck, let's crash the process
|
// We're stuck, let's crash the process
|
||||||
log.warn('Forcefully crashing the renderer process');
|
log.warn("Forcefully crashing the renderer process");
|
||||||
window.webContents.forcefullyCrashRenderer();
|
window.webContents.forcefullyCrashRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug('Destroying the window');
|
log.debug("Destroying the window");
|
||||||
window.destroy();
|
window.destroy();
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
};
|
||||||
|
|
||||||
const clearWindowHandler = () => {
|
const clearWindowHandler = () => {
|
||||||
window = null;
|
window = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopProcessHandler = () => {
|
const stopProcessHandler = () => {
|
||||||
log.info('Quitting the app...');
|
log.info("Quitting the app...");
|
||||||
app.isQuiting = true;
|
app.isQuiting = true;
|
||||||
app.quit();
|
app.quit();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
@ -121,12 +122,12 @@ function setStopProcessHandler(app, window, enabled) {
|
|||||||
const restoreNewest = config.get("onload-restore-newest", true);
|
const restoreNewest = config.get("onload-restore-newest", true);
|
||||||
if (restoreNewest && !isRestoreDisabled) {
|
if (restoreNewest && !isRestoreDisabled) {
|
||||||
try {
|
try {
|
||||||
await storage.restoreIfNewerExists(window)
|
await storage.restoreIfNewerExists(window);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Could not restore newer file", error);
|
log.error("Could not restore newer file", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const receivedDisableRestoreHandler = async (event, arg) => {
|
const receivedDisableRestoreHandler = async (event, arg) => {
|
||||||
if (!window) {
|
if (!window) {
|
||||||
@ -140,7 +141,7 @@ function setStopProcessHandler(app, window, enabled) {
|
|||||||
isRestoreDisabled = false;
|
isRestoreDisabled = false;
|
||||||
log.debug("Re-enabling auto-restore");
|
log.debug("Re-enabling auto-restore");
|
||||||
}, arg.duration);
|
}, arg.duration);
|
||||||
}
|
};
|
||||||
|
|
||||||
const receivedGameSavedHandler = async (event, arg) => {
|
const receivedGameSavedHandler = async (event, arg) => {
|
||||||
if (!window) {
|
if (!window) {
|
||||||
@ -149,7 +150,7 @@ function setStopProcessHandler(app, window, enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { save, ...other } = arg;
|
const { save, ...other } = arg;
|
||||||
log.silly("Received game saved info", {...other, save: `${save.length} bytes`});
|
log.silly("Received game saved info", { ...other, save: `${save.length} bytes` });
|
||||||
|
|
||||||
if (storage.isAutosaveEnabled()) {
|
if (storage.isAutosaveEnabled()) {
|
||||||
saveToDisk(save, arg.fileName);
|
saveToDisk(save, arg.fileName);
|
||||||
@ -159,43 +160,51 @@ function setStopProcessHandler(app, window, enabled) {
|
|||||||
const playtime = window.gameInfo.player.playtime;
|
const playtime = window.gameInfo.player.playtime;
|
||||||
log.silly(window.gameInfo);
|
log.silly(window.gameInfo);
|
||||||
if (playtime > minimumPlaytime) {
|
if (playtime > minimumPlaytime) {
|
||||||
saveToCloud(save);
|
saveToCloud(save);
|
||||||
} else {
|
} else {
|
||||||
log.debug(`Auto-save to cloud disabled for save game under ${minimumPlaytime}ms (${playtime}ms)`);
|
log.debug(`Auto-save to cloud disabled for save game under ${minimumPlaytime}ms (${playtime}ms)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const saveToCloud = debounce(async (save) => {
|
const saveToCloud = debounce(
|
||||||
log.debug("Saving to Steam Cloud ...")
|
async (save) => {
|
||||||
try {
|
log.debug("Saving to Steam Cloud ...");
|
||||||
const playerId = window.gameInfo.player.identifier;
|
try {
|
||||||
await storage.pushGameSaveToSteamCloud(save, playerId);
|
const playerId = window.gameInfo.player.identifier;
|
||||||
log.silly("Saved Game to Steam Cloud");
|
await storage.pushGameSaveToSteamCloud(save, playerId);
|
||||||
} catch (error) {
|
log.silly("Saved Game to Steam Cloud");
|
||||||
log.error(error);
|
} catch (error) {
|
||||||
utils.writeToast(window, "Could not save to Steam Cloud.", "error", 5000);
|
log.error(error);
|
||||||
}
|
utils.writeToast(window, "Could not save to Steam Cloud.", "error", 5000);
|
||||||
}, config.get("cloud-save-min-time", 1000 * 60 * 15), { leading: true });
|
}
|
||||||
|
},
|
||||||
|
config.get("cloud-save-min-time", 1000 * 60 * 15),
|
||||||
|
{ leading: true },
|
||||||
|
);
|
||||||
|
|
||||||
const saveToDisk = debounce(async (save, fileName) => {
|
const saveToDisk = debounce(
|
||||||
log.debug("Saving to Disk ...")
|
async (save, fileName) => {
|
||||||
try {
|
log.debug("Saving to Disk ...");
|
||||||
const file = await storage.saveGameToDisk(window, { save, fileName });
|
try {
|
||||||
log.silly(`Saved Game to '${file.replaceAll('\\', '\\\\')}'`);
|
const file = await storage.saveGameToDisk(window, { save, fileName });
|
||||||
} catch (error) {
|
log.silly(`Saved Game to '${file.replaceAll("\\", "\\\\")}'`);
|
||||||
log.error(error);
|
} catch (error) {
|
||||||
utils.writeToast(window, "Could not save to disk", "error", 5000);
|
log.error(error);
|
||||||
}
|
utils.writeToast(window, "Could not save to disk", "error", 5000);
|
||||||
}, config.get("disk-save-min-time", 1000 * 60 * 5), { leading: true });
|
}
|
||||||
|
},
|
||||||
|
config.get("disk-save-min-time", 1000 * 60 * 5),
|
||||||
|
{ leading: true },
|
||||||
|
);
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
log.debug("Adding closing handlers");
|
log.debug("Adding closing handlers");
|
||||||
ipcMain.on("push-game-ready", receivedGameReadyHandler);
|
ipcMain.on("push-game-ready", receivedGameReadyHandler);
|
||||||
ipcMain.on("push-game-saved", receivedGameSavedHandler);
|
ipcMain.on("push-game-saved", receivedGameSavedHandler);
|
||||||
ipcMain.on("push-disable-restore", receivedDisableRestoreHandler)
|
ipcMain.on("push-disable-restore", receivedDisableRestoreHandler);
|
||||||
window.on("closed", clearWindowHandler);
|
window.on("closed", clearWindowHandler);
|
||||||
window.on("close", closingWindowHandler)
|
window.on("close", closingWindowHandler);
|
||||||
app.on("window-all-closed", stopProcessHandler);
|
app.on("window-all-closed", stopProcessHandler);
|
||||||
} else {
|
} else {
|
||||||
log.debug("Removing closing handlers");
|
log.debug("Removing closing handlers");
|
||||||
@ -213,7 +222,7 @@ async function startWindow(noScript) {
|
|||||||
global.app_handlers = {
|
global.app_handlers = {
|
||||||
stopProcess: setStopProcessHandler,
|
stopProcess: setStopProcessHandler,
|
||||||
createWindow: startWindow,
|
createWindow: startWindow,
|
||||||
}
|
};
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
log.info("Application is ready!");
|
log.info("Application is ready!");
|
||||||
@ -231,7 +240,8 @@ app.whenReady().then(async () => {
|
|||||||
title: "Bitburner",
|
title: "Bitburner",
|
||||||
message: "Could not connect to Steam",
|
message: "Could not connect to Steam",
|
||||||
detail: `${global.greenworksError}\n\nYou won't be able to receive achievements until this is resolved and you restart the game.`,
|
detail: `${global.greenworksError}\n\nYou won't be able to receive achievements until this is resolved and you restart the game.`,
|
||||||
type: 'warning', buttons: ['OK']
|
type: "warning",
|
||||||
|
buttons: ["OK"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
112
electron/menu.js
112
electron/menu.js
@ -37,7 +37,7 @@ function getMenu(window) {
|
|||||||
log.error(error);
|
log.error(error);
|
||||||
utils.writeToast(window, "Could not load last save from disk", "error", 5000);
|
utils.writeToast(window, "Could not load last save from disk", "error", 5000);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Load From File",
|
label: "Load From File",
|
||||||
@ -51,9 +51,7 @@ function getMenu(window) {
|
|||||||
{ name: "Game Saves", extensions: ["json", "json.gz", "txt"] },
|
{ name: "Game Saves", extensions: ["json", "json.gz", "txt"] },
|
||||||
{ name: "All", extensions: ["*"] },
|
{ name: "All", extensions: ["*"] },
|
||||||
],
|
],
|
||||||
properties: [
|
properties: ["openFile", "dontAddToRecent"],
|
||||||
"openFile", "dontAddToRecent",
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
if (result.canceled) return;
|
if (result.canceled) return;
|
||||||
const file = result.filePaths[0];
|
const file = result.filePaths[0];
|
||||||
@ -65,7 +63,7 @@ function getMenu(window) {
|
|||||||
log.error(error);
|
log.error(error);
|
||||||
utils.writeToast(window, "Could not load save from disk", "error", 5000);
|
utils.writeToast(window, "Could not load save from disk", "error", 5000);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Load From Steam Cloud",
|
label: "Load From Steam Cloud",
|
||||||
@ -78,7 +76,7 @@ function getMenu(window) {
|
|||||||
log.error(error);
|
log.error(error);
|
||||||
utils.writeToast(window, "Could not load from Steam Cloud", "error", 5000);
|
utils.writeToast(window, "Could not load from Steam Cloud", "error", 5000);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "separator",
|
type: "separator",
|
||||||
@ -89,8 +87,7 @@ function getMenu(window) {
|
|||||||
checked: storage.isSaveCompressionEnabled(),
|
checked: storage.isSaveCompressionEnabled(),
|
||||||
click: (menuItem) => {
|
click: (menuItem) => {
|
||||||
storage.setSaveCompressionConfig(menuItem.checked);
|
storage.setSaveCompressionConfig(menuItem.checked);
|
||||||
utils.writeToast(window,
|
utils.writeToast(window, `${menuItem.checked ? "Enabled" : "Disabled"} Save Compression`, "info", 5000);
|
||||||
`${menuItem.checked ? "Enabled" : "Disabled"} Save Compression`, "info", 5000);
|
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -100,8 +97,7 @@ function getMenu(window) {
|
|||||||
checked: storage.isAutosaveEnabled(),
|
checked: storage.isAutosaveEnabled(),
|
||||||
click: (menuItem) => {
|
click: (menuItem) => {
|
||||||
storage.setAutosaveConfig(menuItem.checked);
|
storage.setAutosaveConfig(menuItem.checked);
|
||||||
utils.writeToast(window,
|
utils.writeToast(window, `${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Disk`, "info", 5000);
|
||||||
`${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Disk`, "info", 5000);
|
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -112,8 +108,12 @@ function getMenu(window) {
|
|||||||
checked: storage.isCloudEnabled(),
|
checked: storage.isCloudEnabled(),
|
||||||
click: (menuItem) => {
|
click: (menuItem) => {
|
||||||
storage.setCloudEnabledConfig(menuItem.checked);
|
storage.setCloudEnabledConfig(menuItem.checked);
|
||||||
utils.writeToast(window,
|
utils.writeToast(
|
||||||
`${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Steam Cloud`, "info", 5000);
|
window,
|
||||||
|
`${menuItem.checked ? "Enabled" : "Disabled"} Auto-Save to Steam Cloud`,
|
||||||
|
"info",
|
||||||
|
5000,
|
||||||
|
);
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -123,8 +123,12 @@ function getMenu(window) {
|
|||||||
checked: config.get("onload-restore-newest", true),
|
checked: config.get("onload-restore-newest", true),
|
||||||
click: (menuItem) => {
|
click: (menuItem) => {
|
||||||
config.set("onload-restore-newest", menuItem.checked);
|
config.set("onload-restore-newest", menuItem.checked);
|
||||||
utils.writeToast(window,
|
utils.writeToast(
|
||||||
`${menuItem.checked ? "Enabled" : "Disabled"} Restore Newest on Load`, "info", 5000);
|
window,
|
||||||
|
`${menuItem.checked ? "Enabled" : "Disabled"} Restore Newest on Load`,
|
||||||
|
"info",
|
||||||
|
5000,
|
||||||
|
);
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -153,7 +157,7 @@ function getMenu(window) {
|
|||||||
label: "Open Data Directory",
|
label: "Open Data Directory",
|
||||||
click: () => shell.openPath(app.getPath("userData")),
|
click: () => shell.openPath(app.getPath("userData")),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "separator",
|
type: "separator",
|
||||||
@ -162,7 +166,7 @@ function getMenu(window) {
|
|||||||
label: "Quit",
|
label: "Quit",
|
||||||
click: () => app.quit(),
|
click: () => app.quit(),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Edit",
|
label: "Edit",
|
||||||
@ -210,29 +214,29 @@ function getMenu(window) {
|
|||||||
label: "API Server",
|
label: "API Server",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: api.isListening() ? 'Disable Server' : 'Enable Server',
|
label: api.isListening() ? "Disable Server" : "Enable Server",
|
||||||
click: (async () => {
|
click: async () => {
|
||||||
let success = false;
|
let success = false;
|
||||||
try {
|
try {
|
||||||
await api.toggleServer();
|
await api.toggleServer();
|
||||||
success = true;
|
success = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
utils.showErrorBox('Error Toggling Server', error);
|
utils.showErrorBox("Error Toggling Server", error);
|
||||||
}
|
}
|
||||||
if (success && api.isListening()) {
|
if (success && api.isListening()) {
|
||||||
utils.writeToast(window, "Started API Server", "success");
|
utils.writeToast(window, "Started API Server", "success");
|
||||||
} else if (success && !api.isListening()) {
|
} else if (success && !api.isListening()) {
|
||||||
utils.writeToast(window, "Stopped API Server", "success");
|
utils.writeToast(window, "Stopped API Server", "success");
|
||||||
} else {
|
} else {
|
||||||
utils.writeToast(window, 'Error Toggling Server', "error");
|
utils.writeToast(window, "Error Toggling Server", "error");
|
||||||
}
|
}
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
})
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: api.isAutostart() ? 'Disable Autostart' : 'Enable Autostart',
|
label: api.isAutostart() ? "Disable Autostart" : "Enable Autostart",
|
||||||
click: (async () => {
|
click: async () => {
|
||||||
api.toggleAutostart();
|
api.toggleAutostart();
|
||||||
if (api.isAutostart()) {
|
if (api.isAutostart()) {
|
||||||
utils.writeToast(window, "Enabled API Server Autostart", "success");
|
utils.writeToast(window, "Enabled API Server Autostart", "success");
|
||||||
@ -240,42 +244,45 @@ function getMenu(window) {
|
|||||||
utils.writeToast(window, "Disabled API Server Autostart", "success");
|
utils.writeToast(window, "Disabled API Server Autostart", "success");
|
||||||
}
|
}
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
})
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Copy Auth Token',
|
label: "Copy Auth Token",
|
||||||
click: (async () => {
|
click: async () => {
|
||||||
const token = api.getAuthenticationToken();
|
const token = api.getAuthenticationToken();
|
||||||
log.log('Wrote authentication token to clipboard');
|
log.log("Wrote authentication token to clipboard");
|
||||||
clipboard.writeText(token);
|
clipboard.writeText(token);
|
||||||
utils.writeToast(window, "Copied Authentication Token to Clipboard", "info");
|
utils.writeToast(window, "Copied Authentication Token to Clipboard", "info");
|
||||||
})
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: "separator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Information',
|
label: "Information",
|
||||||
click: () => {
|
click: () => {
|
||||||
dialog.showMessageBox({
|
dialog
|
||||||
type: 'info',
|
.showMessageBox({
|
||||||
title: 'Bitburner > API Server Information',
|
type: "info",
|
||||||
message: 'The API Server is used to write script files to your in-game home.',
|
title: "Bitburner > API Server Information",
|
||||||
detail: 'There is an official Visual Studio Code extension that makes use of that feature.\n\n' +
|
message: "The API Server is used to write script files to your in-game home.",
|
||||||
'It allows you to write your script file in an external IDE and have them pushed over to the game automatically.\n' +
|
detail:
|
||||||
'If you want more information, head over to: https://github.com/bitburner-official/bitburner-vscode.',
|
"There is an official Visual Studio Code extension that makes use of that feature.\n\n" +
|
||||||
buttons: ['Dismiss', 'Open Extension Link (GitHub)'],
|
"It allows you to write your script file in an external IDE and have them pushed over to the game automatically.\n" +
|
||||||
|
"If you want more information, head over to: https://github.com/bitburner-official/bitburner-vscode.",
|
||||||
|
buttons: ["Dismiss", "Open Extension Link (GitHub)"],
|
||||||
defaultId: 0,
|
defaultId: 0,
|
||||||
cancelId: 0,
|
cancelId: 0,
|
||||||
noLink: true,
|
noLink: true,
|
||||||
}).then(({response}) => {
|
})
|
||||||
if (response === 1) {
|
.then(({ response }) => {
|
||||||
utils.openExternal('https://github.com/bitburner-official/bitburner-vscode');
|
if (response === 1) {
|
||||||
}
|
utils.openExternal("https://github.com/bitburner-official/bitburner-vscode");
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}
|
},
|
||||||
]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Zoom",
|
label: "Zoom",
|
||||||
@ -291,7 +298,7 @@ function getMenu(window) {
|
|||||||
utils.setZoomFactor(window, newZoom);
|
utils.setZoomFactor(window, newZoom);
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
} else {
|
} else {
|
||||||
log.log('Max zoom out')
|
log.log("Max zoom out");
|
||||||
utils.writeToast(window, "Cannot zoom in anymore", "warning");
|
utils.writeToast(window, "Cannot zoom in anymore", "warning");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -307,7 +314,7 @@ function getMenu(window) {
|
|||||||
utils.setZoomFactor(window, newZoom);
|
utils.setZoomFactor(window, newZoom);
|
||||||
refreshMenu(window);
|
refreshMenu(window);
|
||||||
} else {
|
} else {
|
||||||
log.log('Max zoom in')
|
log.log("Max zoom in");
|
||||||
utils.writeToast(window, "Cannot zoom out anymore", "warning");
|
utils.writeToast(window, "Cannot zoom out anymore", "warning");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -340,8 +347,8 @@ function getMenu(window) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@ -352,5 +359,6 @@ function refreshMenu(window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMenu, refreshMenu,
|
getMenu,
|
||||||
}
|
refreshMenu,
|
||||||
|
};
|
||||||
|
@ -1,38 +1,36 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const { ipcRenderer, contextBridge } = require('electron')
|
const { ipcRenderer, contextBridge } = require("electron");
|
||||||
const log = require("electron-log");
|
const log = require("electron-log");
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld(
|
contextBridge.exposeInMainWorld("electronBridge", {
|
||||||
"electronBridge", {
|
send: (channel, data) => {
|
||||||
send: (channel, data) => {
|
log.log("Send on channel " + channel);
|
||||||
log.log("Send on channel " + channel)
|
// whitelist channels
|
||||||
// whitelist channels
|
let validChannels = [
|
||||||
let validChannels = [
|
"get-save-data-response",
|
||||||
"get-save-data-response",
|
"get-save-info-response",
|
||||||
"get-save-info-response",
|
"push-game-saved",
|
||||||
"push-game-saved",
|
"push-game-ready",
|
||||||
"push-game-ready",
|
"push-import-result",
|
||||||
"push-import-result",
|
"push-disable-restore",
|
||||||
"push-disable-restore",
|
];
|
||||||
];
|
if (validChannels.includes(channel)) {
|
||||||
if (validChannels.includes(channel)) {
|
ipcRenderer.send(channel, data);
|
||||||
ipcRenderer.send(channel, data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
receive: (channel, func) => {
|
|
||||||
log.log("Receive on channel " + channel)
|
|
||||||
let validChannels = [
|
|
||||||
"get-save-data-request",
|
|
||||||
"get-save-info-request",
|
|
||||||
"push-save-request",
|
|
||||||
"trigger-save",
|
|
||||||
"trigger-game-export",
|
|
||||||
"trigger-scripts-export",
|
|
||||||
];
|
|
||||||
if (validChannels.includes(channel)) {
|
|
||||||
// Deliberately strip event as it includes `sender`
|
|
||||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
receive: (channel, func) => {
|
||||||
|
log.log("Receive on channel " + channel);
|
||||||
|
let validChannels = [
|
||||||
|
"get-save-data-request",
|
||||||
|
"get-save-info-request",
|
||||||
|
"push-save-request",
|
||||||
|
"trigger-save",
|
||||||
|
"trigger-game-export",
|
||||||
|
"trigger-scripts-export",
|
||||||
|
];
|
||||||
|
if (validChannels.includes(channel)) {
|
||||||
|
// Deliberately strip event as it includes `sender`
|
||||||
|
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -16,9 +16,9 @@ const config = new Config();
|
|||||||
// https://stackoverflow.com/a/69418940
|
// https://stackoverflow.com/a/69418940
|
||||||
const dirSize = async (directory) => {
|
const dirSize = async (directory) => {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
const stats = files.map(file => fs.stat(path.join(directory, file)));
|
const stats = files.map((file) => fs.stat(path.join(directory, file)));
|
||||||
return (await Promise.all(stats)).reduce((accumulator, { size }) => accumulator + size, 0);
|
return (await Promise.all(stats)).reduce((accumulator, { size }) => accumulator + size, 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getDirFileStats = async (directory) => {
|
const getDirFileStats = async (directory) => {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
@ -26,30 +26,31 @@ const getDirFileStats = async (directory) => {
|
|||||||
const file = path.join(directory, f);
|
const file = path.join(directory, f);
|
||||||
return fs.stat(file).then((stat) => ({ file, stat }));
|
return fs.stat(file).then((stat) => ({ file, stat }));
|
||||||
});
|
});
|
||||||
const data = (await Promise.all(stats));
|
const data = await Promise.all(stats);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNewestFile = async (directory) => {
|
const getNewestFile = async (directory) => {
|
||||||
const data = await getDirFileStats(directory)
|
const data = await getDirFileStats(directory);
|
||||||
return data.sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime())[0];
|
return data.sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime())[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAllSaves = async (window) => {
|
const getAllSaves = async (window) => {
|
||||||
const rootDirectory = await getSaveFolder(window, true);
|
const rootDirectory = await getSaveFolder(window, true);
|
||||||
const data = await fs.readdir(rootDirectory, { withFileTypes: true});
|
const data = await fs.readdir(rootDirectory, { withFileTypes: true });
|
||||||
const savesPromises = data.filter((e) => e.isDirectory()).
|
const savesPromises = data
|
||||||
map((dir) => path.join(rootDirectory, dir.name)).
|
.filter((e) => e.isDirectory())
|
||||||
map((dir) => getDirFileStats(dir));
|
.map((dir) => path.join(rootDirectory, dir.name))
|
||||||
|
.map((dir) => getDirFileStats(dir));
|
||||||
const saves = await Promise.all(savesPromises);
|
const saves = await Promise.all(savesPromises);
|
||||||
const flat = flatten(saves);
|
const flat = flatten(saves);
|
||||||
return flat;
|
return flat;
|
||||||
}
|
};
|
||||||
|
|
||||||
async function prepareSaveFolders(window) {
|
async function prepareSaveFolders(window) {
|
||||||
const rootFolder = await getSaveFolder(window, true);
|
const rootFolder = await getSaveFolder(window, true);
|
||||||
const currentFolder = await getSaveFolder(window);
|
const currentFolder = await getSaveFolder(window);
|
||||||
const backupsFolder = path.join(rootFolder, "/_backups")
|
const backupsFolder = path.join(rootFolder, "/_backups");
|
||||||
await prepareFolders(rootFolder, currentFolder, backupsFolder);
|
await prepareFolders(rootFolder, currentFolder, backupsFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ async function prepareFolders(...folders) {
|
|||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await fs.stat(folder);
|
await fs.stat(folder);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === "ENOENT") {
|
||||||
log.warn(`'${folder}' not found, creating it...`);
|
log.warn(`'${folder}' not found, creating it...`);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await fs.mkdir(folder);
|
await fs.mkdir(folder);
|
||||||
@ -125,14 +126,14 @@ function isCloudEnabled() {
|
|||||||
function saveCloudFile(name, content) {
|
function saveCloudFile(name, content) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
greenworks.saveTextToFile(name, content, resolve, reject);
|
greenworks.saveTextToFile(name, content, resolve, reject);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFirstCloudFile() {
|
function getFirstCloudFile() {
|
||||||
const nbFiles = greenworks.getFileCount();
|
const nbFiles = greenworks.getFileCount();
|
||||||
if (nbFiles === 0) throw new Error('No files in cloud');
|
if (nbFiles === 0) throw new Error("No files in cloud");
|
||||||
const file = greenworks.getFileNameAndSize(0);
|
const file = greenworks.getFileNameAndSize(0);
|
||||||
log.silly(`Found ${nbFiles} files.`)
|
log.silly(`Found ${nbFiles} files.`);
|
||||||
log.silly(`First File: ${file.name} (${file.size} bytes)`);
|
log.silly(`First File: ${file.name} (${file.size} bytes)`);
|
||||||
return file.name;
|
return file.name;
|
||||||
}
|
}
|
||||||
@ -153,7 +154,7 @@ function deleteCloudFile() {
|
|||||||
|
|
||||||
async function getSteamCloudQuota() {
|
async function getSteamCloudQuota() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
greenworks.getCloudQuota(resolve, reject)
|
greenworks.getCloudQuota(resolve, reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,9 +167,9 @@ async function backupSteamDataToDisk(currentPlayerId) {
|
|||||||
if (previousPlayerId !== currentPlayerId) {
|
if (previousPlayerId !== currentPlayerId) {
|
||||||
const backupSave = await getSteamCloudSaveString();
|
const backupSave = await getSteamCloudSaveString();
|
||||||
const backupFile = path.join(app.getPath("userData"), "/saves/_backups", `${previousPlayerId}.json.gz`);
|
const backupFile = path.join(app.getPath("userData"), "/saves/_backups", `${previousPlayerId}.json.gz`);
|
||||||
const buffer = Buffer.from(backupSave, 'base64').toString('utf8');
|
const buffer = Buffer.from(backupSave, "base64").toString("utf8");
|
||||||
saveContent = await gzip(buffer);
|
saveContent = await gzip(buffer);
|
||||||
await fs.writeFile(backupFile, saveContent, 'utf8');
|
await fs.writeFile(backupFile, saveContent, "utf8");
|
||||||
log.debug(`Saved backup game to '${backupFile}`);
|
log.debug(`Saved backup game to '${backupFile}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,7 +220,9 @@ async function saveGameToDisk(window, saveData) {
|
|||||||
const remainingSpaceBytes = maxFolderSizeBytes - saveFolderSizeBytes;
|
const remainingSpaceBytes = maxFolderSizeBytes - saveFolderSizeBytes;
|
||||||
log.debug(`Folder Usage: ${saveFolderSizeBytes} bytes`);
|
log.debug(`Folder Usage: ${saveFolderSizeBytes} bytes`);
|
||||||
log.debug(`Folder Capacity: ${maxFolderSizeBytes} bytes`);
|
log.debug(`Folder Capacity: ${maxFolderSizeBytes} bytes`);
|
||||||
log.debug(`Remaining: ${remainingSpaceBytes} bytes (${(saveFolderSizeBytes / maxFolderSizeBytes * 100).toFixed(2)}% used)`)
|
log.debug(
|
||||||
|
`Remaining: ${remainingSpaceBytes} bytes (${((saveFolderSizeBytes / maxFolderSizeBytes) * 100).toFixed(2)}% used)`,
|
||||||
|
);
|
||||||
const shouldCompress = isSaveCompressionEnabled();
|
const shouldCompress = isSaveCompressionEnabled();
|
||||||
const fileName = saveData.fileName;
|
const fileName = saveData.fileName;
|
||||||
const file = path.join(currentFolder, fileName + (shouldCompress ? ".gz" : ""));
|
const file = path.join(currentFolder, fileName + (shouldCompress ? ".gz" : ""));
|
||||||
@ -227,10 +230,10 @@ async function saveGameToDisk(window, saveData) {
|
|||||||
let saveContent = saveData.save;
|
let saveContent = saveData.save;
|
||||||
if (shouldCompress) {
|
if (shouldCompress) {
|
||||||
// Let's decode the base64 string so GZIP is more efficient.
|
// Let's decode the base64 string so GZIP is more efficient.
|
||||||
const buffer = Buffer.from(saveContent, 'base64').toString('utf8');
|
const buffer = Buffer.from(saveContent, "base64").toString("utf8");
|
||||||
saveContent = await gzip(buffer);
|
saveContent = await gzip(buffer);
|
||||||
}
|
}
|
||||||
await fs.writeFile(file, saveContent, 'utf8');
|
await fs.writeFile(file, saveContent, "utf8");
|
||||||
log.debug(`Saved Game to '${file}'`);
|
log.debug(`Saved Game to '${file}'`);
|
||||||
log.debug(`Save Size: ${saveContent.length} bytes`);
|
log.debug(`Save Size: ${saveContent.length} bytes`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -240,7 +243,8 @@ async function saveGameToDisk(window, saveData) {
|
|||||||
const fileStats = await getDirFileStats(currentFolder);
|
const fileStats = await getDirFileStats(currentFolder);
|
||||||
const oldestFiles = fileStats
|
const oldestFiles = fileStats
|
||||||
.sort((a, b) => a.stat.mtime.getTime() - b.stat.mtime.getTime())
|
.sort((a, b) => a.stat.mtime.getTime() - b.stat.mtime.getTime())
|
||||||
.map(f => f.file).filter(f => f !== file);
|
.map((f) => f.file)
|
||||||
|
.filter((f) => f !== file);
|
||||||
|
|
||||||
while (saveFolderSizeBytes > maxFolderSizeBytes && oldestFiles.length > 0) {
|
while (saveFolderSizeBytes > maxFolderSizeBytes && oldestFiles.length > 0) {
|
||||||
const fileToRemove = oldestFiles.shift();
|
const fileToRemove = oldestFiles.shift();
|
||||||
@ -255,7 +259,12 @@ async function saveGameToDisk(window, saveData) {
|
|||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
|
saveFolderSizeBytes = await getFolderSizeInBytes(currentFolder);
|
||||||
log.debug(`Save Folder: ${saveFolderSizeBytes} bytes`);
|
log.debug(`Save Folder: ${saveFolderSizeBytes} bytes`);
|
||||||
log.debug(`Remaining: ${maxFolderSizeBytes - saveFolderSizeBytes} bytes (${(saveFolderSizeBytes / maxFolderSizeBytes * 100).toFixed(2)}% used)`)
|
log.debug(
|
||||||
|
`Remaining: ${maxFolderSizeBytes - saveFolderSizeBytes} bytes (${(
|
||||||
|
(saveFolderSizeBytes / maxFolderSizeBytes) *
|
||||||
|
100
|
||||||
|
).toFixed(2)}% used)`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
@ -271,13 +280,13 @@ async function loadLastFromDisk(window) {
|
|||||||
async function loadFileFromDisk(path) {
|
async function loadFileFromDisk(path) {
|
||||||
const buffer = await fs.readFile(path);
|
const buffer = await fs.readFile(path);
|
||||||
let content;
|
let content;
|
||||||
if (path.endsWith('.gz')) {
|
if (path.endsWith(".gz")) {
|
||||||
const uncompressedBuffer = await gunzip(buffer);
|
const uncompressedBuffer = await gunzip(buffer);
|
||||||
content = uncompressedBuffer.toString('base64');
|
content = uncompressedBuffer.toString("base64");
|
||||||
log.debug(`Uncompressed file content (new size: ${content.length} bytes)`);
|
log.debug(`Uncompressed file content (new size: ${content.length} bytes)`);
|
||||||
} else {
|
} else {
|
||||||
content = buffer.toString('utf8');
|
content = buffer.toString("utf8");
|
||||||
log.debug(`Loaded file with ${content.length} bytes`)
|
log.debug(`Loaded file with ${content.length} bytes`);
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -293,10 +302,10 @@ function getSaveInformation(window, save) {
|
|||||||
|
|
||||||
function getCurrentSave(window) {
|
function getCurrentSave(window) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
ipcMain.once('get-save-data-response', (event, data) => {
|
ipcMain.once("get-save-data-response", (event, data) => {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
window.webContents.send('get-save-data-request');
|
window.webContents.send("get-save-data-request");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,13 +331,12 @@ async function restoreIfNewerExists(window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saves = (await getAllSaves()).
|
const saves = (await getAllSaves()).sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
|
||||||
sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
|
|
||||||
if (saves.length > 0) {
|
if (saves.length > 0) {
|
||||||
disk.save = await loadFileFromDisk(saves[0].file);
|
disk.save = await loadFileFromDisk(saves[0].file);
|
||||||
disk.data = await getSaveInformation(window, disk.save);
|
disk.data = await getSaveInformation(window, disk.save);
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
log.error("Could not retrieve disk file");
|
log.error("Could not retrieve disk file");
|
||||||
log.debug(error);
|
log.debug(error);
|
||||||
}
|
}
|
||||||
@ -339,18 +347,17 @@ async function restoreIfNewerExists(window) {
|
|||||||
log.info("No data to import");
|
log.info("No data to import");
|
||||||
} else if (!steam.data) {
|
} else if (!steam.data) {
|
||||||
// We'll just compare using the lastSave field for now.
|
// We'll just compare using the lastSave field for now.
|
||||||
log.debug('Best potential save match: Disk');
|
log.debug("Best potential save match: Disk");
|
||||||
bestMatch = disk;
|
bestMatch = disk;
|
||||||
} else if (!disk.data) {
|
} else if (!disk.data) {
|
||||||
log.debug('Best potential save match: Steam Cloud');
|
log.debug("Best potential save match: Steam Cloud");
|
||||||
bestMatch = steam;
|
bestMatch = steam;
|
||||||
} else if ((steam.data.lastSave >= disk.data.lastSave)
|
} else if (steam.data.lastSave >= disk.data.lastSave || steam.data.playtime + lowPlaytime > disk.data.playtime) {
|
||||||
|| (steam.data.playtime + lowPlaytime > disk.data.playtime)) {
|
|
||||||
// We want to prioritze steam data if the playtime is very close
|
// We want to prioritze steam data if the playtime is very close
|
||||||
log.debug('Best potential save match: Steam Cloud');
|
log.debug("Best potential save match: Steam Cloud");
|
||||||
bestMatch = steam;
|
bestMatch = steam;
|
||||||
} else {
|
} else {
|
||||||
log.debug('Best potential save match: disk');
|
log.debug("Best potential save match: disk");
|
||||||
bestMatch = disk;
|
bestMatch = disk;
|
||||||
}
|
}
|
||||||
if (bestMatch) {
|
if (bestMatch) {
|
||||||
@ -360,7 +367,7 @@ async function restoreIfNewerExists(window) {
|
|||||||
log.silly(bestMatch.data);
|
log.silly(bestMatch.data);
|
||||||
await pushSaveGameForImport(window, bestMatch.save, true);
|
await pushSaveGameForImport(window, bestMatch.save, true);
|
||||||
return true;
|
return true;
|
||||||
} else if(bestMatch.data.playtime > currentData.playtime && currentData.playtime < lowPlaytime) {
|
} else if (bestMatch.data.playtime > currentData.playtime && currentData.playtime < lowPlaytime) {
|
||||||
log.info("Found older save, but with more playtime, and current less than 15 mins played");
|
log.info("Found older save, but with more playtime, and current less than 15 mins played");
|
||||||
log.silly(bestMatch.data);
|
log.silly(bestMatch.data);
|
||||||
await pushSaveGameForImport(window, bestMatch.save, true);
|
await pushSaveGameForImport(window, bestMatch.save, true);
|
||||||
@ -373,12 +380,24 @@ async function restoreIfNewerExists(window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getCurrentSave, getSaveInformation,
|
getCurrentSave,
|
||||||
restoreIfNewerExists, pushSaveGameForImport,
|
getSaveInformation,
|
||||||
pushGameSaveToSteamCloud, getSteamCloudSaveString, getSteamCloudQuota, deleteCloudFile,
|
restoreIfNewerExists,
|
||||||
saveGameToDisk, loadLastFromDisk, loadFileFromDisk,
|
pushSaveGameForImport,
|
||||||
getSaveFolder, prepareSaveFolders, getAllSaves,
|
pushGameSaveToSteamCloud,
|
||||||
isCloudEnabled, setCloudEnabledConfig,
|
getSteamCloudSaveString,
|
||||||
isAutosaveEnabled, setAutosaveConfig,
|
getSteamCloudQuota,
|
||||||
isSaveCompressionEnabled, setSaveCompressionConfig,
|
deleteCloudFile,
|
||||||
};
|
saveGameToDisk,
|
||||||
|
loadLastFromDisk,
|
||||||
|
loadFileFromDisk,
|
||||||
|
getSaveFolder,
|
||||||
|
prepareSaveFolders,
|
||||||
|
getAllSaves,
|
||||||
|
isCloudEnabled,
|
||||||
|
setCloudEnabledConfig,
|
||||||
|
isAutosaveEnabled,
|
||||||
|
setAutosaveConfig,
|
||||||
|
isSaveCompressionEnabled,
|
||||||
|
setSaveCompressionConfig,
|
||||||
|
};
|
||||||
|
@ -9,61 +9,61 @@ const Config = require("electron-config");
|
|||||||
const config = new Config();
|
const config = new Config();
|
||||||
|
|
||||||
function reloadAndKill(window, killScripts) {
|
function reloadAndKill(window, killScripts) {
|
||||||
const setStopProcessHandler = global.app_handlers.stopProcess
|
const setStopProcessHandler = global.app_handlers.stopProcess;
|
||||||
const createWindowHandler = global.app_handlers.createWindow;
|
const createWindowHandler = global.app_handlers.createWindow;
|
||||||
|
|
||||||
log.info('Reloading & Killing all scripts...');
|
log.info("Reloading & Killing all scripts...");
|
||||||
setStopProcessHandler(app, window, false);
|
setStopProcessHandler(app, window, false);
|
||||||
|
|
||||||
achievements.disableAchievementsInterval(window);
|
achievements.disableAchievementsInterval(window);
|
||||||
api.disable();
|
api.disable();
|
||||||
|
|
||||||
window.webContents.forcefullyCrashRenderer();
|
window.webContents.forcefullyCrashRenderer();
|
||||||
window.on('closed', () => {
|
window.on("closed", () => {
|
||||||
// Wait for window to be closed before opening the new one to prevent race conditions
|
// Wait for window to be closed before opening the new one to prevent race conditions
|
||||||
log.debug('Opening new window');
|
log.debug("Opening new window");
|
||||||
createWindowHandler(killScripts);
|
createWindowHandler(killScripts);
|
||||||
})
|
});
|
||||||
|
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function promptForReload(window) {
|
function promptForReload(window) {
|
||||||
detachUnresponsiveAppHandler(window);
|
detachUnresponsiveAppHandler(window);
|
||||||
dialog.showMessageBox({
|
dialog
|
||||||
type: 'error',
|
.showMessageBox({
|
||||||
title: 'Bitburner > Application Unresponsive',
|
type: "error",
|
||||||
message: 'The application is unresponsive, possibly due to an infinite loop in your scripts.',
|
title: "Bitburner > Application Unresponsive",
|
||||||
detail:' Did you forget a ns.sleep(x)?\n\n' +
|
message: "The application is unresponsive, possibly due to an infinite loop in your scripts.",
|
||||||
'The application will be restarted for you, do you want to kill all running scripts?',
|
detail:
|
||||||
buttons: ['Restart', 'Cancel'],
|
" Did you forget a ns.sleep(x)?\n\n" +
|
||||||
defaultId: 0,
|
"The application will be restarted for you, do you want to kill all running scripts?",
|
||||||
checkboxLabel: 'Kill all running scripts',
|
buttons: ["Restart", "Cancel"],
|
||||||
checkboxChecked: true,
|
defaultId: 0,
|
||||||
noLink: true,
|
checkboxLabel: "Kill all running scripts",
|
||||||
}).then(({response, checkboxChecked}) => {
|
checkboxChecked: true,
|
||||||
if (response === 0) {
|
noLink: true,
|
||||||
reloadAndKill(window, checkboxChecked);
|
})
|
||||||
} else {
|
.then(({ response, checkboxChecked }) => {
|
||||||
attachUnresponsiveAppHandler(window);
|
if (response === 0) {
|
||||||
}
|
reloadAndKill(window, checkboxChecked);
|
||||||
});
|
} else {
|
||||||
|
attachUnresponsiveAppHandler(window);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachUnresponsiveAppHandler(window) {
|
function attachUnresponsiveAppHandler(window) {
|
||||||
window.unresponsiveHandler = () => promptForReload(window);
|
window.unresponsiveHandler = () => promptForReload(window);
|
||||||
window.on('unresponsive', window.unresponsiveHandler);
|
window.on("unresponsive", window.unresponsiveHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
function detachUnresponsiveAppHandler(window) {
|
function detachUnresponsiveAppHandler(window) {
|
||||||
window.off('unresponsive', window.unresponsiveHandler);
|
window.off("unresponsive", window.unresponsiveHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showErrorBox(title, error) {
|
function showErrorBox(title, error) {
|
||||||
dialog.showErrorBox(
|
dialog.showErrorBox(title, `${error.name}\n\n${error.message}`);
|
||||||
title,
|
|
||||||
`${error.name}\n\n${error.message}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportSaveFromIndexedDb() {
|
function exportSaveFromIndexedDb() {
|
||||||
@ -71,15 +71,15 @@ function exportSaveFromIndexedDb() {
|
|||||||
const dbRequest = indexedDB.open("bitburnerSave");
|
const dbRequest = indexedDB.open("bitburnerSave");
|
||||||
dbRequest.onsuccess = () => {
|
dbRequest.onsuccess = () => {
|
||||||
const db = dbRequest.result;
|
const db = dbRequest.result;
|
||||||
const transaction = db.transaction(['savestring'], "readonly");
|
const transaction = db.transaction(["savestring"], "readonly");
|
||||||
const store = transaction.objectStore('savestring');
|
const store = transaction.objectStore("savestring");
|
||||||
const request = store.get('save');
|
const request = store.get("save");
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
const file = new Blob([request.result], {type: 'text/plain'});
|
const file = new Blob([request.result], { type: "text/plain" });
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'save.json';
|
a.download = "save.json";
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@ -87,24 +87,21 @@ function exportSaveFromIndexedDb() {
|
|||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
resolve();
|
resolve();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportSave(window) {
|
async function exportSave(window) {
|
||||||
await window.webContents
|
await window.webContents.executeJavaScript(`${exportSaveFromIndexedDb.toString()}; exportSaveFromIndexedDb();`, true);
|
||||||
.executeJavaScript(`${exportSaveFromIndexedDb.toString()}; exportSaveFromIndexedDb();`, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeTerminal(window, message, type = null) {
|
async function writeTerminal(window, message, type = null) {
|
||||||
await window.webContents
|
await window.webContents.executeJavaScript(`window.appNotifier.terminal("${message}", "${type}");`, true);
|
||||||
.executeJavaScript(`window.appNotifier.terminal("${message}", "${type}");`, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeToast(window, message, type = "info", duration = 2000) {
|
async function writeToast(window, message, type = "info", duration = 2000) {
|
||||||
await window.webContents
|
await window.webContents.executeJavaScript(`window.appNotifier.toast("${message}", "${type}", ${duration});`, true);
|
||||||
.executeJavaScript(`window.appNotifier.toast("${message}", "${type}", ${duration});`, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExternal(url) {
|
function openExternal(url) {
|
||||||
@ -113,7 +110,7 @@ function openExternal(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getZoomFactor() {
|
function getZoomFactor() {
|
||||||
const configZoom = config.get('zoom', 1);
|
const configZoom = config.get("zoom", 1);
|
||||||
return configZoom;
|
return configZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,14 +118,20 @@ function setZoomFactor(window, zoom = null) {
|
|||||||
if (zoom === null) {
|
if (zoom === null) {
|
||||||
zoom = 1;
|
zoom = 1;
|
||||||
} else {
|
} else {
|
||||||
config.set('zoom', zoom);
|
config.set("zoom", zoom);
|
||||||
}
|
}
|
||||||
window.webContents.setZoomFactor(zoom);
|
window.webContents.setZoomFactor(zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
reloadAndKill, showErrorBox, exportSave,
|
reloadAndKill,
|
||||||
attachUnresponsiveAppHandler, detachUnresponsiveAppHandler,
|
showErrorBox,
|
||||||
openExternal, writeTerminal, writeToast,
|
exportSave,
|
||||||
getZoomFactor, setZoomFactor,
|
attachUnresponsiveAppHandler,
|
||||||
}
|
detachUnresponsiveAppHandler,
|
||||||
|
openExternal,
|
||||||
|
writeTerminal,
|
||||||
|
writeToast,
|
||||||
|
getZoomFactor,
|
||||||
|
setZoomFactor,
|
||||||
|
};
|
||||||
|
@ -4,12 +4,11 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
|
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
|
||||||
},
|
},
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [".cypress", "node_modules", "dist"],
|
||||||
'.cypress', 'node_modules', 'dist',
|
|
||||||
],
|
|
||||||
testEnvironment: "jsdom",
|
testEnvironment: "jsdom",
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",
|
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||||
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
|
"<rootDir>/test/__mocks__/fileMock.js",
|
||||||
}
|
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
1
letter_to_fandom.com
Normal file
1
letter_to_fandom.com
Normal file
@ -0,0 +1 @@
|
|||||||
|
I want the wiki here https://bitburner.fandom.com/wiki/Bitburner_Wiki taken down please.
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
import { Achievement } from "./Achievements";
|
import { Achievement } from "./Achievements";
|
||||||
import { Settings } from "../Settings/Settings"
|
import { Settings } from "../Settings/Settings";
|
||||||
import { AchievementIcon } from "./AchievementIcon";
|
import { AchievementIcon } from "./AchievementIcon";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -13,43 +13,58 @@ interface IProps {
|
|||||||
cssFiltersLocked: string;
|
cssFiltersLocked: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AchievementEntry({ achievement, unlockedOn, cssFiltersUnlocked, cssFiltersLocked }: IProps): JSX.Element {
|
export function AchievementEntry({
|
||||||
|
achievement,
|
||||||
|
unlockedOn,
|
||||||
|
cssFiltersUnlocked,
|
||||||
|
cssFiltersLocked,
|
||||||
|
}: IProps): JSX.Element {
|
||||||
if (!achievement) return <></>;
|
if (!achievement) return <></>;
|
||||||
const isUnlocked = !!unlockedOn;
|
const isUnlocked = !!unlockedOn;
|
||||||
|
|
||||||
const mainColor = isUnlocked ? Settings.theme.primary : Settings.theme.secondarylight;
|
const mainColor = isUnlocked ? Settings.theme.primary : Settings.theme.secondarylight;
|
||||||
|
|
||||||
let achievedOn = '';
|
let achievedOn = "";
|
||||||
if (unlockedOn) {
|
if (unlockedOn) {
|
||||||
achievedOn = new Date(unlockedOn).toLocaleString();
|
achievedOn = new Date(unlockedOn).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box
|
||||||
border: `1px solid ${Settings.theme.well}`, mb: 2
|
sx={{
|
||||||
}}>
|
border: `1px solid ${Settings.theme.well}`,
|
||||||
<Box sx={{
|
mb: 2,
|
||||||
display: 'flex',
|
}}
|
||||||
flexDirection: 'row',
|
>
|
||||||
flexWrap: 'wrap',
|
<Box
|
||||||
}}>
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<AchievementIcon
|
<AchievementIcon
|
||||||
achievement={achievement} unlocked={isUnlocked} size="72px"
|
achievement={achievement}
|
||||||
colorFilters={isUnlocked ? cssFiltersUnlocked: cssFiltersLocked} />
|
unlocked={isUnlocked}
|
||||||
<Box sx={{
|
size="72px"
|
||||||
display: 'flex',
|
colorFilters={isUnlocked ? cssFiltersUnlocked : cssFiltersLocked}
|
||||||
flexDirection: 'column',
|
/>
|
||||||
justifyContent: 'center',
|
<Box
|
||||||
px: 1
|
sx={{
|
||||||
}}>
|
display: "flex",
|
||||||
<Typography variant="h6" sx={{ color: mainColor}}>
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
px: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" sx={{ color: mainColor }}>
|
||||||
{achievement.Name}
|
{achievement.Name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ maxWidth: '500px', color: mainColor}}>
|
<Typography variant="body2" sx={{ maxWidth: "500px", color: mainColor }}>
|
||||||
{achievement.Description}
|
{achievement.Description}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isUnlocked && (
|
{isUnlocked && (
|
||||||
<Typography variant="caption" sx={{ fontSize: '12px', color: Settings.theme.primarydark }}>
|
<Typography variant="caption" sx={{ fontSize: "12px", color: Settings.theme.primarydark }}>
|
||||||
Acquired on {achievedOn}
|
Acquired on {achievedOn}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
@ -3,7 +3,7 @@ import React, { useState } from "react";
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
import { Achievement } from "./Achievements";
|
import { Achievement } from "./Achievements";
|
||||||
import { Settings } from "../Settings/Settings"
|
import { Settings } from "../Settings/Settings";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
achievement: Achievement;
|
achievement: Achievement;
|
||||||
@ -16,20 +16,23 @@ export function AchievementIcon({ achievement, unlocked, colorFilters, size }: I
|
|||||||
const [imgLoaded, setImgLoaded] = useState(false);
|
const [imgLoaded, setImgLoaded] = useState(false);
|
||||||
const mainColor = unlocked ? Settings.theme.primarydark : Settings.theme.secondarydark;
|
const mainColor = unlocked ? Settings.theme.primarydark : Settings.theme.secondarydark;
|
||||||
|
|
||||||
if (!achievement.Icon) return (<></>);
|
if (!achievement.Icon) return <></>;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: `1px solid ${mainColor}`,
|
border: `1px solid ${mainColor}`,
|
||||||
width: size, height: size,
|
width: size,
|
||||||
|
height: size,
|
||||||
m: 1,
|
m: 1,
|
||||||
visibility: imgLoaded ? 'visible' : 'hidden'
|
visibility: imgLoaded ? "visible" : "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={`dist/icons/achievements/${encodeURI(achievement.Icon)}.svg`}
|
<img
|
||||||
|
src={`dist/icons/achievements/${encodeURI(achievement.Icon)}.svg`}
|
||||||
style={{ filter: colorFilters, width: size, height: size }}
|
style={{ filter: colorFilters, width: size, height: size }}
|
||||||
onLoad={() => setImgLoaded(true)}
|
onLoad={() => setImgLoaded(true)}
|
||||||
alt={achievement.Name} />
|
alt={achievement.Name}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import React from "react";
|
|||||||
import { Accordion, AccordionSummary, AccordionDetails, Box, Typography } from "@mui/material";
|
import { Accordion, AccordionSummary, AccordionDetails, Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
import { AchievementEntry } from "./AchievementEntry";
|
import { AchievementEntry } from "./AchievementEntry";
|
||||||
import { Achievement, PlayerAchievement} from "./Achievements";
|
import { Achievement, PlayerAchievement } from "./Achievements";
|
||||||
import { Settings } from "../Settings/Settings"
|
import { Settings } from "../Settings/Settings";
|
||||||
import { getFiltersFromHex } from "../ThirdParty/colorUtils";
|
import { getFiltersFromHex } from "../ThirdParty/colorUtils";
|
||||||
import { CorruptableText } from "../ui/React/CorruptableText";
|
import { CorruptableText } from "../ui/React/CorruptableText";
|
||||||
|
|
||||||
@ -18,32 +18,39 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
|||||||
const cssPrimary = getFiltersFromHex(Settings.theme.primary);
|
const cssPrimary = getFiltersFromHex(Settings.theme.primary);
|
||||||
const cssSecondary = getFiltersFromHex(Settings.theme.secondary);
|
const cssSecondary = getFiltersFromHex(Settings.theme.secondary);
|
||||||
|
|
||||||
const data = achievements.map(achievement => ({
|
const data = achievements
|
||||||
achievement,
|
.map((achievement) => ({
|
||||||
unlockedOn: playerAchievements.find(playerAchievement => playerAchievement.ID === achievement.ID)?.unlockedOn,
|
achievement,
|
||||||
})).sort((a, b) => (b.unlockedOn ?? 0) - (a.unlockedOn ?? 0));
|
unlockedOn: playerAchievements.find((playerAchievement) => playerAchievement.ID === achievement.ID)?.unlockedOn,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (b.unlockedOn ?? 0) - (a.unlockedOn ?? 0));
|
||||||
|
|
||||||
const unlocked = data.filter(entry => entry.unlockedOn);
|
const unlocked = data.filter((entry) => entry.unlockedOn);
|
||||||
|
|
||||||
// Hidden achievements
|
// Hidden achievements
|
||||||
const secret = data.filter(entry => !entry.unlockedOn && entry.achievement.Secret)
|
const secret = data.filter((entry) => !entry.unlockedOn && entry.achievement.Secret);
|
||||||
|
|
||||||
// Locked behind locked content (bitnode x)
|
// Locked behind locked content (bitnode x)
|
||||||
const unavailable = data.filter(entry => !entry.unlockedOn && !entry.achievement.Secret && entry.achievement.Visible && !entry.achievement.Visible());
|
const unavailable = data.filter(
|
||||||
|
(entry) =>
|
||||||
|
!entry.unlockedOn && !entry.achievement.Secret && entry.achievement.Visible && !entry.achievement.Visible(),
|
||||||
|
);
|
||||||
|
|
||||||
// Remaining achievements
|
// Remaining achievements
|
||||||
const locked = data
|
const locked = data
|
||||||
.filter(entry => !unlocked.map(u => u.achievement.ID).includes(entry.achievement.ID))
|
.filter((entry) => !unlocked.map((u) => u.achievement.ID).includes(entry.achievement.ID))
|
||||||
.filter(entry => !secret.map(u => u.achievement.ID).includes(entry.achievement.ID))
|
.filter((entry) => !secret.map((u) => u.achievement.ID).includes(entry.achievement.ID))
|
||||||
.filter(entry => !unavailable.map(u => u.achievement.ID).includes(entry.achievement.ID));
|
.filter((entry) => !unavailable.map((u) => u.achievement.ID).includes(entry.achievement.ID));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ pr: 18, my: 2 }}>
|
<Box sx={{ pr: 18, my: 2 }}>
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexDirection: 'column',
|
display: "flex",
|
||||||
flexWrap: 'wrap',
|
flexDirection: "column",
|
||||||
}}>
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{unlocked.length > 0 && (
|
{unlocked.length > 0 && (
|
||||||
<Accordion defaultExpanded disableGutters square>
|
<Accordion defaultExpanded disableGutters square>
|
||||||
<AccordionSummary>
|
<AccordionSummary>
|
||||||
@ -52,12 +59,14 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
|||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ pt: 2 }}>
|
<AccordionDetails sx={{ pt: 2 }}>
|
||||||
{unlocked.map(item => (
|
{unlocked.map((item) => (
|
||||||
<AchievementEntry key={`unlocked_${item.achievement.ID}`}
|
<AchievementEntry
|
||||||
|
key={`unlocked_${item.achievement.ID}`}
|
||||||
achievement={item.achievement}
|
achievement={item.achievement}
|
||||||
unlockedOn={item.unlockedOn}
|
unlockedOn={item.unlockedOn}
|
||||||
cssFiltersUnlocked={cssPrimary}
|
cssFiltersUnlocked={cssPrimary}
|
||||||
cssFiltersLocked={cssSecondary} />
|
cssFiltersLocked={cssSecondary}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -71,11 +80,13 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
|||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ pt: 2 }}>
|
<AccordionDetails sx={{ pt: 2 }}>
|
||||||
{locked.map(item => (
|
{locked.map((item) => (
|
||||||
<AchievementEntry key={`locked_${item.achievement.ID}`}
|
<AchievementEntry
|
||||||
|
key={`locked_${item.achievement.ID}`}
|
||||||
achievement={item.achievement}
|
achievement={item.achievement}
|
||||||
cssFiltersUnlocked={cssPrimary}
|
cssFiltersUnlocked={cssPrimary}
|
||||||
cssFiltersLocked={cssSecondary} />
|
cssFiltersLocked={cssSecondary}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -89,7 +100,7 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
|||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Typography sx={{ mt: 1 }}>
|
<Typography sx={{ mt: 1 }}>
|
||||||
{unavailable.length} additional achievements hidden behind content you don't have access to.
|
{unavailable.length} additional achievements hidden behind content you don't have access to.
|
||||||
</Typography>
|
</Typography>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
@ -105,7 +116,7 @@ export function AchievementList({ achievements, playerAchievements }: IProps): J
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Typography color="secondary" sx={{ mt: 1 }}>
|
<Typography color="secondary" sx={{ mt: 1 }}>
|
||||||
{secret.map(item => (
|
{secret.map((item) => (
|
||||||
<span key={`secret_${item.achievement.ID}`}>
|
<span key={`secret_${item.achievement.ID}`}>
|
||||||
<CorruptableText content={item.achievement.ID}></CorruptableText>
|
<CorruptableText content={item.achievement.ID}></CorruptableText>
|
||||||
<br />
|
<br />
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# Adding Achievements
|
# Adding Achievements
|
||||||
|
|
||||||
* Add a .svg in `./assets/Steam/achievements/real`
|
- Add a .svg in `./assets/Steam/achievements/real`
|
||||||
* Create the achievement in Steam Dev Portal
|
- Create the achievement in Steam Dev Portal
|
||||||
* Run `sh ./assets/Steam/achievements/pack-for-web.sh`
|
- Run `sh ./assets/Steam/achievements/pack-for-web.sh`
|
||||||
* Run `node ./tools/fetch-steam-achievements-data DEVKEYHERE`
|
- Run `node ./tools/fetch-steam-achievements-data DEVKEYHERE`
|
||||||
* Get your key here: https://steamcommunity.com/dev/apikey
|
- Get your key here: https://steamcommunity.com/dev/apikey
|
||||||
* Add an entry in `./src/Achievements/Achievements.ts` -> achievements
|
- Add an entry in `./src/Achievements/Achievements.ts` -> achievements
|
||||||
* Commit `./dist/icons/achievements` & `./src/Achievements/AchievementData.json`
|
- Commit `./dist/icons/achievements` & `./src/Achievements/AchievementData.json`
|
||||||
|
1939
src/Augmentation/AugmentationCreator.tsx
Normal file
1939
src/Augmentation/AugmentationCreator.tsx
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,234 +1,121 @@
|
|||||||
export const AugmentationNames: {
|
export enum AugmentationNames {
|
||||||
Targeting1: string;
|
Targeting1 = "Augmented Targeting I",
|
||||||
Targeting2: string;
|
Targeting2 = "Augmented Targeting II",
|
||||||
Targeting3: string;
|
Targeting3 = "Augmented Targeting III",
|
||||||
SyntheticHeart: string;
|
SyntheticHeart = "Synthetic Heart",
|
||||||
SynfibrilMuscle: string;
|
SynfibrilMuscle = "Synfibril Muscle",
|
||||||
CombatRib1: string;
|
CombatRib1 = "Combat Rib I",
|
||||||
CombatRib2: string;
|
CombatRib2 = "Combat Rib II",
|
||||||
CombatRib3: string;
|
CombatRib3 = "Combat Rib III",
|
||||||
NanofiberWeave: string;
|
NanofiberWeave = "Nanofiber Weave",
|
||||||
SubdermalArmor: string;
|
SubdermalArmor = "NEMEAN Subdermal Weave",
|
||||||
WiredReflexes: string;
|
WiredReflexes = "Wired Reflexes",
|
||||||
GrapheneBoneLacings: string;
|
GrapheneBoneLacings = "Graphene Bone Lacings",
|
||||||
BionicSpine: string;
|
BionicSpine = "Bionic Spine",
|
||||||
GrapheneBionicSpine: string;
|
GrapheneBionicSpine = "Graphene Bionic Spine Upgrade",
|
||||||
BionicLegs: string;
|
BionicLegs = "Bionic Legs",
|
||||||
GrapheneBionicLegs: string;
|
GrapheneBionicLegs = "Graphene Bionic Legs Upgrade",
|
||||||
SpeechProcessor: string;
|
SpeechProcessor = "Speech Processor Implant",
|
||||||
TITN41Injection: string;
|
TITN41Injection = "TITN-41 Gene-Modification Injection",
|
||||||
EnhancedSocialInteractionImplant: string;
|
EnhancedSocialInteractionImplant = "Enhanced Social Interaction Implant",
|
||||||
BitWire: string;
|
BitWire = "BitWire",
|
||||||
ArtificialBioNeuralNetwork: string;
|
ArtificialBioNeuralNetwork = "Artificial Bio-neural Network Implant",
|
||||||
ArtificialSynapticPotentiation: string;
|
ArtificialSynapticPotentiation = "Artificial Synaptic Potentiation",
|
||||||
EnhancedMyelinSheathing: string;
|
EnhancedMyelinSheathing = "Enhanced Myelin Sheathing",
|
||||||
SynapticEnhancement: string;
|
SynapticEnhancement = "Synaptic Enhancement Implant",
|
||||||
NeuralRetentionEnhancement: string;
|
NeuralRetentionEnhancement = "Neural-Retention Enhancement",
|
||||||
DataJack: string;
|
DataJack = "DataJack",
|
||||||
ENM: string;
|
ENM = "Embedded Netburner Module",
|
||||||
ENMCore: string;
|
ENMCore = "Embedded Netburner Module Core Implant",
|
||||||
ENMCoreV2: string;
|
ENMCoreV2 = "Embedded Netburner Module Core V2 Upgrade",
|
||||||
ENMCoreV3: string;
|
ENMCoreV3 = "Embedded Netburner Module Core V3 Upgrade",
|
||||||
ENMAnalyzeEngine: string;
|
ENMAnalyzeEngine = "Embedded Netburner Module Analyze Engine",
|
||||||
ENMDMA: string;
|
ENMDMA = "Embedded Netburner Module Direct Memory Access Upgrade",
|
||||||
Neuralstimulator: string;
|
Neuralstimulator = "Neuralstimulator",
|
||||||
NeuralAccelerator: string;
|
NeuralAccelerator = "Neural Accelerator",
|
||||||
CranialSignalProcessorsG1: string;
|
CranialSignalProcessorsG1 = "Cranial Signal Processors - Gen I",
|
||||||
CranialSignalProcessorsG2: string;
|
CranialSignalProcessorsG2 = "Cranial Signal Processors - Gen II",
|
||||||
CranialSignalProcessorsG3: string;
|
CranialSignalProcessorsG3 = "Cranial Signal Processors - Gen III",
|
||||||
CranialSignalProcessorsG4: string;
|
CranialSignalProcessorsG4 = "Cranial Signal Processors - Gen IV",
|
||||||
CranialSignalProcessorsG5: string;
|
CranialSignalProcessorsG5 = "Cranial Signal Processors - Gen V",
|
||||||
NeuronalDensification: string;
|
NeuronalDensification = "Neuronal Densification",
|
||||||
NeuroreceptorManager: string;
|
NeuroreceptorManager = "Neuroreceptor Management Implant",
|
||||||
NuoptimalInjectorImplant: string;
|
NuoptimalInjectorImplant = "Nuoptimal Nootropic Injector Implant",
|
||||||
SpeechEnhancement: string;
|
SpeechEnhancement = "Speech Enhancement",
|
||||||
FocusWire: string;
|
FocusWire = "FocusWire",
|
||||||
PCDNI: string;
|
PCDNI = "PC Direct-Neural Interface",
|
||||||
PCDNIOptimizer: string;
|
PCDNIOptimizer = "PC Direct-Neural Interface Optimization Submodule",
|
||||||
PCDNINeuralNetwork: string;
|
PCDNINeuralNetwork = "PC Direct-Neural Interface NeuroNet Injector",
|
||||||
PCMatrix: string;
|
PCMatrix = "PCMatrix",
|
||||||
ADRPheromone1: string;
|
ADRPheromone1 = "ADR-V1 Pheromone Gene",
|
||||||
ADRPheromone2: string;
|
ADRPheromone2 = "ADR-V2 Pheromone Gene",
|
||||||
ShadowsSimulacrum: string;
|
ShadowsSimulacrum = "The Shadow's Simulacrum",
|
||||||
HacknetNodeCPUUpload: string;
|
HacknetNodeCPUUpload = "Hacknet Node CPU Architecture Neural-Upload",
|
||||||
HacknetNodeCacheUpload: string;
|
HacknetNodeCacheUpload = "Hacknet Node Cache Architecture Neural-Upload",
|
||||||
HacknetNodeNICUpload: string;
|
HacknetNodeNICUpload = "Hacknet Node NIC Architecture Neural-Upload",
|
||||||
HacknetNodeKernelDNI: string;
|
HacknetNodeKernelDNI = "Hacknet Node Kernel Direct-Neural Interface",
|
||||||
HacknetNodeCoreDNI: string;
|
HacknetNodeCoreDNI = "Hacknet Node Core Direct-Neural Interface",
|
||||||
NeuroFluxGovernor: string;
|
NeuroFluxGovernor = "NeuroFlux Governor",
|
||||||
Neurotrainer1: string;
|
Neurotrainer1 = "Neurotrainer I",
|
||||||
Neurotrainer2: string;
|
Neurotrainer2 = "Neurotrainer II",
|
||||||
Neurotrainer3: string;
|
Neurotrainer3 = "Neurotrainer III",
|
||||||
Hypersight: string;
|
Hypersight = "HyperSight Corneal Implant",
|
||||||
LuminCloaking1: string;
|
LuminCloaking1 = "LuminCloaking-V1 Skin Implant",
|
||||||
LuminCloaking2: string;
|
LuminCloaking2 = "LuminCloaking-V2 Skin Implant",
|
||||||
HemoRecirculator: string;
|
HemoRecirculator = "HemoRecirculator",
|
||||||
SmartSonar: string;
|
SmartSonar = "SmartSonar Implant",
|
||||||
PowerRecirculator: string;
|
PowerRecirculator = "Power Recirculation Core",
|
||||||
QLink: string;
|
QLink = "QLink",
|
||||||
TheRedPill: string;
|
TheRedPill = "The Red Pill",
|
||||||
SPTN97: string;
|
SPTN97 = "SPTN-97 Gene Modification",
|
||||||
HiveMind: string;
|
HiveMind = "ECorp HVMind Implant",
|
||||||
CordiARCReactor: string;
|
CordiARCReactor = "CordiARC Fusion Reactor",
|
||||||
SmartJaw: string;
|
SmartJaw = "SmartJaw",
|
||||||
Neotra: string;
|
Neotra = "Neotra",
|
||||||
Xanipher: string;
|
Xanipher = "Xanipher",
|
||||||
nextSENS: string;
|
nextSENS = "nextSENS Gene Modification",
|
||||||
OmniTekInfoLoad: string;
|
OmniTekInfoLoad = "OmniTek InfoLoad",
|
||||||
PhotosyntheticCells: string;
|
PhotosyntheticCells = "Photosynthetic Cells",
|
||||||
Neurolink: string;
|
Neurolink = "BitRunners Neurolink",
|
||||||
TheBlackHand: string;
|
TheBlackHand = "The Black Hand",
|
||||||
UnstableCircadianModulator: string;
|
UnstableCircadianModulator = "Unstable Circadian Modulator",
|
||||||
CRTX42AA: string;
|
CRTX42AA = "CRTX42-AA Gene Modification",
|
||||||
Neuregen: string;
|
Neuregen = "Neuregen Gene Modification",
|
||||||
CashRoot: string;
|
CashRoot = "CashRoot Starter Kit",
|
||||||
NutriGen: string;
|
NutriGen = "NutriGen Implant",
|
||||||
INFRARet: string;
|
INFRARet = "INFRARET Enhancement",
|
||||||
DermaForce: string;
|
DermaForce = "DermaForce Particle Barrier",
|
||||||
GrapheneBrachiBlades: string;
|
GrapheneBrachiBlades = "Graphene BrachiBlades Upgrade",
|
||||||
GrapheneBionicArms: string;
|
GrapheneBionicArms = "Graphene Bionic Arms Upgrade",
|
||||||
BrachiBlades: string;
|
BrachiBlades = "BrachiBlades",
|
||||||
BionicArms: string;
|
BionicArms = "Bionic Arms",
|
||||||
SNA: string;
|
SNA = "Social Negotiation Assistant (S.N.A)",
|
||||||
HydroflameLeftArm: string;
|
HydroflameLeftArm = "Hydroflame Left Arm",
|
||||||
EsperEyewear: string;
|
EsperEyewear = "EsperTech Bladeburner Eyewear",
|
||||||
EMS4Recombination: string;
|
EMS4Recombination = "EMS-4 Recombination",
|
||||||
OrionShoulder: string;
|
OrionShoulder = "ORION-MKIV Shoulder",
|
||||||
HyperionV1: string;
|
HyperionV1 = "Hyperion Plasma Cannon V1",
|
||||||
HyperionV2: string;
|
HyperionV2 = "Hyperion Plasma Cannon V2",
|
||||||
GolemSerum: string;
|
GolemSerum = "GOLEM Serum",
|
||||||
VangelisVirus: string;
|
VangelisVirus = "Vangelis Virus",
|
||||||
VangelisVirus3: string;
|
VangelisVirus3 = "Vangelis Virus 3.0",
|
||||||
INTERLINKED: string;
|
INTERLINKED = "I.N.T.E.R.L.I.N.K.E.D",
|
||||||
BladeRunner: string;
|
BladeRunner = "Blade's Runners",
|
||||||
BladeArmor: string;
|
BladeArmor = "BLADE-51b Tesla Armor",
|
||||||
BladeArmorPowerCells: string;
|
BladeArmorPowerCells = "BLADE-51b Tesla Armor: Power Cells Upgrade",
|
||||||
BladeArmorEnergyShielding: string;
|
BladeArmorEnergyShielding = "BLADE-51b Tesla Armor: Energy Shielding Upgrade",
|
||||||
BladeArmorUnibeam: string;
|
BladeArmorUnibeam = "BLADE-51b Tesla Armor: Unibeam Upgrade",
|
||||||
BladeArmorOmnibeam: string;
|
BladeArmorOmnibeam = "BLADE-51b Tesla Armor: Omnibeam Upgrade",
|
||||||
BladeArmorIPU: string;
|
BladeArmorIPU = "BLADE-51b Tesla Armor: IPU Upgrade",
|
||||||
BladesSimulacrum: string;
|
BladesSimulacrum = "The Blade's Simulacrum",
|
||||||
StaneksGift1: string;
|
|
||||||
StaneksGift2: string;
|
|
||||||
StaneksGift3: string;
|
|
||||||
} = {
|
|
||||||
Targeting1: "Augmented Targeting I",
|
|
||||||
Targeting2: "Augmented Targeting II",
|
|
||||||
Targeting3: "Augmented Targeting III",
|
|
||||||
SyntheticHeart: "Synthetic Heart",
|
|
||||||
SynfibrilMuscle: "Synfibril Muscle",
|
|
||||||
CombatRib1: "Combat Rib I",
|
|
||||||
CombatRib2: "Combat Rib II",
|
|
||||||
CombatRib3: "Combat Rib III",
|
|
||||||
NanofiberWeave: "Nanofiber Weave",
|
|
||||||
SubdermalArmor: "NEMEAN Subdermal Weave",
|
|
||||||
WiredReflexes: "Wired Reflexes",
|
|
||||||
GrapheneBoneLacings: "Graphene Bone Lacings",
|
|
||||||
BionicSpine: "Bionic Spine",
|
|
||||||
GrapheneBionicSpine: "Graphene Bionic Spine Upgrade",
|
|
||||||
BionicLegs: "Bionic Legs",
|
|
||||||
GrapheneBionicLegs: "Graphene Bionic Legs Upgrade",
|
|
||||||
SpeechProcessor: "Speech Processor Implant",
|
|
||||||
TITN41Injection: "TITN-41 Gene-Modification Injection",
|
|
||||||
EnhancedSocialInteractionImplant: "Enhanced Social Interaction Implant",
|
|
||||||
BitWire: "BitWire",
|
|
||||||
ArtificialBioNeuralNetwork: "Artificial Bio-neural Network Implant",
|
|
||||||
ArtificialSynapticPotentiation: "Artificial Synaptic Potentiation",
|
|
||||||
EnhancedMyelinSheathing: "Enhanced Myelin Sheathing",
|
|
||||||
SynapticEnhancement: "Synaptic Enhancement Implant",
|
|
||||||
NeuralRetentionEnhancement: "Neural-Retention Enhancement",
|
|
||||||
DataJack: "DataJack",
|
|
||||||
ENM: "Embedded Netburner Module",
|
|
||||||
ENMCore: "Embedded Netburner Module Core Implant",
|
|
||||||
ENMCoreV2: "Embedded Netburner Module Core V2 Upgrade",
|
|
||||||
ENMCoreV3: "Embedded Netburner Module Core V3 Upgrade",
|
|
||||||
ENMAnalyzeEngine: "Embedded Netburner Module Analyze Engine",
|
|
||||||
ENMDMA: "Embedded Netburner Module Direct Memory Access Upgrade",
|
|
||||||
Neuralstimulator: "Neuralstimulator",
|
|
||||||
NeuralAccelerator: "Neural Accelerator",
|
|
||||||
CranialSignalProcessorsG1: "Cranial Signal Processors - Gen I",
|
|
||||||
CranialSignalProcessorsG2: "Cranial Signal Processors - Gen II",
|
|
||||||
CranialSignalProcessorsG3: "Cranial Signal Processors - Gen III",
|
|
||||||
CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV",
|
|
||||||
CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V",
|
|
||||||
NeuronalDensification: "Neuronal Densification",
|
|
||||||
NeuroreceptorManager: "Neuroreceptor Management Implant",
|
|
||||||
NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant",
|
|
||||||
SpeechEnhancement: "Speech Enhancement",
|
|
||||||
FocusWire: "FocusWire",
|
|
||||||
PCDNI: "PC Direct-Neural Interface",
|
|
||||||
PCDNIOptimizer: "PC Direct-Neural Interface Optimization Submodule",
|
|
||||||
PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector",
|
|
||||||
PCMatrix: "PCMatrix",
|
|
||||||
ADRPheromone1: "ADR-V1 Pheromone Gene",
|
|
||||||
ADRPheromone2: "ADR-V2 Pheromone Gene",
|
|
||||||
ShadowsSimulacrum: "The Shadow's Simulacrum",
|
|
||||||
HacknetNodeCPUUpload: "Hacknet Node CPU Architecture Neural-Upload",
|
|
||||||
HacknetNodeCacheUpload: "Hacknet Node Cache Architecture Neural-Upload",
|
|
||||||
HacknetNodeNICUpload: "Hacknet Node NIC Architecture Neural-Upload",
|
|
||||||
HacknetNodeKernelDNI: "Hacknet Node Kernel Direct-Neural Interface",
|
|
||||||
HacknetNodeCoreDNI: "Hacknet Node Core Direct-Neural Interface",
|
|
||||||
NeuroFluxGovernor: "NeuroFlux Governor",
|
|
||||||
Neurotrainer1: "Neurotrainer I",
|
|
||||||
Neurotrainer2: "Neurotrainer II",
|
|
||||||
Neurotrainer3: "Neurotrainer III",
|
|
||||||
Hypersight: "HyperSight Corneal Implant",
|
|
||||||
LuminCloaking1: "LuminCloaking-V1 Skin Implant",
|
|
||||||
LuminCloaking2: "LuminCloaking-V2 Skin Implant",
|
|
||||||
HemoRecirculator: "HemoRecirculator",
|
|
||||||
SmartSonar: "SmartSonar Implant",
|
|
||||||
PowerRecirculator: "Power Recirculation Core",
|
|
||||||
QLink: "QLink",
|
|
||||||
TheRedPill: "The Red Pill",
|
|
||||||
SPTN97: "SPTN-97 Gene Modification",
|
|
||||||
HiveMind: "ECorp HVMind Implant",
|
|
||||||
CordiARCReactor: "CordiARC Fusion Reactor",
|
|
||||||
SmartJaw: "SmartJaw",
|
|
||||||
Neotra: "Neotra",
|
|
||||||
Xanipher: "Xanipher",
|
|
||||||
nextSENS: "nextSENS Gene Modification",
|
|
||||||
OmniTekInfoLoad: "OmniTek InfoLoad",
|
|
||||||
PhotosyntheticCells: "Photosynthetic Cells",
|
|
||||||
Neurolink: "BitRunners Neurolink",
|
|
||||||
TheBlackHand: "The Black Hand",
|
|
||||||
UnstableCircadianModulator: "Unstable Circadian Modulator",
|
|
||||||
CRTX42AA: "CRTX42-AA Gene Modification",
|
|
||||||
Neuregen: "Neuregen Gene Modification",
|
|
||||||
CashRoot: "CashRoot Starter Kit",
|
|
||||||
NutriGen: "NutriGen Implant",
|
|
||||||
INFRARet: "INFRARET Enhancement",
|
|
||||||
DermaForce: "DermaForce Particle Barrier",
|
|
||||||
GrapheneBrachiBlades: "Graphene BrachiBlades Upgrade",
|
|
||||||
GrapheneBionicArms: "Graphene Bionic Arms Upgrade",
|
|
||||||
BrachiBlades: "BrachiBlades",
|
|
||||||
BionicArms: "Bionic Arms",
|
|
||||||
SNA: "Social Negotiation Assistant (S.N.A)",
|
|
||||||
HydroflameLeftArm: "Hydroflame Left Arm",
|
|
||||||
EsperEyewear: "EsperTech Bladeburner Eyewear",
|
|
||||||
EMS4Recombination: "EMS-4 Recombination",
|
|
||||||
OrionShoulder: "ORION-MKIV Shoulder",
|
|
||||||
HyperionV1: "Hyperion Plasma Cannon V1",
|
|
||||||
HyperionV2: "Hyperion Plasma Cannon V2",
|
|
||||||
GolemSerum: "GOLEM Serum",
|
|
||||||
VangelisVirus: "Vangelis Virus",
|
|
||||||
VangelisVirus3: "Vangelis Virus 3.0",
|
|
||||||
INTERLINKED: "I.N.T.E.R.L.I.N.K.E.D",
|
|
||||||
BladeRunner: "Blade's Runners",
|
|
||||||
BladeArmor: "BLADE-51b Tesla Armor",
|
|
||||||
BladeArmorPowerCells: "BLADE-51b Tesla Armor: Power Cells Upgrade",
|
|
||||||
BladeArmorEnergyShielding: "BLADE-51b Tesla Armor: Energy Shielding Upgrade",
|
|
||||||
BladeArmorUnibeam: "BLADE-51b Tesla Armor: Unibeam Upgrade",
|
|
||||||
BladeArmorOmnibeam: "BLADE-51b Tesla Armor: Omnibeam Upgrade",
|
|
||||||
BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade",
|
|
||||||
BladesSimulacrum: "The Blade's Simulacrum",
|
|
||||||
|
|
||||||
StaneksGift1: "Stanek's Gift - Genesis",
|
StaneksGift1 = "Stanek's Gift - Genesis",
|
||||||
StaneksGift2: "Stanek's Gift - Awakening",
|
StaneksGift2 = "Stanek's Gift - Awakening",
|
||||||
StaneksGift3: "Stanek's Gift - Serenity",
|
StaneksGift3 = "Stanek's Gift - Serenity",
|
||||||
|
|
||||||
//Wasteland Augs
|
//Wasteland Augs
|
||||||
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
|
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
|
||||||
//PepBoyForceField Generates plasma force fields
|
//PepBoyForceField Generates plasma force fields
|
||||||
//PepBoyBlasts Generate high density plasma concussive blasts
|
//PepBoyBlasts Generate high density plasma concussive blasts
|
||||||
//PepBoyDataStorage STore more data on pep boy,
|
//PepBoyDataStorage STore more data on pep boy,
|
||||||
};
|
}
|
||||||
|
@ -82,7 +82,9 @@ BitNodes["BitNode2"] = new BitNode(
|
|||||||
<br />
|
<br />
|
||||||
The amount of money gained from crimes and Infiltration is tripled
|
The amount of money gained from crimes and Infiltration is tripled
|
||||||
<br />
|
<br />
|
||||||
Certain Factions ({FactionNames.SlumSnakes}, {FactionNames.Tetrads}, {FactionNames.TheSyndicate}, {FactionNames.TheDarkArmy}, {FactionNames.SpeakersForTheDead}, {FactionNames.NiteSec}, {FactionNames.TheBlackHand}) give the player the ability to form and manage their own gangs. These gangs will earn the player money and
|
Certain Factions ({FactionNames.SlumSnakes}, {FactionNames.Tetrads}, {FactionNames.TheSyndicate},{" "}
|
||||||
|
{FactionNames.TheDarkArmy}, {FactionNames.SpeakersForTheDead}, {FactionNames.NiteSec}, {FactionNames.TheBlackHand}
|
||||||
|
) give the player the ability to form and manage their own gangs. These gangs will earn the player money and
|
||||||
reputation with the corresponding Faction
|
reputation with the corresponding Faction
|
||||||
<br />
|
<br />
|
||||||
Every Augmentation in the game will be available through the Factions listed above
|
Every Augmentation in the game will be available through the Factions listed above
|
||||||
@ -225,15 +227,15 @@ BitNodes["BitNode6"] = new BitNode(
|
|||||||
"Like Tears in Rain",
|
"Like Tears in Rain",
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
In the middle of the 21st century, {FactionNames.OmniTekIncorporated} began designing and manufacturing advanced synthetic
|
In the middle of the 21st century, {FactionNames.OmniTekIncorporated} began designing and manufacturing advanced
|
||||||
androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation of
|
synthetic androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth
|
||||||
their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was the first
|
generation of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was
|
||||||
sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent than
|
the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more
|
||||||
the humans that had created them.
|
intelligent than the humans that had created them.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
In this BitNode you will be able to access the {FactionNames.Bladeburners} Division at the NSA, which provides a new mechanic for
|
In this BitNode you will be able to access the {FactionNames.Bladeburners} Division at the NSA, which provides a
|
||||||
progression. Furthermore:
|
new mechanic for progression. Furthermore:
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Hacking and Hacknet Nodes will be less profitable
|
Hacking and Hacknet Nodes will be less profitable
|
||||||
@ -250,9 +252,9 @@ BitNodes["BitNode6"] = new BitNode(
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade its
|
Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade its
|
||||||
level up to a maximum of 3. This Source-File allows you to access the NSA's {FactionNames.Bladeburners} Division in other
|
level up to a maximum of 3. This Source-File allows you to access the NSA's {FactionNames.Bladeburners} Division
|
||||||
BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat
|
in other BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your
|
||||||
stats by:
|
combat stats by:
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Level 1: 8%
|
Level 1: 8%
|
||||||
@ -270,14 +272,15 @@ BitNodes["BitNode7"] = new BitNode(
|
|||||||
"More human than humans",
|
"More human than humans",
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
In the middle of the 21st century, you were doing cutting-edge work at {FactionNames.OmniTekIncorporated} as part of the AI
|
In the middle of the 21st century, you were doing cutting-edge work at {FactionNames.OmniTekIncorporated} as part
|
||||||
design team for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological
|
of the AI design team for advanced synthetic androids, or Synthoids for short. You helped achieve a major
|
||||||
breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a
|
technological breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a
|
||||||
hyperintelligent AI. Many argue that this was the first sentient AI ever created. This resulted in Synthoid models
|
hyperintelligent AI. Many argue that this was the first sentient AI ever created. This resulted in Synthoid models
|
||||||
that were stronger, faster, and more intelligent than the humans that had created them.
|
that were stronger, faster, and more intelligent than the humans that had created them.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
In this BitNode you will be able to access the {FactionNames.Bladeburners} API, which allows you to access {FactionNames.Bladeburners}
|
In this BitNode you will be able to access the {FactionNames.Bladeburners} API, which allows you to access{" "}
|
||||||
|
{FactionNames.Bladeburners}
|
||||||
functionality through Netscript. Furthermore: <br />
|
functionality through Netscript. Furthermore: <br />
|
||||||
<br />
|
<br />
|
||||||
The rank you gain from {FactionNames.Bladeburners} contracts/operations is reduced by 40%
|
The rank you gain from {FactionNames.Bladeburners} contracts/operations is reduced by 40%
|
||||||
@ -300,8 +303,9 @@ BitNodes["BitNode7"] = new BitNode(
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade its
|
Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade its
|
||||||
level up to a maximum of 3. This Source-File allows you to access the {FactionNames.Bladeburners} Netscript API in other BitNodes.
|
level up to a maximum of 3. This Source-File allows you to access the {FactionNames.Bladeburners} Netscript API in
|
||||||
In addition, this Source-File will increase all of your {FactionNames.Bladeburners} multipliers by:
|
other BitNodes. In addition, this Source-File will increase all of your {FactionNames.Bladeburners} multipliers
|
||||||
|
by:
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Level 1: 8%
|
Level 1: 8%
|
||||||
@ -364,10 +368,10 @@ BitNodes["BitNode9"] = new BitNode(
|
|||||||
"Hacknet Unleashed",
|
"Hacknet Unleashed",
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
When {FactionNames.FulcrumSecretTechnologies} released their open-source Linux distro Chapeau, it quickly became the OS of choice for
|
When {FactionNames.FulcrumSecretTechnologies} released their open-source Linux distro Chapeau, it quickly became
|
||||||
the underground hacking community. Chapeau became especially notorious for powering the Hacknet, a global,
|
the OS of choice for the underground hacking community. Chapeau became especially notorious for powering the
|
||||||
decentralized network used for nefarious purposes. {FactionNames.FulcrumSecretTechnologies} quickly abandoned the project and dissociated
|
Hacknet, a global, decentralized network used for nefarious purposes. {FactionNames.FulcrumSecretTechnologies}{" "}
|
||||||
themselves from it.
|
quickly abandoned the project and dissociated themselves from it.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate hashes,
|
This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate hashes,
|
||||||
@ -538,8 +542,8 @@ BitNodes["BitNode13"] = new BitNode(
|
|||||||
"1 step back, 2 steps forward",
|
"1 step back, 2 steps forward",
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
With the invention of Augmentations in the 2040s a religious group known as the {FactionNames.ChurchOfTheMachineGod} has
|
With the invention of Augmentations in the 2040s a religious group known as the{" "}
|
||||||
rallied far more support than anyone would have hoped.
|
{FactionNames.ChurchOfTheMachineGod} has rallied far more support than anyone would have hoped.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Their leader, Allison "Mother" Stanek is said to have created her own Augmentation whose power goes beyond any
|
Their leader, Allison "Mother" Stanek is said to have created her own Augmentation whose power goes beyond any
|
||||||
@ -555,7 +559,8 @@ BitNodes["BitNode13"] = new BitNode(
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File it will upgrade its
|
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File it will upgrade its
|
||||||
level up to a maximum of 3. This Source-File lets the {FactionNames.ChurchOfTheMachineGod} appear in other BitNodes.
|
level up to a maximum of 3. This Source-File lets the {FactionNames.ChurchOfTheMachineGod} appear in other
|
||||||
|
BitNodes.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Each level of this Source-File increases the size of Stanek's Gift.
|
Each level of this Source-File increases the size of Stanek's Gift.
|
||||||
@ -624,6 +629,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
|
BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
|
||||||
BitNodeMultipliers.GangSoftcap = 0.9;
|
BitNodeMultipliers.GangSoftcap = 0.9;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.5;
|
||||||
break;
|
break;
|
||||||
case 4: // The Singularity
|
case 4: // The Singularity
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
||||||
@ -642,6 +648,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.StaneksGiftExtraSize = 0;
|
BitNodeMultipliers.StaneksGiftExtraSize = 0;
|
||||||
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
|
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 3;
|
BitNodeMultipliers.WorldDaemonDifficulty = 3;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.5;
|
||||||
break;
|
break;
|
||||||
case 5: // Artificial intelligence
|
case 5: // Artificial intelligence
|
||||||
BitNodeMultipliers.ServerMaxMoney = 2;
|
BitNodeMultipliers.ServerMaxMoney = 2;
|
||||||
@ -659,6 +666,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.StaneksGiftExtraSize = 0;
|
BitNodeMultipliers.StaneksGiftExtraSize = 0;
|
||||||
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
|
BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 1.5;
|
BitNodeMultipliers.WorldDaemonDifficulty = 1.5;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.5;
|
||||||
break;
|
break;
|
||||||
case 6: // Bladeburner
|
case 6: // Bladeburner
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
||||||
@ -679,6 +687,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.GangSoftcap = 0.7;
|
BitNodeMultipliers.GangSoftcap = 0.7;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.2;
|
||||||
break;
|
break;
|
||||||
case 7: // Bladeburner 2079
|
case 7: // Bladeburner 2079
|
||||||
BitNodeMultipliers.BladeburnerRank = 0.6;
|
BitNodeMultipliers.BladeburnerRank = 0.6;
|
||||||
@ -704,6 +713,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.GangSoftcap = 0.7;
|
BitNodeMultipliers.GangSoftcap = 0.7;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.2;
|
||||||
break;
|
break;
|
||||||
case 8: // Ghost of Wall Street
|
case 8: // Ghost of Wall Street
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0.3;
|
BitNodeMultipliers.ScriptHackMoney = 0.3;
|
||||||
@ -720,6 +730,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.PurchasedServerSoftcap = 4;
|
BitNodeMultipliers.PurchasedServerSoftcap = 4;
|
||||||
BitNodeMultipliers.GangSoftcap = 0;
|
BitNodeMultipliers.GangSoftcap = 0;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0;
|
BitNodeMultipliers.CorporationSoftCap = 0;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0;
|
||||||
break;
|
break;
|
||||||
case 9: // Hacktocracy
|
case 9: // Hacktocracy
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
|
||||||
@ -746,6 +757,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.GangSoftcap = 0.8;
|
BitNodeMultipliers.GangSoftcap = 0.8;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.7;
|
BitNodeMultipliers.CorporationSoftCap = 0.7;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.25;
|
||||||
break;
|
break;
|
||||||
case 10: // Digital Carbon
|
case 10: // Digital Carbon
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
||||||
@ -775,6 +787,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.GangSoftcap = 0.9;
|
BitNodeMultipliers.GangSoftcap = 0.9;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
BitNodeMultipliers.WorldDaemonDifficulty = 2;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.25;
|
||||||
break;
|
break;
|
||||||
case 11: //The Big Crash
|
case 11: //The Big Crash
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.6;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.6;
|
||||||
@ -796,6 +809,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.PurchasedServerSoftcap = 2;
|
BitNodeMultipliers.PurchasedServerSoftcap = 2;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
BitNodeMultipliers.CorporationSoftCap = 0.9;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 1.5;
|
BitNodeMultipliers.WorldDaemonDifficulty = 1.5;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.75;
|
||||||
break;
|
break;
|
||||||
case 12: {
|
case 12: {
|
||||||
//The Recursion
|
//The Recursion
|
||||||
@ -870,6 +884,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.GangSoftcap = 0.8;
|
BitNodeMultipliers.GangSoftcap = 0.8;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.8;
|
BitNodeMultipliers.CorporationSoftCap = 0.8;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = inc;
|
BitNodeMultipliers.WorldDaemonDifficulty = inc;
|
||||||
|
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = dec;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 13: {
|
case 13: {
|
||||||
@ -912,6 +928,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
|||||||
BitNodeMultipliers.GangSoftcap = 0.3;
|
BitNodeMultipliers.GangSoftcap = 0.3;
|
||||||
BitNodeMultipliers.CorporationSoftCap = 0.3;
|
BitNodeMultipliers.CorporationSoftCap = 0.3;
|
||||||
BitNodeMultipliers.WorldDaemonDifficulty = 3;
|
BitNodeMultipliers.WorldDaemonDifficulty = 3;
|
||||||
|
BitNodeMultipliers.GangUniqueAugs = 0.1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -114,6 +114,11 @@ interface IBitNodeMultipliers {
|
|||||||
*/
|
*/
|
||||||
GangSoftcap: number;
|
GangSoftcap: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percentage of unique augs that the gang has.
|
||||||
|
*/
|
||||||
|
GangUniqueAugs: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Influences the experienced gained when hacking a server.
|
* Influences the experienced gained when hacking a server.
|
||||||
*/
|
*/
|
||||||
@ -232,6 +237,11 @@ interface IBitNodeMultipliers {
|
|||||||
*/
|
*/
|
||||||
WorldDaemonDifficulty: number;
|
WorldDaemonDifficulty: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Influences corporation dividends.
|
||||||
|
*/
|
||||||
|
CorporationSoftCap: number;
|
||||||
|
|
||||||
// Index signature
|
// Index signature
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
}
|
}
|
||||||
@ -295,6 +305,7 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
|
|||||||
BladeburnerSkillCost: 1,
|
BladeburnerSkillCost: 1,
|
||||||
|
|
||||||
GangSoftcap: 1,
|
GangSoftcap: 1,
|
||||||
|
GangUniqueAugs: 1,
|
||||||
|
|
||||||
DaedalusAugsRequirement: 1,
|
DaedalusAugsRequirement: 1,
|
||||||
|
|
||||||
|
@ -134,9 +134,7 @@ export class Action implements IAction {
|
|||||||
for (const decay of Object.keys(this.decays)) {
|
for (const decay of Object.keys(this.decays)) {
|
||||||
if (this.decays.hasOwnProperty(decay)) {
|
if (this.decays.hasOwnProperty(decay)) {
|
||||||
if (this.decays[decay] > 1) {
|
if (this.decays[decay] > 1) {
|
||||||
throw new Error(
|
throw new Error(`Invalid decays when constructing Action ${this.name}. Decay value cannot be greater than 1`);
|
||||||
`Invalid decays when constructing Action ${this.name}. Decay value cannot be greater than 1`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import { joinFaction } from "../Faction/FactionHelpers";
|
|||||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||||
import { FactionNames } from "../Faction/data/FactionNames";
|
import { FactionNames } from "../Faction/data/FactionNames";
|
||||||
import { BlackOperationNames } from "./data/BlackOperationNames";
|
import { BlackOperationNames } from "./data/BlackOperationNames";
|
||||||
|
import { KEY } from "../utils/helpers/keyCodes";
|
||||||
|
|
||||||
interface BlackOpsAttempt {
|
interface BlackOpsAttempt {
|
||||||
error?: string;
|
error?: string;
|
||||||
@ -793,7 +794,7 @@ export class Bladeburner implements IBladeburner {
|
|||||||
if (c === '"') {
|
if (c === '"') {
|
||||||
// Double quotes
|
// Double quotes
|
||||||
const endQuote = command.indexOf('"', i + 1);
|
const endQuote = command.indexOf('"', i + 1);
|
||||||
if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === " ")) {
|
if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === KEY.SPACE)) {
|
||||||
args.push(command.substr(i + 1, endQuote - i - 1));
|
args.push(command.substr(i + 1, endQuote - i - 1));
|
||||||
if (endQuote === command.length - 1) {
|
if (endQuote === command.length - 1) {
|
||||||
start = i = endQuote + 1;
|
start = i = endQuote + 1;
|
||||||
@ -805,7 +806,7 @@ export class Bladeburner implements IBladeburner {
|
|||||||
} else if (c === "'") {
|
} else if (c === "'") {
|
||||||
// Single quotes, same thing as above
|
// Single quotes, same thing as above
|
||||||
const endQuote = command.indexOf("'", i + 1);
|
const endQuote = command.indexOf("'", i + 1);
|
||||||
if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === " ")) {
|
if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === KEY.SPACE)) {
|
||||||
args.push(command.substr(i + 1, endQuote - i - 1));
|
args.push(command.substr(i + 1, endQuote - i - 1));
|
||||||
if (endQuote === command.length - 1) {
|
if (endQuote === command.length - 1) {
|
||||||
start = i = endQuote + 1;
|
start = i = endQuote + 1;
|
||||||
@ -814,7 +815,7 @@ export class Bladeburner implements IBladeburner {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (c === " ") {
|
} else if (c === KEY.SPACE) {
|
||||||
args.push(command.substr(start, i - start));
|
args.push(command.substr(start, i - start));
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,16 @@ import { IMap } from "../types";
|
|||||||
|
|
||||||
export const GeneralActions: IMap<Action> = {};
|
export const GeneralActions: IMap<Action> = {};
|
||||||
|
|
||||||
const actionNames : Array<string> = [
|
const actionNames: Array<string> = [
|
||||||
"Training",
|
"Training",
|
||||||
"Field Analysis",
|
"Field Analysis",
|
||||||
"Recruitment",
|
"Recruitment",
|
||||||
"Diplomacy",
|
"Diplomacy",
|
||||||
"Hyperbolic Regeneration Chamber",
|
"Hyperbolic Regeneration Chamber",
|
||||||
"Incite Violence"
|
"Incite Violence",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const actionName of actionNames){
|
for (const actionName of actionNames) {
|
||||||
GeneralActions[actionName] = new Action({
|
GeneralActions[actionName] = new Action({
|
||||||
name: actionName,
|
name: actionName,
|
||||||
});
|
});
|
||||||
|
@ -7,8 +7,7 @@ export const Skills: IMap<Skill> = {};
|
|||||||
(function () {
|
(function () {
|
||||||
Skills[SkillNames.BladesIntuition] = new Skill({
|
Skills[SkillNames.BladesIntuition] = new Skill({
|
||||||
name: SkillNames.BladesIntuition,
|
name: SkillNames.BladesIntuition,
|
||||||
desc:
|
desc: "Each level of this skill increases your success chance for all Contracts, Operations, and BlackOps by 3%",
|
||||||
"Each level of this skill increases your success chance for all Contracts, Operations, and BlackOps by 3%",
|
|
||||||
baseCost: 3,
|
baseCost: 3,
|
||||||
costInc: 2.1,
|
costInc: 2.1,
|
||||||
successChanceAll: 3,
|
successChanceAll: 3,
|
||||||
|
@ -17,8 +17,8 @@ export const BlackOperations: {
|
|||||||
Zenyatta, along with the rest of the PMC, is a Synthoid.
|
Zenyatta, along with the rest of the PMC, is a Synthoid.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
The goal of {BlackOperationNames.OperationTyphoon} is to find and eliminate Zenyatta and RedWater by any means necessary. After the
|
The goal of {BlackOperationNames.OperationTyphoon} is to find and eliminate Zenyatta and RedWater by any means
|
||||||
task is completed, the actions must be covered up from the general public.
|
necessary. After the task is completed, the actions must be covered up from the general public.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -30,10 +30,10 @@ export const BlackOperations: {
|
|||||||
supporter of Synthoid rights. He must be removed.
|
supporter of Synthoid rights. He must be removed.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
The goal of {BlackOperationNames.OperationZero} is to covertly infiltrate AeroCorp and uncover any incriminating evidence or
|
The goal of {BlackOperationNames.OperationZero} is to covertly infiltrate AeroCorp and uncover any incriminating
|
||||||
information against Watataki that will cause him to be removed from his position at AeroCorp. Incriminating
|
evidence or information against Watataki that will cause him to be removed from his position at AeroCorp.
|
||||||
evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced security
|
Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced
|
||||||
measures in the world.
|
security measures in the world.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -47,8 +47,8 @@ export const BlackOperations: {
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that
|
Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that
|
||||||
their base of operations is in {CityName.Ishima}'s underground sewer systems. Your task is to investigate the sewer
|
their base of operations is in {CityName.Ishima}'s underground sewer systems. Your task is to investigate the
|
||||||
systems, and eliminate Samizdat. They must never publish anything again.
|
sewer systems, and eliminate Samizdat. They must never publish anything again.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -61,9 +61,9 @@ export const BlackOperations: {
|
|||||||
dangerous.
|
dangerous.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Your goal is to enter and destroy the Bioengineering department's facility in {CityName.Aevum}. The task is not just to
|
Your goal is to enter and destroy the Bioengineering department's facility in {CityName.Aevum}. The task is not
|
||||||
retire the Synthoids there, but also to destroy any information or research at the facility that is relevant to
|
just to retire the Synthoids there, but also to destroy any information or research at the facility that is
|
||||||
the Synthoids and their goals.
|
relevant to the Synthoids and their goals.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -96,10 +96,10 @@ export const BlackOperations: {
|
|||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls
|
The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls
|
||||||
himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into {CityName.Sector12}. We
|
himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into{" "}
|
||||||
also have reason to believe they tried to break into one of Universal Energy's facilities in order to cause a
|
{CityName.Sector12}. We also have reason to believe they tried to break into one of Universal Energy's
|
||||||
city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented Synthoid, and have thus enlisted our
|
facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented
|
||||||
help.
|
Synthoid, and have thus enlisted our help.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Your mission is to eradicate Juggernaut and his followers.
|
Your mission is to eradicate Juggernaut and his followers.
|
||||||
@ -109,13 +109,13 @@ export const BlackOperations: {
|
|||||||
[BlackOperationNames.OperationRedDragon]: {
|
[BlackOperationNames.OperationRedDragon]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
The {FactionNames.Tetrads} criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. We believe they
|
The {FactionNames.Tetrads} criminal organization is suspected of reverse-engineering the MK-VI Synthoid design.
|
||||||
altered and possibly improved the design and began manufacturing their own Synthoid models in order to bolster
|
We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in
|
||||||
their criminal activities.
|
order to bolster their criminal activities.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Your task is to infiltrate and destroy the {FactionNames.Tetrads}' base of operations in Los Angeles. Intelligence tells us
|
Your task is to infiltrate and destroy the {FactionNames.Tetrads}' base of operations in Los Angeles.
|
||||||
that their base houses one of their Synthoid manufacturing units.
|
Intelligence tells us that their base houses one of their Synthoid manufacturing units.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -138,23 +138,24 @@ export const BlackOperations: {
|
|||||||
[BlackOperationNames.OperationDeckard]: {
|
[BlackOperationNames.OperationDeckard]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
Despite your success in eliminating VitaLife's new android-replicating technology in {BlackOperationNames.OperationK}, we've
|
Despite your success in eliminating VitaLife's new android-replicating technology in{" "}
|
||||||
discovered that a small group of MK-VI Synthoids were able to make off with the schematics and design of the
|
{BlackOperationNames.OperationK}, we've discovered that a small group of MK-VI Synthoids were able to make off
|
||||||
technology before the Operation. It is almost a certainty that these Synthoids are some of the rogue MK-VI ones
|
with the schematics and design of the technology before the Operation. It is almost a certainty that these
|
||||||
from the Synthoid Uprising.
|
Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
The goal of {BlackOperationNames.OperationDeckard} is to hunt down these Synthoids and retire them. I don't need to tell you how
|
The goal of {BlackOperationNames.OperationDeckard} is to hunt down these Synthoids and retire them. I don't need
|
||||||
critical this mission is.
|
to tell you how critical this mission is.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
[BlackOperationNames.OperationTyrell]: {
|
[BlackOperationNames.OperationTyrell]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
A week ago {FactionNames.BladeIndustries} reported a small break-in at one of their {CityName.Aevum} Augmentation storage facilities. We
|
A week ago {FactionNames.BladeIndustries} reported a small break-in at one of their {CityName.Aevum}{" "}
|
||||||
figured out that {FactionNames.TheDarkArmy} was behind the heist, and didn't think any more of it. However, we've just
|
Augmentation storage facilities. We figured out that {FactionNames.TheDarkArmy} was behind the heist, and didn't
|
||||||
discovered that several known MK-VI Synthoids were part of that break-in group.
|
think any more of it. However, we've just discovered that several known MK-VI Synthoids were part of that
|
||||||
|
break-in group.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt
|
We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt
|
||||||
@ -165,15 +166,17 @@ export const BlackOperations: {
|
|||||||
[BlackOperationNames.OperationWallace]: {
|
[BlackOperationNames.OperationWallace]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
Based on information gathered from {BlackOperationNames.OperationTyrell}, we've discovered that {FactionNames.TheDarkArmy} was well aware that
|
Based on information gathered from {BlackOperationNames.OperationTyrell}, we've discovered that{" "}
|
||||||
there were Synthoids amongst their ranks. Even worse, we believe that {FactionNames.TheDarkArmy} is working together with
|
{FactionNames.TheDarkArmy} was well aware that there were Synthoids amongst their ranks. Even worse, we believe
|
||||||
other criminal organizations such as {FactionNames.TheSyndicate} and that they are planning some sort of large-scale takeover
|
that {FactionNames.TheDarkArmy} is working together with other criminal organizations such as{" "}
|
||||||
of multiple major cities, most notably {CityName.Aevum}. We suspect that Synthoids have infiltrated the ranks of these
|
{FactionNames.TheSyndicate} and that they are planning some sort of large-scale takeover of multiple major
|
||||||
criminal factions and are trying to stage another Synthoid uprising.
|
cities, most notably {CityName.Aevum}. We suspect that Synthoids have infiltrated the ranks of these criminal
|
||||||
|
factions and are trying to stage another Synthoid uprising.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
The best way to deal with this is to prevent it before it even happens. The goal of {BlackOperationNames.OperationWallace} is to
|
The best way to deal with this is to prevent it before it even happens. The goal of{" "}
|
||||||
destroy {FactionNames.TheDarkArmy} and Syndicate factions in {CityName.Aevum} immediately. Leave no survivors.
|
{BlackOperationNames.OperationWallace} is to destroy {FactionNames.TheDarkArmy} and Syndicate factions in{" "}
|
||||||
|
{CityName.Aevum} immediately. Leave no survivors.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -193,18 +196,18 @@ export const BlackOperations: {
|
|||||||
[BlackOperationNames.OperationHyron]: {
|
[BlackOperationNames.OperationHyron]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
Our intelligence tells us that {FactionNames.FulcrumSecretTechnologies} is developing a quantum supercomputer using human brains as
|
Our intelligence tells us that {FactionNames.FulcrumSecretTechnologies} is developing a quantum supercomputer
|
||||||
core processors. This supercomputer is rumored to be able to store vast amounts of data and perform computations
|
using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data
|
||||||
unmatched by any other supercomputer on the planet. But more importantly, the use of organic human brains means
|
and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of
|
||||||
that the supercomputer may be able to reason abstractly and become self-aware.
|
organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.
|
I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
The research for this project is being conducted at one of {FactionNames.FulcrumSecretTechnologies} secret facilities in {CityName.Aevum},
|
The research for this project is being conducted at one of {FactionNames.FulcrumSecretTechnologies} secret
|
||||||
codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, and then find and kill the
|
facilities in {CityName.Aevum}, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work,
|
||||||
project lead.
|
and then find and kill the project lead.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -213,8 +216,8 @@ export const BlackOperations: {
|
|||||||
<>
|
<>
|
||||||
DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the
|
DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the
|
||||||
people's dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our
|
people's dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our
|
||||||
agents and informants in {CityName.Chongqing}, we have reason to believe that one of the broadcast towers there has been
|
agents and informants in {CityName.Chongqing}, we have reason to believe that one of the broadcast towers there
|
||||||
compromised by Synthoids and is being used to spread pro-Synthoid propaganda.
|
has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
The mission is to destroy this broadcast tower. Speed and stealth are of the utmost importance for this.
|
The mission is to destroy this broadcast tower. Speed and stealth are of the utmost importance for this.
|
||||||
@ -224,36 +227,38 @@ export const BlackOperations: {
|
|||||||
[BlackOperationNames.OperationIonStorm]: {
|
[BlackOperationNames.OperationIonStorm]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the {CityName.Sector12} Slums.
|
Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the{" "}
|
||||||
We don't know if they are rogue Synthoids from the Uprising, but we do know that they have been stockpiling
|
{CityName.Sector12} Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they
|
||||||
weapons, money, and other resources. This makes them dangerous.
|
have been stockpiling weapons, money, and other resources. This makes them dangerous.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
This is a full-scale assault operation to find and retire all of these Synthoids in the {CityName.Sector12} Slums.
|
This is a full-scale assault operation to find and retire all of these Synthoids in the {CityName.Sector12}{" "}
|
||||||
|
Slums.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
[BlackOperationNames.OperationAnnihilus]: {
|
[BlackOperationNames.OperationAnnihilus]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
Our superiors have ordered us to eradicate everything and everyone in an underground facility located in {CityName.Aevum}.
|
Our superiors have ordered us to eradicate everything and everyone in an underground facility located in{" "}
|
||||||
They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist organization called
|
{CityName.Aevum}. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist
|
||||||
'{FactionNames.TheCovenant}'. We have no prior intelligence about this organization, so you are going in blind.
|
organization called '{FactionNames.TheCovenant}'. We have no prior intelligence about this organization, so you
|
||||||
|
are going in blind.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
[BlackOperationNames.OperationUltron]: {
|
[BlackOperationNames.OperationUltron]: {
|
||||||
desc: (
|
desc: (
|
||||||
<>
|
<>
|
||||||
{FactionNames.OmniTekIncorporated}, the original designer and manufacturer of Synthoids, has notified us of a malfunction in
|
{FactionNames.OmniTekIncorporated}, the original designer and manufacturer of Synthoids, has notified us of a
|
||||||
their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized and seek out the
|
malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized
|
||||||
destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue ones from the
|
and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue
|
||||||
Uprising.
|
ones from the Uprising.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{FactionNames.OmniTekIncorporated} has also told us they they believe someone has triggered this malfunction in a large group of MK-VI
|
{FactionNames.OmniTekIncorporated} has also told us they they believe someone has triggered this malfunction in
|
||||||
Synthoids, and that these newly-radicalized Synthoids are now amassing in {CityName.Volhaven} to form a terrorist group
|
a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in{" "}
|
||||||
called Ultron.
|
{CityName.Volhaven} to form a terrorist group called Ultron.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making
|
Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CityName } from './../../Locations/data/CityNames';
|
import { CityName } from "./../../Locations/data/CityNames";
|
||||||
export const BladeburnerConstants: {
|
export const BladeburnerConstants: {
|
||||||
CityNames: string[];
|
CityNames: string[];
|
||||||
CyclesPerSecond: number;
|
CyclesPerSecond: number;
|
||||||
@ -28,7 +28,14 @@ export const BladeburnerConstants: {
|
|||||||
HrcHpGain: number;
|
HrcHpGain: number;
|
||||||
HrcStaminaGain: number;
|
HrcStaminaGain: number;
|
||||||
} = {
|
} = {
|
||||||
CityNames: [CityName.Aevum, CityName.Chongqing, CityName.Sector12, CityName.NewTokyo, CityName.Ishima, CityName.Volhaven],
|
CityNames: [
|
||||||
|
CityName.Aevum,
|
||||||
|
CityName.Chongqing,
|
||||||
|
CityName.Sector12,
|
||||||
|
CityName.NewTokyo,
|
||||||
|
CityName.Ishima,
|
||||||
|
CityName.Volhaven,
|
||||||
|
],
|
||||||
CyclesPerSecond: 5, // Game cycle is 200 ms
|
CyclesPerSecond: 5, // Game cycle is 200 ms
|
||||||
|
|
||||||
StaminaGainPerSecond: 0.0085,
|
StaminaGainPerSecond: 0.0085,
|
||||||
|
@ -26,8 +26,8 @@ export const ConsoleHelpText: {
|
|||||||
"Usage: automate [var] [val] [hi/low]",
|
"Usage: automate [var] [val] [hi/low]",
|
||||||
" ",
|
" ",
|
||||||
"A simple way to automate your Bladeburner actions. This console command can be used " +
|
"A simple way to automate your Bladeburner actions. This console command can be used " +
|
||||||
"to automatically start an action when your stamina rises above a certain threshold, and " +
|
"to automatically start an action when your stamina rises above a certain threshold, and " +
|
||||||
"automatically switch to another action when your stamina drops below another threshold.",
|
"automatically switch to another action when your stamina drops below another threshold.",
|
||||||
" ",
|
" ",
|
||||||
" automate status - Check the current status of your automation and get a brief description of what it'll do",
|
" automate status - Check the current status of your automation and get a brief description of what it'll do",
|
||||||
" automate en - Enable the automation feature",
|
" automate en - Enable the automation feature",
|
||||||
@ -41,9 +41,9 @@ export const ConsoleHelpText: {
|
|||||||
" automate general 'Field Analysis' low",
|
" automate general 'Field Analysis' low",
|
||||||
" ",
|
" ",
|
||||||
"Using the four console commands above will set the automation to perform Tracking contracts " +
|
"Using the four console commands above will set the automation to perform Tracking contracts " +
|
||||||
"if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below " +
|
"if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below " +
|
||||||
"50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must " +
|
"50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must " +
|
||||||
"exactly match whatever the name is in the UI.",
|
"exactly match whatever the name is in the UI.",
|
||||||
" ",
|
" ",
|
||||||
],
|
],
|
||||||
clear: ["Usage: clear", " ", "Clears the console", " "],
|
clear: ["Usage: clear", " ", "Clears the console", " "],
|
||||||
@ -52,8 +52,8 @@ export const ConsoleHelpText: {
|
|||||||
"Usage: help [command]",
|
"Usage: help [command]",
|
||||||
" ",
|
" ",
|
||||||
"Running 'help' with no arguments displays the general help text, which lists all console commands " +
|
"Running 'help' with no arguments displays the general help text, which lists all console commands " +
|
||||||
"and a brief description of what they do. A command can be specified to get more specific help text " +
|
"and a brief description of what they do. A command can be specified to get more specific help text " +
|
||||||
"about that particular command. For example:",
|
"about that particular command. For example:",
|
||||||
" ",
|
" ",
|
||||||
" help automate",
|
" help automate",
|
||||||
" ",
|
" ",
|
||||||
@ -64,8 +64,8 @@ export const ConsoleHelpText: {
|
|||||||
"Usage: log [en/dis] [type]",
|
"Usage: log [en/dis] [type]",
|
||||||
" ",
|
" ",
|
||||||
"Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " +
|
"Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " +
|
||||||
"in the console. There are also random events that are logged in the console as well. The five categories of " +
|
"in the console. There are also random events that are logged in the console as well. The five categories of " +
|
||||||
"things that get logged are:",
|
"things that get logged are:",
|
||||||
" ",
|
" ",
|
||||||
"[general, contracts, ops, blackops, events]",
|
"[general, contracts, ops, blackops, events]",
|
||||||
" ",
|
" ",
|
||||||
@ -91,8 +91,8 @@ export const ConsoleHelpText: {
|
|||||||
" skill list",
|
" skill list",
|
||||||
" ",
|
" ",
|
||||||
"To display information about a specific skill, specify the name of the skill afterwards. " +
|
"To display information about a specific skill, specify the name of the skill afterwards. " +
|
||||||
"Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If " +
|
"Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If " +
|
||||||
"the name of the skill has whitespace, enclose the name of the skill in double quotation marks:",
|
"the name of the skill has whitespace, enclose the name of the skill in double quotation marks:",
|
||||||
" ",
|
" ",
|
||||||
" skill list Reaper",
|
" skill list Reaper",
|
||||||
" skill list 'Digital Observer'",
|
" skill list 'Digital Observer'",
|
||||||
@ -106,9 +106,9 @@ export const ConsoleHelpText: {
|
|||||||
"Usage: start [type] [name]",
|
"Usage: start [type] [name]",
|
||||||
" ",
|
" ",
|
||||||
"Start an action. An action is specified by its type and its name. The " +
|
"Start an action. An action is specified by its type and its name. The " +
|
||||||
"name is case-sensitive. It must appear exactly as it does in the UI. If " +
|
"name is case-sensitive. It must appear exactly as it does in the UI. If " +
|
||||||
"the name of the action has whitespace, enclose it in double quotation marks. " +
|
"the name of the action has whitespace, enclose it in double quotation marks. " +
|
||||||
"Valid action types include:",
|
"Valid action types include:",
|
||||||
" ",
|
" ",
|
||||||
"[general, contract, op, blackop]",
|
"[general, contract, op, blackop]",
|
||||||
" ",
|
" ",
|
||||||
|
@ -18,7 +18,10 @@ export function BlackOpPage(props: IProps): React.ReactElement {
|
|||||||
successively by completing the one before it.
|
successively by completing the one before it.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<b>Your ultimate goal to climb through the ranks of {FactionNames.Bladeburners} is to complete all of the Black Ops.</b>
|
<b>
|
||||||
|
Your ultimate goal to climb through the ranks of {FactionNames.Bladeburners} is to complete all of the Black
|
||||||
|
Ops.
|
||||||
|
</b>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank
|
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank
|
||||||
|
@ -35,7 +35,7 @@ export function BladeburnerCinematic(): React.ReactElement {
|
|||||||
router.toTerminal();
|
router.toTerminal();
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
`Visit the National Security Agency (NSA) to apply for their ${FactionNames.Bladeburners} ` +
|
`Visit the National Security Agency (NSA) to apply for their ${FactionNames.Bladeburners} ` +
|
||||||
"division! You will need 100 of each combat stat before doing this.",
|
"division! You will need 100 of each combat stat before doing this.",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -89,7 +89,7 @@ export function Console(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
const consoleHistory = props.bladeburner.consoleHistory;
|
const consoleHistory = props.bladeburner.consoleHistory;
|
||||||
|
|
||||||
if (event.key === KEY.UPARROW) {
|
if (event.key === KEY.UP_ARROW) {
|
||||||
// up
|
// up
|
||||||
let i = consoleHistoryIndex;
|
let i = consoleHistoryIndex;
|
||||||
const len = consoleHistory.length;
|
const len = consoleHistory.length;
|
||||||
@ -109,7 +109,7 @@ export function Console(props: IProps): React.ReactElement {
|
|||||||
setCommand(prevCommand);
|
setCommand(prevCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === KEY.DOWNARROW) {
|
if (event.key === KEY.DOWN_ARROW) {
|
||||||
const i = consoleHistoryIndex;
|
const i = consoleHistoryIndex;
|
||||||
const len = consoleHistory.length;
|
const len = consoleHistory.length;
|
||||||
|
|
||||||
@ -140,14 +140,16 @@ export function Console(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 1 }}>
|
<Paper sx={{ p: 1 }}>
|
||||||
<Box sx={{
|
<Box
|
||||||
height: '60vh',
|
sx={{
|
||||||
paddingBottom: '8px',
|
height: "60vh",
|
||||||
display: 'flex',
|
paddingBottom: "8px",
|
||||||
alignItems: 'stretch',
|
display: "flex",
|
||||||
whiteSpace: 'pre-wrap',
|
alignItems: "stretch",
|
||||||
}}
|
whiteSpace: "pre-wrap",
|
||||||
onClick={handleClick}>
|
}}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Logs entries={[...props.bladeburner.consoleLogs]} />
|
<Logs entries={[...props.bladeburner.consoleLogs]} />
|
||||||
</Box>
|
</Box>
|
||||||
@ -195,9 +197,7 @@ function Logs({ entries }: ILogProps): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<List sx={{ height: "100%", overflow: "auto", p: 1 }} ref={scrollHook}>
|
<List sx={{ height: "100%", overflow: "auto", p: 1 }} ref={scrollHook}>
|
||||||
{entries && entries.map((log: any, i: number) => (
|
{entries && entries.map((log: any, i: number) => <Line key={i} content={log} />)}
|
||||||
<Line key={i} content={log} />
|
|
||||||
))}
|
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -44,13 +44,15 @@ export function Stats(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 1, overflowY: 'auto', overflowX: 'hidden', wordBreak: 'break-all' }}>
|
<Paper sx={{ p: 1, overflowY: "auto", overflowX: "hidden", wordBreak: "break-all" }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, maxHeight: '60vh' }}>
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 1, maxHeight: "60vh" }}>
|
||||||
<Box sx={{ alignSelf: 'flex-start', width: '100%' }}>
|
<Box sx={{ alignSelf: "flex-start", width: "100%" }}>
|
||||||
<Button onClick={() => setTravelOpen(true)} sx={{ width: '50%' }}>Travel</Button>
|
<Button onClick={() => setTravelOpen(true)} sx={{ width: "50%" }}>
|
||||||
|
Travel
|
||||||
|
</Button>
|
||||||
<Tooltip title={!inFaction ? <Typography>Rank 25 required.</Typography> : ""}>
|
<Tooltip title={!inFaction ? <Typography>Rank 25 required.</Typography> : ""}>
|
||||||
<span>
|
<span>
|
||||||
<Button disabled={!inFaction} onClick={openFaction} sx={{ width: '50%' }}>
|
<Button disabled={!inFaction} onClick={openFaction} sx={{ width: "50%" }}>
|
||||||
Faction
|
Faction
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
@ -110,8 +112,8 @@ export function Stats(props: IProps): React.ReactElement {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<Typography>
|
<Typography>
|
||||||
This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An accurate
|
This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An
|
||||||
population count increases success rate estimates.
|
accurate population count increases success rate estimates.
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -61,3 +61,35 @@ export class WHRNG implements RNG {
|
|||||||
return (this.s1 / 30269.0 + this.s2 / 30307.0 + this.s3 / 30323.0) % 1.0;
|
return (this.s1 / 30269.0 + this.s2 / 30307.0 + this.s3 / 30323.0) % 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SFC32RNG(seed: string): () => number {
|
||||||
|
let h = 1779033703 ^ seed.length;
|
||||||
|
for (let i = 0; i < seed.length; i++) {
|
||||||
|
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
|
||||||
|
h = (h << 13) | (h >>> 19);
|
||||||
|
}
|
||||||
|
const genSeed = (): number => {
|
||||||
|
h = Math.imul(h ^ (h >>> 16), 2246822507);
|
||||||
|
h = Math.imul(h ^ (h >>> 13), 3266489909);
|
||||||
|
return (h ^= h >>> 16) >>> 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let a = genSeed();
|
||||||
|
let b = genSeed();
|
||||||
|
let c = genSeed();
|
||||||
|
let d = genSeed();
|
||||||
|
return (): number => {
|
||||||
|
a >>>= 0;
|
||||||
|
b >>>= 0;
|
||||||
|
c >>>= 0;
|
||||||
|
d >>>= 0;
|
||||||
|
let t = (a + b) | 0;
|
||||||
|
a = b ^ (b >>> 9);
|
||||||
|
b = (c + (c << 3)) | 0;
|
||||||
|
c = (c << 21) | (c >>> 11);
|
||||||
|
d = (d + 1) | 0;
|
||||||
|
t = (t + d) | 0;
|
||||||
|
c = (c + t) | 0;
|
||||||
|
return (t >>> 0) / 4294967296;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -159,7 +159,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
|
|||||||
const copy = index.slice();
|
const copy = index.slice();
|
||||||
for (let i = 0; i < copy.length; i++) {
|
for (let i = 0; i < copy.length; i++) {
|
||||||
if (copy[i] === locks[i] && !stoppedOne) continue;
|
if (copy[i] === locks[i] && !stoppedOne) continue;
|
||||||
copy[i] = (copy[i] - 1 >= 0) ? copy[i] - 1 : symbols.length - 1;
|
copy[i] = copy[i] - 1 >= 0 ? copy[i] - 1 : symbols.length - 1;
|
||||||
stoppedOne = true;
|
stoppedOne = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTable(index:number[], symbols:string[]): string[][] {
|
function getTable(index: number[], symbols: string[]): string[][] {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
symbols[(index[0] + symbols.length - 1) % symbols.length],
|
symbols[(index[0] + symbols.length - 1) % symbols.length],
|
||||||
@ -209,7 +209,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkWinnings(t:string[][]): void {
|
function checkWinnings(t: string[][]): void {
|
||||||
const getPaylineData = function (payline: number[][]): string[] {
|
const getPaylineData = function (payline: number[][]): string[] {
|
||||||
const data = [];
|
const data = [];
|
||||||
for (const point of payline) {
|
for (const point of payline) {
|
||||||
|
106
src/Constants.ts
106
src/Constants.ts
@ -116,8 +116,8 @@ export const CONSTANTS: {
|
|||||||
TotalNumBitNodes: number;
|
TotalNumBitNodes: number;
|
||||||
LatestUpdate: string;
|
LatestUpdate: string;
|
||||||
} = {
|
} = {
|
||||||
VersionString: "1.6.0",
|
VersionString: "1.6.3",
|
||||||
VersionNumber: 12,
|
VersionNumber: 13,
|
||||||
|
|
||||||
// Speed (in ms) at which the main loop is updated
|
// Speed (in ms) at which the main loop is updated
|
||||||
_idleSpeed: 200,
|
_idleSpeed: 200,
|
||||||
@ -287,105 +287,19 @@ export const CONSTANTS: {
|
|||||||
TotalNumBitNodes: 24,
|
TotalNumBitNodes: 24,
|
||||||
|
|
||||||
LatestUpdate: `
|
LatestUpdate: `
|
||||||
v1.6.0 - 2022-03-29 Grafting
|
v1.6.3 - 2022-04-01 Few stanek fixes
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
** Vitalife secret lab **
|
Stanek Gift
|
||||||
|
|
||||||
* A new mechanic called Augmentation Grafting has been added. Resleeving has been removed.
|
* Has a minimum size of 2x3
|
||||||
* Credit to @nickofolas for his incredible work.
|
* Active Fragment property 'avgCharge' renamed to 'highestCharge'
|
||||||
|
* Formula for fragment effect updated to make 561% more sense.
|
||||||
|
Now you can charge to your heart content.
|
||||||
|
* Logs for the 'chargeFragment' function updated.
|
||||||
|
|
||||||
** Stanek **
|
Misc.
|
||||||
|
|
||||||
* BREAKING: Many functions in the stanek API were renamed in order to avoid name collision with things like Map.prototype.get
|
|
||||||
|
|
||||||
** UI **
|
|
||||||
|
|
||||||
* Major update to Sleeve, Gang UI, and Create Program (@nickofolas)
|
|
||||||
* re-add pre tags to support slash n in prompt (@jacktose)
|
|
||||||
* Tabelize linked output of 'ls' (@Master-Guy)
|
|
||||||
* Add the ability to filter open scripts (@phyzical)
|
|
||||||
* Add minHeight to editor tabs (@nickofolas)
|
|
||||||
* Properly expand gang equipment cards to fill entire screen (@nickofolas)
|
|
||||||
* Add shortcut to Faction augmentations page from FactionsRoot (@nickofolas)
|
|
||||||
* Fix extra space on editor tabs (@nickofolas)
|
|
||||||
* Present offline message as list (@DSteve595)
|
|
||||||
* add box showing remaining augments per faction (@jjayeon)
|
|
||||||
* Add tab switching support to vim mode (@JParisFerrer)
|
|
||||||
* Show current task on gang management screen (@zeddrak)
|
|
||||||
* Fix for ui of gang members current task when set via api (@phyzical)
|
|
||||||
* Don't hide irrelevant materials if their stock is not empty and hide irrelevant divisions from Export (@SagePtr)
|
|
||||||
* Fix regex to enable alpha transparency hex codes (8 digits) (@surdaft)
|
|
||||||
|
|
||||||
** API **
|
|
||||||
|
|
||||||
* Added dark web functions to ns api
|
|
||||||
* BREAKING: purchaseTor() should returns true if player already has Tor. (@DavidGrinberg, @waffleattack)
|
|
||||||
* Implement getBonusTime in Corporation API (@t-wolfeadam)
|
|
||||||
* Added functions to purchase TIX and WSI (@incubusnb)
|
|
||||||
* purchaseSleeveAug checks shock value (@incubusnb)
|
|
||||||
* Fix bug with hacknet api
|
|
||||||
* Fix spendHashes bug
|
|
||||||
* Added 0 cost of asleep() (@Master-Guy)
|
|
||||||
* Fix some misleading corporation errors (@TheRealMaxion)
|
|
||||||
* expose the inBladeburner on the player object (@phyzical)
|
|
||||||
* added ram charge for stanek width and height (@phyzical)
|
|
||||||
* Fix sufficient player money check to buy back shares. (@ChrissiQ)
|
|
||||||
* Fix Static Ram Circumventing for some NS functions (@CrafterKolyan)
|
|
||||||
* added CorporationSoftCap to NetscriptDefinitions (@phyzical)
|
|
||||||
* Added definition of autocomplete() 'data' argument. (@tigercat2000)
|
|
||||||
* Adding support for text/select options in Prompt command (@PhilipArmstead)
|
|
||||||
* Added the ability to exportGame via api (@phyzical)
|
|
||||||
|
|
||||||
** Arcade **
|
|
||||||
|
|
||||||
* Added an arcade to New Tokyo where you can play a 4 year old version of bitburner.
|
|
||||||
|
|
||||||
** Misc. **
|
|
||||||
|
|
||||||
* Add a warning triggered while auto-saves are off. (@MartinFournier)
|
|
||||||
* Log info for field analysis now displays actual rank gained. (@ApamNapat)
|
|
||||||
* Removed BladeburnerSkillCost from skill point cost description. (@ApamNapat)
|
|
||||||
* Fix handling for UpArrow in bladeburner console. (@dowinter)
|
|
||||||
* Add GitHub action to check PRs for generated files. (@MartinFournier)
|
|
||||||
* Cap Staneks gift at 25x25 to prevent crashes. (@waffleattack)
|
|
||||||
* Remove old & unused files from repository. (@MartinFournier)
|
|
||||||
* Factions on the factions screens are sorted by story progress / type. (@phyzical)
|
|
||||||
* Fix log manager not picking up new runs of scripts. (@phyzical)
|
|
||||||
* Added prettier to cicd.
|
|
||||||
* UI improvements (@phyzical)
|
|
||||||
* Documentation / Typos (@nanogyth, @Master-Guy, @incubusnb, @ApamNapat, @phyzical, @SagePtr)
|
|
||||||
* Give player code a copy of Division.upgrades instead of the live object (@Ornedan)
|
|
||||||
* Fix bug with small town achievement.
|
|
||||||
* Fix bug with purchaseSleeveAug (@phyzical)
|
|
||||||
* Check before unlocking corp upgrade (@gianfun)
|
|
||||||
* General codebase improvements. (@phyzical, @Master-Guy, @ApamNapat)
|
|
||||||
* Waiting on promises in NS1 no longer freezes the script. (@Master-Guy)
|
|
||||||
* Fix bug with missing ramcost for tFormat (@TheMas3212)
|
|
||||||
* Fix crash with new prompt
|
|
||||||
* Quick fix to prevent division by 0 in terminal (@Master-Guy)
|
|
||||||
* removed ip references (@phyzical, @Master-Guy)
|
|
||||||
* Terminal now supports 'ls -l'
|
|
||||||
* Fix negative number formatting (@Master-Guy)
|
|
||||||
* Fix unique ip generation (@InDieTasten)
|
|
||||||
* remove terminal command theme from docs (@phyzical)
|
|
||||||
* Fix 'Augmentations Left' with gang factions (@nickofolas)
|
|
||||||
* Attempt to fix 'bladeburner.process()' early routing issue (@MartinFournier)
|
|
||||||
* work in progress augment fix (@phyzical)
|
|
||||||
* Fixes missing space in Smart Supply (@TheRealMaxion)
|
|
||||||
* Change license to Apache 2 with Commons Clause
|
|
||||||
* updated regex sanitization (@mbrannen)
|
|
||||||
* Sleeve fix for when faction isnt found (@phyzical)
|
|
||||||
* Fix editor "close" naming (@phyzical)
|
|
||||||
* Fix bug with sleeves where some factions would be listed as workable. (@phyzical)
|
|
||||||
* Fix research tree of product industries post-prestige (@pd)
|
|
||||||
* Added a check for exisiting industry type before expanding (@phyzical)
|
|
||||||
* fix hackAnalyzeThreads returning infinity (@chrisrabe)
|
|
||||||
* Make growthAnalyze more accurate (@dwRchyngqxs)
|
|
||||||
* Add 'Zoom -> Reset Zoom' command to Steam (@smolgumball)
|
|
||||||
* Add hasOwnProperty check to GetServer (@SagePtr)
|
|
||||||
* Speed up employee productivity calculation (@pd)
|
|
||||||
* Field Work and Security Work benefit from 'share' (@SagePtr)
|
|
||||||
* Nerf noodle bar.
|
* Nerf noodle bar.
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IPlayer } from 'src/PersonObjects/IPlayer';
|
import { IPlayer } from "src/PersonObjects/IPlayer";
|
||||||
import { MaterialSizes } from './MaterialSizes';
|
import { MaterialSizes } from "./MaterialSizes";
|
||||||
import { ICorporation } from "./ICorporation";
|
import { ICorporation } from "./ICorporation";
|
||||||
import { IIndustry } from "./IIndustry";
|
import { IIndustry } from "./IIndustry";
|
||||||
import { IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
|
import { IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
|
||||||
@ -64,7 +64,7 @@ export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnl
|
|||||||
if (corporation.funds < upgrade[1]) {
|
if (corporation.funds < upgrade[1]) {
|
||||||
throw new Error("Insufficient funds");
|
throw new Error("Insufficient funds");
|
||||||
}
|
}
|
||||||
if(corporation.unlockUpgrades[upgrade[0]] === 1){
|
if (corporation.unlockUpgrades[upgrade[0]] === 1) {
|
||||||
throw new Error(`You have already unlocked the ${upgrade[2]} upgrade!`);
|
throw new Error(`You have already unlocked the ${upgrade[2]} upgrade!`);
|
||||||
}
|
}
|
||||||
corporation.unlock(upgrade);
|
corporation.unlock(upgrade);
|
||||||
@ -222,15 +222,15 @@ export function SellProduct(product: Product, city: string, amt: string, price:
|
|||||||
product.sllman[city][1] = "";
|
product.sllman[city][1] = "";
|
||||||
}
|
}
|
||||||
} else if (all) {
|
} else if (all) {
|
||||||
for (let i = 0; i < cities.length; ++i) {
|
for (let i = 0; i < cities.length; ++i) {
|
||||||
const tempCity = cities[i];
|
const tempCity = cities[i];
|
||||||
product.sllman[tempCity][0] = true;
|
product.sllman[tempCity][0] = true;
|
||||||
product.sllman[tempCity][1] = qty;
|
product.sllman[tempCity][1] = qty;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
product.sllman[city][0] = true;
|
|
||||||
product.sllman[city][1] = qty;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
product.sllman[city][0] = true;
|
||||||
|
product.sllman[city][1] = qty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ export function BulkPurchase(corp: ICorporation, warehouse: Warehouse, material:
|
|||||||
if (isNaN(amt) || amt < 0) {
|
if (isNaN(amt) || amt < 0) {
|
||||||
throw new Error(`Invalid input amount`);
|
throw new Error(`Invalid input amount`);
|
||||||
}
|
}
|
||||||
if (amt * matSize > maxAmount) {
|
if (amt * matSize <= maxAmount) {
|
||||||
throw new Error(`You do not have enough warehouse size to fit this purchase`);
|
throw new Error(`You do not have enough warehouse size to fit this purchase`);
|
||||||
}
|
}
|
||||||
const cost = amt * material.bCost;
|
const cost = amt * material.bCost;
|
||||||
@ -295,7 +295,7 @@ export function BuyBackShares(corporation: ICorporation, player: IPlayer, numSha
|
|||||||
if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!");
|
if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!");
|
||||||
if (!corporation.public) throw new Error("You haven't gone public!");
|
if (!corporation.public) throw new Error("You haven't gone public!");
|
||||||
const buybackPrice = corporation.sharePrice * 1.1;
|
const buybackPrice = corporation.sharePrice * 1.1;
|
||||||
if (player.money < (numShares * buybackPrice)) throw new Error("You cant afford that many shares!");
|
if (player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!");
|
||||||
corporation.numShares += numShares;
|
corporation.numShares += numShares;
|
||||||
corporation.issuedShares -= numShares;
|
corporation.issuedShares -= numShares;
|
||||||
player.loseMoney(numShares * buybackPrice, "corporation");
|
player.loseMoney(numShares * buybackPrice, "corporation");
|
||||||
@ -404,13 +404,13 @@ export function MakeProduct(
|
|||||||
if (corp.funds < designInvest + marketingInvest) {
|
if (corp.funds < designInvest + marketingInvest) {
|
||||||
throw new Error("You don't have enough company funds to make this large of an investment");
|
throw new Error("You don't have enough company funds to make this large of an investment");
|
||||||
}
|
}
|
||||||
let maxProducts = 3
|
let maxProducts = 3;
|
||||||
if (division.hasResearch("uPgrade: Capacity.II")) {
|
if (division.hasResearch("uPgrade: Capacity.II")) {
|
||||||
maxProducts = 5
|
maxProducts = 5;
|
||||||
} else if (division.hasResearch("uPgrade: Capacity.I")) {
|
} else if (division.hasResearch("uPgrade: Capacity.I")) {
|
||||||
maxProducts = 4
|
maxProducts = 4;
|
||||||
}
|
}
|
||||||
const products = division.products
|
const products = division.products;
|
||||||
if (Object.keys(products).length >= maxProducts) {
|
if (Object.keys(products).length >= maxProducts) {
|
||||||
throw new Error(`You are already at the max products (${maxProducts}) for division: ${division.name}!`);
|
throw new Error(`You are already at the max products (${maxProducts}) for division: ${division.name}!`);
|
||||||
}
|
}
|
||||||
@ -445,7 +445,13 @@ export function Research(division: IIndustry, researchName: string): void {
|
|||||||
division.researched[researchName] = true;
|
division.researched[researchName] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExportMaterial(divisionName: string, cityName: string, material: Material, amt: string, division?: Industry): void {
|
export function ExportMaterial(
|
||||||
|
divisionName: string,
|
||||||
|
cityName: string,
|
||||||
|
material: Material,
|
||||||
|
amt: string,
|
||||||
|
division?: Industry,
|
||||||
|
): void {
|
||||||
// Sanitize amt
|
// Sanitize amt
|
||||||
let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase();
|
let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase();
|
||||||
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, "");
|
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, "");
|
||||||
|
@ -435,10 +435,10 @@ export class Industry implements IIndustry {
|
|||||||
const popularityGain = corporation.getDreamSenseGain(),
|
const popularityGain = corporation.getDreamSenseGain(),
|
||||||
awarenessGain = popularityGain * 4;
|
awarenessGain = popularityGain * 4;
|
||||||
if (popularityGain > 0) {
|
if (popularityGain > 0) {
|
||||||
const awareness = this.awareness + (awarenessGain * marketCycles);
|
const awareness = this.awareness + awarenessGain * marketCycles;
|
||||||
this.awareness = Math.min(awareness, Number.MAX_VALUE);
|
this.awareness = Math.min(awareness, Number.MAX_VALUE);
|
||||||
|
|
||||||
const popularity = this.popularity + (popularityGain * marketCycles);
|
const popularity = this.popularity + popularityGain * marketCycles;
|
||||||
this.popularity = Math.min(popularity, Number.MAX_VALUE);
|
this.popularity = Math.min(popularity, Number.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1188,7 +1188,8 @@ export class Industry implements IIndustry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
product.maxsll = 0.5 *
|
product.maxsll =
|
||||||
|
0.5 *
|
||||||
Math.pow(product.rat, 0.65) *
|
Math.pow(product.rat, 0.65) *
|
||||||
marketFactor *
|
marketFactor *
|
||||||
corporation.getSalesMultiplier() *
|
corporation.getSalesMultiplier() *
|
||||||
@ -1281,10 +1282,10 @@ export class Industry implements IIndustry {
|
|||||||
case 1: {
|
case 1: {
|
||||||
//AdVert.Inc,
|
//AdVert.Inc,
|
||||||
const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
|
const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
|
||||||
const awareness = (this.awareness + (3 * advMult)) * (1.01 * advMult);
|
const awareness = (this.awareness + 3 * advMult) * (1.01 * advMult);
|
||||||
this.awareness = Math.min(awareness, Number.MAX_VALUE);
|
this.awareness = Math.min(awareness, Number.MAX_VALUE);
|
||||||
|
|
||||||
const popularity = (this.popularity + (1 * advMult)) * ((1 + getRandomInt(1, 3) / 100) * advMult);
|
const popularity = (this.popularity + 1 * advMult) * ((1 + getRandomInt(1, 3) / 100) * advMult);
|
||||||
this.popularity = Math.min(popularity, Number.MAX_VALUE);
|
this.popularity = Math.min(popularity, Number.MAX_VALUE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ export class OfficeSpace {
|
|||||||
let jobCount = this.employees.reduce((acc, employee) => (employee.pos === job ? acc + 1 : acc), 0);
|
let jobCount = this.employees.reduce((acc, employee) => (employee.pos === job ? acc + 1 : acc), 0);
|
||||||
|
|
||||||
for (const employee of this.employees) {
|
for (const employee of this.employees) {
|
||||||
if (jobCount == amount) return true
|
if (jobCount == amount) return true;
|
||||||
if (employee.pos === EmployeePositions.Unassigned && jobCount <= amount) {
|
if (employee.pos === EmployeePositions.Unassigned && jobCount <= amount) {
|
||||||
employee.pos = job;
|
employee.pos = job;
|
||||||
jobCount++;
|
jobCount++;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CityName } from './../../Locations/data/CityNames';
|
import { CityName } from "./../../Locations/data/CityNames";
|
||||||
const CyclesPerMarketCycle = 50;
|
const CyclesPerMarketCycle = 50;
|
||||||
const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
|
const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
|
||||||
export const CorporationConstants: {
|
export const CorporationConstants: {
|
||||||
@ -38,7 +38,14 @@ export const CorporationConstants: {
|
|||||||
CyclesPerIndustryStateCycle: CyclesPerMarketCycle / AllCorporationStates.length,
|
CyclesPerIndustryStateCycle: CyclesPerMarketCycle / AllCorporationStates.length,
|
||||||
SecsPerMarketCycle: CyclesPerMarketCycle / 5,
|
SecsPerMarketCycle: CyclesPerMarketCycle / 5,
|
||||||
|
|
||||||
Cities: [CityName.Aevum, CityName.Chongqing, CityName.Sector12, CityName.NewTokyo, CityName.Ishima, CityName.Volhaven],
|
Cities: [
|
||||||
|
CityName.Aevum,
|
||||||
|
CityName.Chongqing,
|
||||||
|
CityName.Sector12,
|
||||||
|
CityName.NewTokyo,
|
||||||
|
CityName.Ishima,
|
||||||
|
CityName.Volhaven,
|
||||||
|
],
|
||||||
|
|
||||||
WarehouseInitialCost: 5e9, //Initial purchase cost of warehouse
|
WarehouseInitialCost: 5e9, //Initial purchase cost of warehouse
|
||||||
WarehouseInitialSize: 100,
|
WarehouseInitialSize: 100,
|
||||||
@ -74,16 +81,6 @@ export const CorporationConstants: {
|
|||||||
"AI Cores",
|
"AI Cores",
|
||||||
"Real Estate",
|
"Real Estate",
|
||||||
],
|
],
|
||||||
FundingRoundShares: [
|
FundingRoundShares: [0.1, 0.35, 0.25, 0.2],
|
||||||
0.1,
|
FundingRoundMultiplier: [4, 3, 3, 2.5],
|
||||||
0.35,
|
|
||||||
0.25,
|
|
||||||
0.2
|
|
||||||
],
|
|
||||||
FundingRoundMultiplier: [
|
|
||||||
4,
|
|
||||||
3,
|
|
||||||
3,
|
|
||||||
2.5
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
@ -6,8 +6,8 @@ import { useCorporation } from "./Context";
|
|||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { BuyBackShares } from '../Actions';
|
import { BuyBackShares } from "../Actions";
|
||||||
import { dialogBoxCreate } from '../../ui/React/DialogBox';
|
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||||
import { KEY } from "../../utils/helpers/keyCodes";
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -40,9 +40,8 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
|
|||||||
function buy(): void {
|
function buy(): void {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
try {
|
try {
|
||||||
BuyBackShares(corp, player, shares)
|
BuyBackShares(corp, player, shares);
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
dialogBoxCreate(err + "");
|
dialogBoxCreate(err + "");
|
||||||
}
|
}
|
||||||
props.onClose();
|
props.onClose();
|
||||||
|
@ -30,7 +30,7 @@ export function CityTabs(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs variant="fullWidth" value={city} onChange={handleChange} sx={{ maxWidth: '65%' }}>
|
<Tabs variant="fullWidth" value={city} onChange={handleChange} sx={{ maxWidth: "65%" }}>
|
||||||
{Object.values(division.offices).map(
|
{Object.values(division.offices).map(
|
||||||
(office: OfficeSpace | 0) => office !== 0 && <Tab key={office.loc} label={office.loc} value={office.loc} />,
|
(office: OfficeSpace | 0) => office !== 0 && <Tab key={office.loc} label={office.loc} value={office.loc} />,
|
||||||
)}
|
)}
|
||||||
|
@ -38,7 +38,7 @@ export function CorporationRoot(): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Context.Corporation.Provider value={corporation}>
|
<Context.Corporation.Provider value={corporation}>
|
||||||
<Tabs variant="scrollable" value={divisionName} onChange={handleChange} sx={{ maxWidth: '65%' }} scrollButtons>
|
<Tabs variant="scrollable" value={divisionName} onChange={handleChange} sx={{ maxWidth: "65%" }} scrollButtons>
|
||||||
<Tab label={corporation.name} value={"Overview"} />
|
<Tab label={corporation.name} value={"Overview"} />
|
||||||
{corporation.divisions.map((div) => (
|
{corporation.divisions.map((div) => (
|
||||||
<Tab key={div.name} label={div.name} value={div.name} />
|
<Tab key={div.name} label={div.name} value={div.name} />
|
||||||
|
@ -56,9 +56,11 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
|
|||||||
return (
|
return (
|
||||||
<Modal open={props.open} onClose={props.onClose}>
|
<Modal open={props.open} onClose={props.onClose}>
|
||||||
<Typography>
|
<Typography>
|
||||||
Would you like to start a corporation? This will require $150b for registration and initial funding. {player.bitNodeN === 3 && (`This $150b
|
Would you like to start a corporation? This will require $150b for registration and initial funding.{" "}
|
||||||
|
{player.bitNodeN === 3 &&
|
||||||
|
`This $150b
|
||||||
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
|
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
|
||||||
shares`)}
|
shares`}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
If you would like to start one, please enter a name for your corporation below:
|
If you would like to start one, please enter a name for your corporation below:
|
||||||
|
@ -23,7 +23,9 @@ interface IProps {
|
|||||||
// Create a popup that lets the player manage exports
|
// Create a popup that lets the player manage exports
|
||||||
export function ExportModal(props: IProps): React.ReactElement {
|
export function ExportModal(props: IProps): React.ReactElement {
|
||||||
const corp = useCorporation();
|
const corp = useCorporation();
|
||||||
const possibleDivisions = corp.divisions.filter((division: IIndustry) => isRelevantMaterial(props.mat.name, division));
|
const possibleDivisions = corp.divisions.filter((division: IIndustry) =>
|
||||||
|
isRelevantMaterial(props.mat.name, division),
|
||||||
|
);
|
||||||
if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions.");
|
if (possibleDivisions.length === 0) throw new Error("Export popup created with no divisions.");
|
||||||
const defaultDivision = possibleDivisions[0];
|
const defaultDivision = possibleDivisions[0];
|
||||||
if (Object.keys(defaultDivision.warehouses).length === 0)
|
if (Object.keys(defaultDivision.warehouses).length === 0)
|
||||||
@ -93,7 +95,7 @@ export function ExportModal(props: IProps): React.ReactElement {
|
|||||||
<MenuItem key={division.name} value={division.name}>
|
<MenuItem key={division.name} value={division.name}>
|
||||||
{division.name}
|
{division.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<Select onChange={onCityChange} value={city}>
|
<Select onChange={onCityChange} value={city}>
|
||||||
{possibleCities.map((cityName: string) => {
|
{possibleCities.map((cityName: string) => {
|
||||||
|
@ -17,7 +17,11 @@ interface IProps {
|
|||||||
export function FindInvestorsModal(props: IProps): React.ReactElement {
|
export function FindInvestorsModal(props: IProps): React.ReactElement {
|
||||||
const corporation = useCorporation();
|
const corporation = useCorporation();
|
||||||
const val = corporation.determineValuation();
|
const val = corporation.determineValuation();
|
||||||
if (corporation.fundingRound >= CorporationConstants.FundingRoundShares.length || corporation.fundingRound >= CorporationConstants.FundingRoundMultiplier.length) return <></>;
|
if (
|
||||||
|
corporation.fundingRound >= CorporationConstants.FundingRoundShares.length ||
|
||||||
|
corporation.fundingRound >= CorporationConstants.FundingRoundMultiplier.length
|
||||||
|
)
|
||||||
|
return <></>;
|
||||||
const percShares = CorporationConstants.FundingRoundShares[corporation.fundingRound];
|
const percShares = CorporationConstants.FundingRoundShares[corporation.fundingRound];
|
||||||
const roundMultiplier = CorporationConstants.FundingRoundMultiplier[corporation.fundingRound];
|
const roundMultiplier = CorporationConstants.FundingRoundMultiplier[corporation.fundingRound];
|
||||||
const funding = val * percShares * roundMultiplier;
|
const funding = val * percShares * roundMultiplier;
|
||||||
|
@ -18,7 +18,5 @@ export function IndustryProductEquation(props: IProps): React.ReactElement {
|
|||||||
prod.push("Products");
|
prod.push("Products");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <MathJaxWrapper>{"\\(" + reqs.join("+") + `\\Rightarrow ` + prod.join("+") + "\\)"}</MathJaxWrapper>;
|
||||||
<MathJaxWrapper>{"\\(" + reqs.join("+") + `\\Rightarrow ` + prod.join("+") + "\\)"}</MathJaxWrapper>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,9 @@ interface IProps {
|
|||||||
const useStyles = makeStyles(() =>
|
const useStyles = makeStyles(() =>
|
||||||
createStyles({
|
createStyles({
|
||||||
retainHeight: {
|
retainHeight: {
|
||||||
minHeight: '3em',
|
minHeight: "3em",
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
function WarehouseRoot(props: IProps): React.ReactElement {
|
function WarehouseRoot(props: IProps): React.ReactElement {
|
||||||
@ -127,27 +127,53 @@ function WarehouseRoot(props: IProps): React.ReactElement {
|
|||||||
const mat = props.warehouse.materials[matName];
|
const mat = props.warehouse.materials[matName];
|
||||||
if (!MaterialSizes.hasOwnProperty(matName)) continue;
|
if (!MaterialSizes.hasOwnProperty(matName)) continue;
|
||||||
if (mat.qty === 0) continue;
|
if (mat.qty === 0) continue;
|
||||||
breakdownItems.push(<>{matName}: {numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0")}</>);
|
breakdownItems.push(
|
||||||
|
<>
|
||||||
|
{matName}: {numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0")}
|
||||||
|
</>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const prodName of Object.keys(division.products)) {
|
for (const prodName of Object.keys(division.products)) {
|
||||||
const prod = division.products[prodName];
|
const prod = division.products[prodName];
|
||||||
if (prod === undefined) continue;
|
if (prod === undefined) continue;
|
||||||
breakdownItems.push(<>{prodName}: {numeralWrapper.format(prod.data[props.warehouse.loc][0] * prod.siz, "0,0.0")}</>);
|
breakdownItems.push(
|
||||||
|
<>
|
||||||
|
{prodName}: {numeralWrapper.format(prod.data[props.warehouse.loc][0] * prod.siz, "0,0.0")}
|
||||||
|
</>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let breakdown;
|
let breakdown;
|
||||||
if (breakdownItems && breakdownItems.length > 0) {
|
if (breakdownItems && breakdownItems.length > 0) {
|
||||||
breakdown = breakdownItems.reduce(
|
breakdown = breakdownItems.reduce(
|
||||||
(previous: JSX.Element, current: JSX.Element): JSX.Element => previous && <>{previous}<br />{current}</> || <>{current}</>);
|
(previous: JSX.Element, current: JSX.Element): JSX.Element =>
|
||||||
|
(previous && (
|
||||||
|
<>
|
||||||
|
{previous}
|
||||||
|
<br />
|
||||||
|
{current}
|
||||||
|
</>
|
||||||
|
)) || <>{current}</>,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
breakdown = <>No items in storage.</>
|
breakdown = <>No items in storage.</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper>
|
<Paper>
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Tooltip title={props.warehouse.sizeUsed !== 0 ? <Typography><>{breakdown}</></Typography> : ""}>
|
<Tooltip
|
||||||
|
title={
|
||||||
|
props.warehouse.sizeUsed !== 0 ? (
|
||||||
|
<Typography>
|
||||||
|
<>{breakdown}</>
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
<Typography color={props.warehouse.sizeUsed >= props.warehouse.size ? "error" : "primary"}>
|
<Typography color={props.warehouse.sizeUsed >= props.warehouse.size ? "error" : "primary"}>
|
||||||
Storage: {numeralWrapper.formatBigNumber(props.warehouse.sizeUsed)} /{" "}
|
Storage: {numeralWrapper.formatBigNumber(props.warehouse.sizeUsed)} /{" "}
|
||||||
{numeralWrapper.formatBigNumber(props.warehouse.size)}
|
{numeralWrapper.formatBigNumber(props.warehouse.size)}
|
||||||
|
@ -112,7 +112,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper>
|
<Paper>
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: '2fr 1fr', m: '5px' }}>
|
<Box sx={{ display: "grid", gridTemplateColumns: "2fr 1fr", m: "5px" }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@ -149,17 +149,17 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ "& button": { width: '100%' } }}>
|
<Box sx={{ "& button": { width: "100%" } }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={tutorial ? <Typography>Purchase your required materials to get production started!</Typography> : ""}
|
title={tutorial ? <Typography>Purchase your required materials to get production started!</Typography> : ""}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color={tutorial ? "error" : "primary"}
|
color={tutorial ? "error" : "primary"}
|
||||||
onClick={() => setPurchaseMaterialOpen(true)}
|
onClick={() => setPurchaseMaterialOpen(true)}
|
||||||
disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}
|
disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}
|
||||||
>
|
>
|
||||||
{purchaseButtonText}
|
{purchaseButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<PurchaseMaterialModal
|
<PurchaseMaterialModal
|
||||||
mat={mat}
|
mat={mat}
|
||||||
|
@ -89,7 +89,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
|
|||||||
<StatsTable rows={multRows} />
|
<StatsTable rows={multRows} />
|
||||||
<br />
|
<br />
|
||||||
<BonusTime />
|
<BonusTime />
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', width: 'fit-content' }}>
|
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", width: "fit-content" }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<Typography>
|
<Typography>
|
||||||
@ -200,8 +200,8 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
|
|||||||
const sellSharesTooltip = sellSharesOnCd
|
const sellSharesTooltip = sellSharesOnCd
|
||||||
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
|
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
|
||||||
: "Sell your shares in the company. The money earned from selling your " +
|
: "Sell your shares in the company. The money earned from selling your " +
|
||||||
"shares goes into your personal account, not the Corporation's. " +
|
"shares goes into your personal account, not the Corporation's. " +
|
||||||
"This is one of the only ways to profit from your business venture.";
|
"This is one of the only ways to profit from your business venture.";
|
||||||
|
|
||||||
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
|
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
|
||||||
const issueNewSharesTooltip = issueNewSharesOnCd
|
const issueNewSharesTooltip = issueNewSharesOnCd
|
||||||
|
@ -16,7 +16,7 @@ import Box from "@mui/material/Box";
|
|||||||
import Collapse from "@mui/material/Collapse";
|
import Collapse from "@mui/material/Collapse";
|
||||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
|
|
||||||
interface INodeProps {
|
interface INodeProps {
|
||||||
n: Node | null;
|
n: Node | null;
|
||||||
@ -43,8 +43,8 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
|
|||||||
|
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
`Researched ${n.text}. It may take a market cycle ` +
|
`Researched ${n.text}. It may take a market cycle ` +
|
||||||
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
|
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
|
||||||
`the Research apply.`,
|
`the Research apply.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,19 +66,23 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
|
|||||||
>
|
>
|
||||||
{ele}
|
{ele}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const but = (
|
const but = (
|
||||||
<Box>
|
<Box>
|
||||||
{wrapInTooltip(
|
{wrapInTooltip(
|
||||||
<span>
|
<span>
|
||||||
<Button color={color} disabled={disabled && !n.researched} onClick={research}
|
<Button
|
||||||
style={{ width: '100%', textAlign: 'left', justifyContent: 'unset' }}
|
color={color}
|
||||||
|
disabled={disabled && !n.researched}
|
||||||
|
onClick={research}
|
||||||
|
style={{ width: "100%", textAlign: "left", justifyContent: "unset" }}
|
||||||
>
|
>
|
||||||
{n.researched && (<CheckIcon sx={{ mr: 1 }} />)}{n.text}
|
{n.researched && <CheckIcon sx={{ mr: 1 }} />}
|
||||||
|
{n.text}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>,
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -87,20 +91,29 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box display="flex" sx={{ border: '1px solid ' + Settings.theme.well }}>
|
<Box display="flex" sx={{ border: "1px solid " + Settings.theme.well }}>
|
||||||
{wrapInTooltip(
|
{wrapInTooltip(
|
||||||
<span style={{ width: '100%' }}>
|
<span style={{ width: "100%" }}>
|
||||||
<Button color={color} disabled={disabled && !n.researched} onClick={research} sx={{
|
<Button
|
||||||
width: '100%',
|
color={color}
|
||||||
textAlign: 'left',
|
disabled={disabled && !n.researched}
|
||||||
justifyContent: 'unset',
|
onClick={research}
|
||||||
borderColor: Settings.theme.button
|
sx={{
|
||||||
}}>
|
width: "100%",
|
||||||
{n.researched && (<CheckIcon sx={{ mr: 1 }} />)}{n.text}
|
textAlign: "left",
|
||||||
|
justifyContent: "unset",
|
||||||
|
borderColor: Settings.theme.button,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{n.researched && <CheckIcon sx={{ mr: 1 }} />}
|
||||||
|
{n.text}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>,
|
||||||
)}
|
)}
|
||||||
<Button onClick={() => setOpen((old) => !old)} sx={{ borderColor: Settings.theme.button, minWidth: 'fit-content' }}>
|
<Button
|
||||||
|
onClick={() => setOpen((old) => !old)}
|
||||||
|
sx={{ borderColor: Settings.theme.button, minWidth: "fit-content" }}
|
||||||
|
>
|
||||||
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -51,7 +51,7 @@ export function SellSharesModal(props: IProps): React.ReactElement {
|
|||||||
function sell(): void {
|
function sell(): void {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
try {
|
try {
|
||||||
const profit = SellShares(corp, player, shares)
|
const profit = SellShares(corp, player, shares);
|
||||||
props.onClose();
|
props.onClose();
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
<>
|
<>
|
||||||
@ -65,7 +65,6 @@ export function SellSharesModal(props: IProps): React.ReactElement {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
dialogBoxCreate(err + "");
|
dialogBoxCreate(err + "");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||||
|
@ -35,17 +35,17 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
|
|||||||
if (cost === null || isNaN(cost) || cost < 0) {
|
if (cost === null || isNaN(cost) || cost < 0) {
|
||||||
dialogBoxCreate("Invalid value entered");
|
dialogBoxCreate("Invalid value entered");
|
||||||
} else if (!canParty) {
|
} else if (!canParty) {
|
||||||
dialogBoxCreate("You don't have enough company funds to throw a party!");
|
dialogBoxCreate("You don't have enough company funds to throw a party!");
|
||||||
} else {
|
} else {
|
||||||
const mult = ThrowParty(corp, props.office, cost);
|
const mult = ThrowParty(corp, props.office, cost);
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
"You threw a party for the office! The morale and happiness " +
|
"You threw a party for the office! The morale and happiness " +
|
||||||
"of each employee increased by " +
|
"of each employee increased by " +
|
||||||
numeralWrapper.formatPercentage(mult - 1),
|
numeralWrapper.formatPercentage(mult - 1),
|
||||||
);
|
);
|
||||||
props.rerender();
|
props.rerender();
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function EffectText(): React.ReactElement {
|
function EffectText(): React.ReactElement {
|
||||||
|
@ -10,7 +10,7 @@ export interface IActiveFragmentParams {
|
|||||||
|
|
||||||
export class ActiveFragment {
|
export class ActiveFragment {
|
||||||
id: number;
|
id: number;
|
||||||
avgCharge: number;
|
highestCharge: number;
|
||||||
numCharge: number;
|
numCharge: number;
|
||||||
rotation: number;
|
rotation: number;
|
||||||
x: number;
|
x: number;
|
||||||
@ -21,16 +21,16 @@ export class ActiveFragment {
|
|||||||
this.id = params.fragment.id;
|
this.id = params.fragment.id;
|
||||||
this.x = params.x;
|
this.x = params.x;
|
||||||
this.y = params.y;
|
this.y = params.y;
|
||||||
this.avgCharge = 0;
|
this.highestCharge = 0;
|
||||||
this.numCharge = 0;
|
this.numCharge = 0;
|
||||||
this.rotation = params.rotation;
|
this.rotation = params.rotation;
|
||||||
} else {
|
} else {
|
||||||
this.id = -1;
|
this.id = -1;
|
||||||
this.x = -1;
|
this.x = -1;
|
||||||
this.y = -1;
|
this.y = -1;
|
||||||
this.avgCharge = -1;
|
this.highestCharge = 0;
|
||||||
this.numCharge = -1;
|
this.numCharge = 0;
|
||||||
this.rotation = -1;
|
this.rotation = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ export class ActiveFragment {
|
|||||||
const fragment = FragmentById(this.id);
|
const fragment = FragmentById(this.id);
|
||||||
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
|
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
|
||||||
const c = new ActiveFragment({ x: this.x, y: this.y, rotation: this.rotation, fragment: fragment });
|
const c = new ActiveFragment({ x: this.x, y: this.y, rotation: this.rotation, fragment: fragment });
|
||||||
c.avgCharge = this.avgCharge;
|
c.highestCharge = this.highestCharge;
|
||||||
c.numCharge = this.numCharge;
|
c.numCharge = this.numCharge;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export function Effect(tpe: FragmentType): string {
|
|||||||
return "+x% hacknet production";
|
return "+x% hacknet production";
|
||||||
}
|
}
|
||||||
case FragmentType.HacknetCost: {
|
case FragmentType.HacknetCost: {
|
||||||
return "x% cheaper hacknet cost";
|
return "-x% cheaper hacknet cost";
|
||||||
}
|
}
|
||||||
case FragmentType.Rep: {
|
case FragmentType.Rep: {
|
||||||
return "+x% reputation from factions and companies";
|
return "+x% reputation from factions and companies";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FactionNames } from '../Faction/data/FactionNames';
|
import { FactionNames } from "../Faction/data/FactionNames";
|
||||||
import { Fragment } from "./Fragment";
|
import { Fragment } from "./Fragment";
|
||||||
import { ActiveFragment } from "./ActiveFragment";
|
import { ActiveFragment } from "./ActiveFragment";
|
||||||
import { FragmentType } from "./FragmentType";
|
import { FragmentType } from "./FragmentType";
|
||||||
@ -19,19 +19,23 @@ export class StaneksGift implements IStaneksGift {
|
|||||||
fragments: ActiveFragment[] = [];
|
fragments: ActiveFragment[] = [];
|
||||||
|
|
||||||
baseSize(): number {
|
baseSize(): number {
|
||||||
return StanekConstants.BaseSize + BitNodeMultipliers.StaneksGiftExtraSize + Player.sourceFileLvl(13)
|
return StanekConstants.BaseSize + BitNodeMultipliers.StaneksGiftExtraSize + Player.sourceFileLvl(13);
|
||||||
}
|
}
|
||||||
|
|
||||||
width(): number {
|
width(): number {
|
||||||
return Math.min(Math.floor(this.baseSize() / 2 + 1),StanekConstants.MaxSize);
|
return Math.max(2, Math.min(Math.floor(this.baseSize() / 2 + 1), StanekConstants.MaxSize));
|
||||||
}
|
}
|
||||||
height(): number {
|
height(): number {
|
||||||
return Math.min(Math.floor(this.baseSize() / 2 + 0.6),StanekConstants.MaxSize);
|
return Math.max(3, Math.min(Math.floor(this.baseSize() / 2 + 0.6), StanekConstants.MaxSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
charge(player: IPlayer, af: ActiveFragment, threads: number): void {
|
charge(player: IPlayer, af: ActiveFragment, threads: number): void {
|
||||||
af.avgCharge = (af.numCharge * af.avgCharge + threads) / (af.numCharge + 1);
|
if (threads > af.highestCharge) {
|
||||||
af.numCharge++;
|
af.numCharge = (af.highestCharge * af.numCharge) / threads + 1;
|
||||||
|
af.highestCharge = threads;
|
||||||
|
} else {
|
||||||
|
af.numCharge += threads / af.highestCharge;
|
||||||
|
}
|
||||||
|
|
||||||
const cotmg = Factions[FactionNames.ChurchOfTheMachineGod];
|
const cotmg = Factions[FactionNames.ChurchOfTheMachineGod];
|
||||||
cotmg.playerReputation += (player.faction_rep_mult * (Math.pow(threads, 0.95) * (cotmg.favor + 100))) / 1000;
|
cotmg.playerReputation += (player.faction_rep_mult * (Math.pow(threads, 0.95) * (cotmg.favor + 100))) / 1000;
|
||||||
@ -66,7 +70,7 @@ export class StaneksGift implements IStaneksGift {
|
|||||||
for (const neighboor of neighboors) {
|
for (const neighboor of neighboors) {
|
||||||
boost *= neighboor.fragment().power;
|
boost *= neighboor.fragment().power;
|
||||||
}
|
}
|
||||||
return CalculateEffect(fragment.avgCharge, fragment.numCharge, fragment.fragment().power, boost);
|
return CalculateEffect(fragment.highestCharge, fragment.numCharge, fragment.fragment().power, boost);
|
||||||
}
|
}
|
||||||
|
|
||||||
canPlace(rootX: number, rootY: number, rotation: number, fragment: Fragment): boolean {
|
canPlace(rootX: number, rootY: number, rotation: number, fragment: Fragment): boolean {
|
||||||
@ -126,7 +130,7 @@ export class StaneksGift implements IStaneksGift {
|
|||||||
|
|
||||||
clearCharge(): void {
|
clearCharge(): void {
|
||||||
this.fragments.forEach((f) => {
|
this.fragments.forEach((f) => {
|
||||||
f.avgCharge = 0;
|
f.highestCharge = 0;
|
||||||
f.numCharge = 0;
|
f.numCharge = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,5 @@ export const StanekConstants: {
|
|||||||
} = {
|
} = {
|
||||||
RAMBonus: 0.1,
|
RAMBonus: 0.1,
|
||||||
BaseSize: 9,
|
BaseSize: 9,
|
||||||
MaxSize: 25
|
MaxSize: 25,
|
||||||
};
|
};
|
||||||
|
@ -48,16 +48,18 @@ export function FragmentInspector(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
const f = props.fragment.fragment();
|
const f = props.fragment.fragment();
|
||||||
|
|
||||||
let charge = `${numeralWrapper.formatStaneksGiftCharge(props.fragment.avgCharge)} avg. * ${
|
let charge = numeralWrapper.formatStaneksGiftCharge(props.fragment.highestCharge * props.fragment.numCharge);
|
||||||
props.fragment.numCharge
|
|
||||||
} times`;
|
|
||||||
let effect = "N/A";
|
let effect = "N/A";
|
||||||
// Boosters and cooling don't deal with heat.
|
// Boosters and cooling don't deal with heat.
|
||||||
if ([FragmentType.Booster, FragmentType.None, FragmentType.Delete].includes(f.type)) {
|
if ([FragmentType.Booster, FragmentType.None, FragmentType.Delete].includes(f.type)) {
|
||||||
charge = "N/A";
|
charge = "N/A";
|
||||||
effect = `${f.power}x adjacent fragment power`;
|
effect = `${f.power}x adjacent fragment power`;
|
||||||
} else {
|
} else if (Effect(f.type).includes("+x%")) {
|
||||||
effect = Effect(f.type).replace(/-*x%/, numeralWrapper.formatPercentage(props.gift.effect(props.fragment) - 1));
|
effect = Effect(f.type).replace(/-*x%/, numeralWrapper.formatPercentage(props.gift.effect(props.fragment) - 1));
|
||||||
|
} else if (Effect(f.type).includes("-x%")) {
|
||||||
|
const effectAmt = props.gift.effect(props.fragment);
|
||||||
|
const perc = numeralWrapper.formatPercentage(1 - 1 / effectAmt);
|
||||||
|
effect = Effect(f.type).replace(/-x%/, perc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -107,7 +107,9 @@ export function buyAllDarkwebItems(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cost > Player.money) {
|
if (cost > Player.money) {
|
||||||
Terminal.error("Not enough money to purchase remaining programs, " + numeralWrapper.formatMoney(cost) + " required");
|
Terminal.error(
|
||||||
|
"Not enough money to purchase remaining programs, " + numeralWrapper.formatMoney(cost) + " required",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ import Typography from "@mui/material/Typography";
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||||
import { Tooltip } from "@mui/material";
|
import { Tooltip } from "@mui/material";
|
||||||
import LockIcon from '@mui/icons-material/Lock';
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
||||||
|
|
||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { achievements } from "../../Achievements/Achievements";
|
import { achievements } from "../../Achievements/Achievements";
|
||||||
@ -21,26 +21,26 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Achievements(props: IProps): React.ReactElement {
|
export function Achievements(props: IProps): React.ReactElement {
|
||||||
const [playerAchievement, setPlayerAchievements] = useState(props.player.achievements.map(m => m.ID));
|
const [playerAchievement, setPlayerAchievements] = useState(props.player.achievements.map((m) => m.ID));
|
||||||
|
|
||||||
function grantAchievement(id: string): void {
|
function grantAchievement(id: string): void {
|
||||||
props.player.giveAchievement(id);
|
props.player.giveAchievement(id);
|
||||||
setPlayerAchievements(props.player.achievements.map(m => m.ID));
|
setPlayerAchievements(props.player.achievements.map((m) => m.ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
function grantAllAchievements(): void {
|
function grantAllAchievements(): void {
|
||||||
Object.values(achievements).forEach(a => props.player.giveAchievement(a.ID));
|
Object.values(achievements).forEach((a) => props.player.giveAchievement(a.ID));
|
||||||
setPlayerAchievements(props.player.achievements.map(m => m.ID));
|
setPlayerAchievements(props.player.achievements.map((m) => m.ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAchievement(id: string): void {
|
function removeAchievement(id: string): void {
|
||||||
props.player.achievements = props.player.achievements.filter(a => a.ID !== id);
|
props.player.achievements = props.player.achievements.filter((a) => a.ID !== id);
|
||||||
setPlayerAchievements(props.player.achievements.map(m => m.ID));
|
setPlayerAchievements(props.player.achievements.map((m) => m.ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAchievements(): void {
|
function clearAchievements(): void {
|
||||||
props.player.achievements = [];
|
props.player.achievements = [];
|
||||||
setPlayerAchievements(props.player.achievements.map(m => m.ID));
|
setPlayerAchievements(props.player.achievements.map((m) => m.ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableEngine(): void {
|
function disableEngine(): void {
|
||||||
@ -48,7 +48,7 @@ export function Achievements(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function enableEngine(): void {
|
function enableEngine(): void {
|
||||||
props.engine.Counters.achievementsCounter = 0
|
props.engine.Counters.achievementsCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -60,8 +60,7 @@ export function Achievements(props: IProps): React.ReactElement {
|
|||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td></td>
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<Typography>Achievements:</Typography>
|
<Typography>Achievements:</Typography>
|
||||||
</td>
|
</td>
|
||||||
@ -76,30 +75,40 @@ export function Achievements(props: IProps): React.ReactElement {
|
|||||||
</tr>
|
</tr>
|
||||||
{Object.values(achievements).map((i) => {
|
{Object.values(achievements).map((i) => {
|
||||||
const achieved = playerAchievement.includes(i.ID);
|
const achieved = playerAchievement.includes(i.ID);
|
||||||
return <tr key={"ach-" + i.ID}>
|
return (
|
||||||
<td>
|
<tr key={"ach-" + i.ID}>
|
||||||
{achieved ? (
|
<td>
|
||||||
<Tooltip title="Achieved">
|
{achieved ? (
|
||||||
<LockOpenIcon color="primary" />
|
<Tooltip title="Achieved">
|
||||||
|
<LockOpenIcon color="primary" />
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip title="Locked">
|
||||||
|
<LockIcon color="secondary" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
{i.ID}
|
||||||
|
<br />
|
||||||
|
{i.Description}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography color={achieved ? "primary" : "secondary"}>{i.Name}:</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
</td>
|
||||||
<Tooltip title="Locked">
|
<td>
|
||||||
<LockIcon color="secondary" />
|
<ButtonGroup>
|
||||||
</Tooltip>
|
<Button onClick={() => grantAchievement(i.ID)}>Grant</Button>
|
||||||
)}
|
<Button onClick={() => removeAchievement(i.ID)}>Clear</Button>
|
||||||
</td>
|
</ButtonGroup>
|
||||||
<td>
|
</td>
|
||||||
<Tooltip title={<>{i.ID}<br />{i.Description}</>}>
|
</tr>
|
||||||
<Typography color={achieved ? 'primary': 'secondary'}>{i.Name}:</Typography>
|
);
|
||||||
</Tooltip>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ButtonGroup>
|
|
||||||
<Button onClick={() => grantAchievement(i.ID)}>Grant</Button>
|
|
||||||
<Button onClick={() => removeAchievement(i.ID)}>Clear</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -37,7 +37,7 @@ export function Factions(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function receiveAllInvites(): void {
|
function receiveAllInvites(): void {
|
||||||
Object.values(FactionNames).forEach(faction => props.player.receiveInvite(faction))
|
Object.values(FactionNames).forEach((faction) => props.player.receiveInvite(faction));
|
||||||
}
|
}
|
||||||
|
|
||||||
function modifyFactionRep(modifier: number): (x: number) => void {
|
function modifyFactionRep(modifier: number): (x: number) => void {
|
||||||
|
@ -46,7 +46,7 @@ export function General(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) throw new ReferenceError('Manually thrown error');
|
if (error) throw new ReferenceError("Manually thrown error");
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -27,20 +27,20 @@ export function Stanek(): React.ReactElement {
|
|||||||
|
|
||||||
function addCharge(): void {
|
function addCharge(): void {
|
||||||
staneksGift.fragments.forEach((f) => {
|
staneksGift.fragments.forEach((f) => {
|
||||||
f.avgCharge = 1e21;
|
f.highestCharge = 1e21;
|
||||||
f.numCharge = 1e21;
|
f.numCharge = 1e21;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function modCharge(modify: number): (x: number) => void {
|
function modCharge(modify: number): (x: number) => void {
|
||||||
return function (cycles: number): void {
|
return function (cycles: number): void {
|
||||||
staneksGift.fragments.forEach((f) => (f.avgCharge += cycles * modify));
|
staneksGift.fragments.forEach((f) => (f.highestCharge += cycles * modify));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetCharge(): void {
|
function resetCharge(): void {
|
||||||
staneksGift.fragments.forEach((f) => {
|
staneksGift.fragments.forEach((f) => {
|
||||||
f.avgCharge = 0;
|
f.highestCharge = 0;
|
||||||
f.numCharge = 0;
|
f.numCharge = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { CONSTANTS } from "../Constants";
|
|||||||
import { Faction } from "./Faction";
|
import { Faction } from "./Faction";
|
||||||
import { Factions } from "./Factions";
|
import { Factions } from "./Factions";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
import { Settings } from "../Settings/Settings";
|
import { Settings } from "../Settings/Settings";
|
||||||
import {
|
import {
|
||||||
getHackingWorkRepGain,
|
getHackingWorkRepGain,
|
||||||
@ -19,6 +20,7 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
|||||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||||
import { InvitationEvent } from "./ui/InvitationModal";
|
import { InvitationEvent } from "./ui/InvitationModal";
|
||||||
import { FactionNames } from "./data/FactionNames";
|
import { FactionNames } from "./data/FactionNames";
|
||||||
|
import { SFC32RNG } from "../Casino/RNG";
|
||||||
|
|
||||||
export function inviteToFaction(faction: Faction): void {
|
export function inviteToFaction(faction: Faction): void {
|
||||||
Player.receiveInvite(faction.name);
|
Player.receiveInvite(faction.name);
|
||||||
@ -32,9 +34,8 @@ export function joinFaction(faction: Faction): void {
|
|||||||
if (faction.isMember) return;
|
if (faction.isMember) return;
|
||||||
faction.isMember = true;
|
faction.isMember = true;
|
||||||
Player.factions.push(faction.name);
|
Player.factions.push(faction.name);
|
||||||
const allFactions = Object.values(FactionNames).map(faction => faction as string)
|
const allFactions = Object.values(FactionNames).map((faction) => faction as string);
|
||||||
Player.factions.sort((a, b) =>
|
Player.factions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
|
||||||
allFactions.indexOf(a) - allFactions.indexOf(b));
|
|
||||||
const factionInfo = faction.getInfo();
|
const factionInfo = faction.getInfo();
|
||||||
|
|
||||||
//Determine what factions you are banned from now that you have joined this faction
|
//Determine what factions you are banned from now that you have joined this faction
|
||||||
@ -134,18 +135,18 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
|
|||||||
} else if (!Settings.SuppressBuyAugmentationConfirmation) {
|
} else if (!Settings.SuppressBuyAugmentationConfirmation) {
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
"You purchased " +
|
"You purchased " +
|
||||||
aug.name +
|
aug.name +
|
||||||
". Its enhancements will not take " +
|
". Its enhancements will not take " +
|
||||||
"effect until they are installed. To install your augmentations, go to the " +
|
"effect until they are installed. To install your augmentations, go to the " +
|
||||||
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
|
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
|
||||||
"augmentations will now be more expensive.",
|
"augmentations will now be more expensive.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
"Hmm, something went wrong when trying to purchase an Augmentation. " +
|
"Hmm, something went wrong when trying to purchase an Augmentation. " +
|
||||||
"Please report this to the game developer with an explanation of how to " +
|
"Please report this to the game developer with an explanation of how to " +
|
||||||
"reproduce this.",
|
"reproduce this.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@ -193,3 +194,43 @@ export function processPassiveFactionRepGain(numCycles: number): void {
|
|||||||
faction.playerReputation += rate * numCycles * Player.faction_rep_mult * BitNodeMultipliers.FactionPassiveRepGain;
|
faction.playerReputation += rate * numCycles * Player.faction_rep_mult * BitNodeMultipliers.FactionPassiveRepGain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Faction): string[] => {
|
||||||
|
// If player has a gang with this faction, return (almost) all augmentations
|
||||||
|
if (player.hasGangWith(faction.name)) {
|
||||||
|
let augs = Object.values(Augmentations);
|
||||||
|
|
||||||
|
// Remove special augs
|
||||||
|
augs = augs.filter((a) => !a.isSpecial);
|
||||||
|
|
||||||
|
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor];
|
||||||
|
|
||||||
|
if (player.bitNodeN !== 2) {
|
||||||
|
// TRP is not available outside of BN2 for Gangs
|
||||||
|
blacklist.push(AugmentationNames.TheRedPill);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);
|
||||||
|
// Remove faction-unique augs that don't belong to this faction
|
||||||
|
const uniqueFilter = (a: Augmentation): boolean => {
|
||||||
|
// Keep all the non-unique one
|
||||||
|
if (a.factions.length > 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Keep all the ones that this faction has anyway.
|
||||||
|
if (faction.augmentations.includes(a.name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rng() >= 1 - BitNodeMultipliers.GangUniqueAugs;
|
||||||
|
};
|
||||||
|
augs = augs.filter(uniqueFilter);
|
||||||
|
|
||||||
|
// Remove blacklisted augs
|
||||||
|
augs = augs.filter((a) => !blacklist.includes(a.name));
|
||||||
|
|
||||||
|
return augs.map((a) => a.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return faction.augmentations.slice();
|
||||||
|
};
|
||||||
|
@ -85,8 +85,6 @@ export class FactionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of all factions and associated info to them.
|
* A map of all factions and associated info to them.
|
||||||
*/
|
*/
|
||||||
@ -143,8 +141,9 @@ export const FactionInfos: IMap<FactionInfo> = {
|
|||||||
[FactionNames.ECorp]: new FactionInfo(
|
[FactionNames.ECorp]: new FactionInfo(
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
{FactionNames.ECorp}'s mission is simple: to connect the world of today with the technology of tomorrow. With our wide range of
|
{FactionNames.ECorp}'s mission is simple: to connect the world of today with the technology of tomorrow. With
|
||||||
Internet-related software and commercial hardware, {FactionNames.ECorp} makes the world's information universally accessible.
|
our wide range of Internet-related software and commercial hardware, {FactionNames.ECorp} makes the world's
|
||||||
|
information universally accessible.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
@ -159,12 +158,13 @@ export const FactionInfos: IMap<FactionInfo> = {
|
|||||||
[FactionNames.MegaCorp]: new FactionInfo(
|
[FactionNames.MegaCorp]: new FactionInfo(
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
{FactionNames.MegaCorp} does what no other dares to do. We imagine. We create. We invent. We create what others have never even
|
{FactionNames.MegaCorp} does what no other dares to do. We imagine. We create. We invent. We create what others
|
||||||
dreamed of. Our work fills the world's needs for food, water, power, and transportation on an unprecedented
|
have never even dreamed of. Our work fills the world's needs for food, water, power, and transportation on an
|
||||||
scale, in ways that no other company can.
|
unprecedented scale, in ways that no other company can.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
In our labs and factories and on the ground with customers, {FactionNames.MegaCorp} is ushering in a new era for the world.
|
In our labs and factories and on the ground with customers, {FactionNames.MegaCorp} is ushering in a new era for
|
||||||
|
the world.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
@ -194,7 +194,16 @@ export const FactionInfos: IMap<FactionInfo> = {
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|
|
||||||
[FactionNames.BladeIndustries]: new FactionInfo(<>Augmentation is Salvation.</>, [], true, true, true, true, false, true),
|
[FactionNames.BladeIndustries]: new FactionInfo(
|
||||||
|
<>Augmentation is Salvation.</>,
|
||||||
|
[],
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
|
||||||
[FactionNames.NWO]: new FactionInfo(
|
[FactionNames.NWO]: new FactionInfo(
|
||||||
(
|
(
|
||||||
@ -486,12 +495,39 @@ export const FactionInfos: IMap<FactionInfo> = {
|
|||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
|
||||||
[FactionNames.SlumSnakes]: new FactionInfo(<>{FactionNames.SlumSnakes} rule!</>, [], false, false, true, true, false, false),
|
[FactionNames.SlumSnakes]: new FactionInfo(
|
||||||
|
<>{FactionNames.SlumSnakes} rule!</>,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
|
||||||
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
|
// Earlygame factions - factions the player will prestige with early on that don't belong in other categories.
|
||||||
[FactionNames.Netburners]: new FactionInfo(<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>, [], true, true, false, false, false, false),
|
[FactionNames.Netburners]: new FactionInfo(
|
||||||
|
<>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}</>,
|
||||||
|
[],
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
|
||||||
[FactionNames.TianDiHui]: new FactionInfo(<>Obey Heaven and work righteously.</>, [], true, true, false, true, false, false),
|
[FactionNames.TianDiHui]: new FactionInfo(
|
||||||
|
<>Obey Heaven and work righteously.</>,
|
||||||
|
[],
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
|
||||||
[FactionNames.CyberSec]: new FactionInfo(
|
[FactionNames.CyberSec]: new FactionInfo(
|
||||||
(
|
(
|
||||||
@ -517,7 +553,8 @@ export const FactionInfos: IMap<FactionInfo> = {
|
|||||||
It's too bad they won't live. But then again, who does?
|
It's too bad they won't live. But then again, who does?
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions. Completing {FactionNames.Bladeburners}
|
Note that for this faction, reputation can only be gained through {FactionNames.Bladeburners} actions.
|
||||||
|
Completing {FactionNames.Bladeburners}
|
||||||
contracts/operations will increase your reputation.
|
contracts/operations will increase your reputation.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -10,7 +10,7 @@ import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
|||||||
import { Faction } from "../Faction";
|
import { Faction } from "../Faction";
|
||||||
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||||
import { Settings } from "../../Settings/Settings";
|
import { Settings } from "../../Settings/Settings";
|
||||||
import { hasAugmentationPrereqs } from "../FactionHelpers";
|
import { hasAugmentationPrereqs, getFactionAugmentationsFiltered } from "../FactionHelpers";
|
||||||
|
|
||||||
import { use } from "../../ui/Context";
|
import { use } from "../../ui/Context";
|
||||||
import { Reputation } from "../../ui/React/Reputation";
|
import { Reputation } from "../../ui/React/Reputation";
|
||||||
@ -23,7 +23,7 @@ import Typography from "@mui/material/Typography";
|
|||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import TableBody from "@mui/material/TableBody";
|
import TableBody from "@mui/material/TableBody";
|
||||||
import Table from "@mui/material/Table";
|
import Table from "@mui/material/Table";
|
||||||
import { CONSTANTS } from "../../Constants";
|
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
faction: Faction;
|
faction: Faction;
|
||||||
@ -32,8 +32,6 @@ type IProps = {
|
|||||||
|
|
||||||
export function AugmentationsPage(props: IProps): React.ReactElement {
|
export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||||
const player = use.Player();
|
const player = use.Player();
|
||||||
// Flag for whether the player has a gang with this faction
|
|
||||||
const isPlayersGang = player.inGang() && player.getGangName() === props.faction.name;
|
|
||||||
|
|
||||||
const setRerender = useState(false)[1];
|
const setRerender = useState(false)[1];
|
||||||
|
|
||||||
@ -42,25 +40,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAugs(): string[] {
|
function getAugs(): string[] {
|
||||||
if (isPlayersGang) {
|
return getFactionAugmentationsFiltered(player, props.faction);
|
||||||
let augs = Object.values(Augmentations);
|
|
||||||
|
|
||||||
// Remove special augs.
|
|
||||||
augs = augs.filter((a) => !a.isSpecial);
|
|
||||||
|
|
||||||
if (player.bitNodeN !== 2) {
|
|
||||||
// Remove faction-unique augs outside BN2. (But keep the one for this faction.)
|
|
||||||
augs = augs.filter((a) => a.factions.length > 1 || props.faction.augmentations.includes(a.name));
|
|
||||||
|
|
||||||
// Remove blacklisted augs.
|
|
||||||
const blacklist = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.TheRedPill];
|
|
||||||
augs = augs.filter((a) => !blacklist.includes(a.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return augs.map((a) => a.name);
|
|
||||||
} else {
|
|
||||||
return props.faction.augmentations.slice();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAugsSorted(): string[] {
|
function getAugsSorted(): string[] {
|
||||||
@ -184,10 +164,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mult = Math.pow(
|
|
||||||
CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][player.sourceFileLvl(11)],
|
|
||||||
player.queuedAugmentations.length,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={props.routeToMainPage}>Back</Button>
|
<Button onClick={props.routeToMainPage}>Back</Button>
|
||||||
@ -208,7 +185,9 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Typography>Price multiplier: x {numeralWrapper.formatMultiplier(mult)}</Typography>
|
<Typography>
|
||||||
|
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
|
||||||
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
|
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
|
||||||
|
@ -2,14 +2,12 @@ import React, { useEffect, useState } from "react";
|
|||||||
|
|
||||||
import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
|
import { Box, Button, Container, Paper, TableBody, TableRow, Typography } from "@mui/material";
|
||||||
|
|
||||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
|
||||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
|
||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { Table, TableCell } from "../../ui/React/Table";
|
import { Table, TableCell } from "../../ui/React/Table";
|
||||||
import { IRouter } from "../../ui/Router";
|
import { IRouter } from "../../ui/Router";
|
||||||
|
|
||||||
import { Faction } from "../Faction";
|
import { Faction } from "../Faction";
|
||||||
import { joinFaction } from "../FactionHelpers";
|
import { joinFaction, getFactionAugmentationsFiltered } from "../FactionHelpers";
|
||||||
import { Factions } from "../Factions";
|
import { Factions } from "../Factions";
|
||||||
import { FactionNames } from "../data/FactionNames";
|
import { FactionNames } from "../data/FactionNames";
|
||||||
|
|
||||||
@ -52,26 +50,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAugsLeft = (faction: Faction, player: IPlayer): number => {
|
const getAugsLeft = (faction: Faction, player: IPlayer): number => {
|
||||||
const isPlayersGang = player.inGang() && player.getGangName() === faction.name;
|
const augs = getFactionAugmentationsFiltered(player, faction);
|
||||||
let augs: string[] = [];
|
|
||||||
|
|
||||||
if (isPlayersGang) {
|
|
||||||
for (const augName of Object.keys(Augmentations)) {
|
|
||||||
const aug = Augmentations[augName];
|
|
||||||
if (
|
|
||||||
augName === AugmentationNames.NeuroFluxGovernor ||
|
|
||||||
(augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) ||
|
|
||||||
// Special augs (i.e. Bladeburner augs)
|
|
||||||
aug.isSpecial ||
|
|
||||||
// Exclusive augs (i.e. QLink)
|
|
||||||
(aug.factions.length <= 1 && !faction.augmentations.includes(augName) && player.bitNodeN !== 2)
|
|
||||||
)
|
|
||||||
continue;
|
|
||||||
augs.push(augName);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
augs = faction.augmentations.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
|
return augs.filter((augmentation: string) => !player.hasAugmentation(augmentation)).length;
|
||||||
};
|
};
|
||||||
|
@ -59,9 +59,7 @@ export function Info(props: IProps): React.ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<MathJaxWrapper>{"\\(\\huge{r = \\text{total faction reputation}}\\)"}</MathJaxWrapper>
|
<MathJaxWrapper>{"\\(\\huge{r = \\text{total faction reputation}}\\)"}</MathJaxWrapper>
|
||||||
<MathJaxWrapper>
|
<MathJaxWrapper>
|
||||||
{
|
{"\\(\\huge{favor=1+\\left\\lfloor\\log_{1.02}\\left(\\frac{r+25000}{25500}\\right)\\right\\rfloor}\\)"}
|
||||||
"\\(\\huge{favor=1+\\left\\lfloor\\log_{1.02}\\left(\\frac{r+25000}{25500}\\right)\\right\\rfloor}\\)"
|
|
||||||
}
|
|
||||||
</MathJaxWrapper>
|
</MathJaxWrapper>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -86,7 +84,6 @@ export function Info(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
<MathJaxWrapper>{"\\(\\huge{r = reputation}\\)"}</MathJaxWrapper>
|
<MathJaxWrapper>{"\\(\\huge{r = reputation}\\)"}</MathJaxWrapper>
|
||||||
<MathJaxWrapper>{"\\(\\huge{\\Delta r = \\Delta r \\times \\frac{100+favor}{100}}\\)"}</MathJaxWrapper>
|
<MathJaxWrapper>{"\\(\\huge{\\Delta r = \\Delta r \\times \\frac{100+favor}{100}}\\)"}</MathJaxWrapper>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FactionNames } from '../Faction/data/FactionNames';
|
import { FactionNames } from "../Faction/data/FactionNames";
|
||||||
import { Reviver } from "../utils/JSONReviver";
|
import { Reviver } from "../utils/JSONReviver";
|
||||||
|
|
||||||
interface GangTerritory {
|
interface GangTerritory {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FactionNames } from '../../Faction/data/FactionNames';
|
import { FactionNames } from "../../Faction/data/FactionNames";
|
||||||
export const PowerMultiplier: {
|
export const PowerMultiplier: {
|
||||||
[key: string]: number | undefined;
|
[key: string]: number | undefined;
|
||||||
} = {
|
} = {
|
||||||
|
@ -51,7 +51,7 @@ export function AscensionModal(props: IProps): React.ReactElement {
|
|||||||
<br />
|
<br />
|
||||||
Charisma: x{numeralWrapper.format(res.cha, "0.000")}
|
Charisma: x{numeralWrapper.format(res.cha, "0.000")}
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>,
|
||||||
);
|
);
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,8 @@ function UpgradeButton(props: IUpgradeButtonProps): React.ReactElement {
|
|||||||
return (
|
return (
|
||||||
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: props.upg.desc }} />}>
|
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: props.upg.desc }} />}>
|
||||||
<span>
|
<span>
|
||||||
<Button onClick={onClick} sx={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }}>
|
<Button onClick={onClick} sx={{ display: "flex", flexDirection: "column", width: "100%", height: "100%" }}>
|
||||||
<Typography sx={{ display: 'block' }}>{props.upg.name}</Typography>
|
<Typography sx={{ display: "block" }}>{props.upg.name}</Typography>
|
||||||
<Money money={gang.getUpgradeCost(props.upg)} />
|
<Money money={gang.getUpgradeCost(props.upg)} />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
@ -113,8 +113,8 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
|
|
||||||
const onChange = (event: SelectChangeEvent<string>): void => {
|
const onChange = (event: SelectChangeEvent<string>): void => {
|
||||||
setCurrentCategory(event.target.value);
|
setCurrentCategory(event.target.value);
|
||||||
rerender()
|
rerender();
|
||||||
}
|
};
|
||||||
|
|
||||||
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
|
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
|
||||||
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
|
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
|
||||||
@ -123,11 +123,11 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
|
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
|
||||||
|
|
||||||
const categories: { [key: string]: (GangMemberUpgrade[] | UpgradeType)[] } = {
|
const categories: { [key: string]: (GangMemberUpgrade[] | UpgradeType)[] } = {
|
||||||
'Weapons': [weaponUpgrades, UpgradeType.Weapon],
|
Weapons: [weaponUpgrades, UpgradeType.Weapon],
|
||||||
'Armor': [armorUpgrades, UpgradeType.Armor],
|
Armor: [armorUpgrades, UpgradeType.Armor],
|
||||||
'Vehicles': [vehicleUpgrades, UpgradeType.Vehicle],
|
Vehicles: [vehicleUpgrades, UpgradeType.Vehicle],
|
||||||
'Rootkits': [rootkitUpgrades, UpgradeType.Rootkit],
|
Rootkits: [rootkitUpgrades, UpgradeType.Rootkit],
|
||||||
'Augmentations': [augUpgrades, UpgradeType.Augmentation]
|
Augmentations: [augUpgrades, UpgradeType.Augmentation],
|
||||||
};
|
};
|
||||||
|
|
||||||
const asc = {
|
const asc = {
|
||||||
@ -140,7 +140,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Paper>
|
<Paper>
|
||||||
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', m: 1, gap: 1 }}>
|
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", m: 1, gap: 1 }}>
|
||||||
<span>
|
<span>
|
||||||
<Typography variant="h5" color="primary">
|
<Typography variant="h5" color="primary">
|
||||||
{props.member.name} ({props.member.task})
|
{props.member.name} ({props.member.task})
|
||||||
@ -149,47 +149,70 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
title={
|
title={
|
||||||
<Typography>
|
<Typography>
|
||||||
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x
|
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x
|
||||||
{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)}{" "}
|
{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x
|
||||||
Asc)
|
{numeralWrapper.formatMultiplier(asc.hack)} Asc)
|
||||||
<br />
|
<br />
|
||||||
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}
|
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}
|
||||||
(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)}{" "}
|
(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x
|
||||||
Asc)
|
{numeralWrapper.formatMultiplier(asc.str)} Asc)
|
||||||
<br />
|
<br />
|
||||||
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}
|
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}
|
||||||
(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)}{" "}
|
(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x
|
||||||
Asc)
|
{numeralWrapper.formatMultiplier(asc.def)} Asc)
|
||||||
<br />
|
<br />
|
||||||
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}
|
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}
|
||||||
(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)}{" "}
|
(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x
|
||||||
Asc)
|
{numeralWrapper.formatMultiplier(asc.dex)} Asc)
|
||||||
<br />
|
<br />
|
||||||
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}
|
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}
|
||||||
(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)}{" "}
|
(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x
|
||||||
Asc)
|
{numeralWrapper.formatMultiplier(asc.agi)} Asc)
|
||||||
<br />
|
<br />
|
||||||
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}
|
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}
|
||||||
(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)}{" "}
|
(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x
|
||||||
Asc)
|
{numeralWrapper.formatMultiplier(asc.cha)} Asc)
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table>
|
<Table>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.member.hack, exp: props.member.hack_exp }} />
|
<StatsRow
|
||||||
<StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.member.str, exp: props.member.str_exp }} />
|
name="Hacking"
|
||||||
<StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.member.def, exp: props.member.def_exp }} />
|
color={Settings.theme.hack}
|
||||||
<StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.member.dex, exp: props.member.dex_exp }} />
|
data={{ level: props.member.hack, exp: props.member.hack_exp }}
|
||||||
<StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.member.agi, exp: props.member.agi_exp }} />
|
/>
|
||||||
<StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.member.cha, exp: props.member.cha_exp }} />
|
<StatsRow
|
||||||
|
name="Strength"
|
||||||
|
color={Settings.theme.combat}
|
||||||
|
data={{ level: props.member.str, exp: props.member.str_exp }}
|
||||||
|
/>
|
||||||
|
<StatsRow
|
||||||
|
name="Defense"
|
||||||
|
color={Settings.theme.combat}
|
||||||
|
data={{ level: props.member.def, exp: props.member.def_exp }}
|
||||||
|
/>
|
||||||
|
<StatsRow
|
||||||
|
name="Dexterity"
|
||||||
|
color={Settings.theme.combat}
|
||||||
|
data={{ level: props.member.dex, exp: props.member.dex_exp }}
|
||||||
|
/>
|
||||||
|
<StatsRow
|
||||||
|
name="Agility"
|
||||||
|
color={Settings.theme.combat}
|
||||||
|
data={{ level: props.member.agi, exp: props.member.agi_exp }}
|
||||||
|
/>
|
||||||
|
<StatsRow
|
||||||
|
name="Charisma"
|
||||||
|
color={Settings.theme.cha}
|
||||||
|
data={{ level: props.member.cha, exp: props.member.cha_exp }}
|
||||||
|
/>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<Select onChange={onChange} value={currentCategory} sx={{ width: '100%', mb: 1 }}>
|
<Select onChange={onChange} value={currentCategory} sx={{ width: "100%", mb: 1 }}>
|
||||||
{Object.keys(categories).map((k, i) => (
|
{Object.keys(categories).map((k, i) => (
|
||||||
<MenuItem key={i + 1} value={k}>
|
<MenuItem key={i + 1} value={k}>
|
||||||
<Typography variant="h6">{k}</Typography>
|
<Typography variant="h6">{k}</Typography>
|
||||||
@ -197,32 +220,22 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: "100%" }}>
|
||||||
{(categories[currentCategory][0] as GangMemberUpgrade[]).length === 0 && (
|
{(categories[currentCategory][0] as GangMemberUpgrade[]).length === 0 && (
|
||||||
<Typography>
|
<Typography>All upgrades owned!</Typography>
|
||||||
All upgrades owned!
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr' }}>
|
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr" }}>
|
||||||
{(categories[currentCategory][0] as GangMemberUpgrade[]).map((upg) => (
|
{(categories[currentCategory][0] as GangMemberUpgrade[]).map((upg) => (
|
||||||
<UpgradeButton
|
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
|
||||||
key={upg.name}
|
|
||||||
rerender={rerender}
|
|
||||||
member={props.member}
|
|
||||||
upg={upg}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
<NextReveal
|
<NextReveal type={categories[currentCategory][1] as UpgradeType} upgrades={props.member.upgrades} />
|
||||||
type={categories[currentCategory][1] as UpgradeType}
|
|
||||||
upgrades={props.member.upgrades}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</span>
|
</span>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography sx={{ mx: 1 }}>Purchased Upgrades: </Typography>
|
<Typography sx={{ mx: 1 }}>Purchased Upgrades: </Typography>
|
||||||
<Box display="grid" sx={{ gridTemplateColumns: 'repeat(4, 1fr)', m: 1 }}>
|
<Box display="grid" sx={{ gridTemplateColumns: "repeat(4, 1fr)", m: 1 }}>
|
||||||
{props.member.upgrades.map((upg: string) => (
|
{props.member.upgrades.map((upg: string) => (
|
||||||
<PurchasedUpgrade key={upg} upgName={upg} />
|
<PurchasedUpgrade key={upg} upgName={upg} />
|
||||||
))}
|
))}
|
||||||
@ -230,7 +243,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
<PurchasedUpgrade key={upg} upgName={upg} />
|
<PurchasedUpgrade key={upg} upgName={upg} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Paper >
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,13 +251,11 @@ export function EquipmentsSubpage(): React.ReactElement {
|
|||||||
const gang = useGang();
|
const gang = useGang();
|
||||||
const [filter, setFilter] = useState("");
|
const [filter, setFilter] = useState("");
|
||||||
|
|
||||||
|
|
||||||
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
setFilter(event.target.value.toLowerCase());
|
setFilter(event.target.value.toLowerCase());
|
||||||
}
|
};
|
||||||
|
|
||||||
const members = gang.members
|
const members = gang.members.filter((member) => member && member.name.toLowerCase().includes(filter));
|
||||||
.filter((member) => member && member.name.toLowerCase().includes(filter));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -265,13 +276,13 @@ export function EquipmentsSubpage(): React.ReactElement {
|
|||||||
autoFocus
|
autoFocus
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <SearchIcon />,
|
startAdornment: <SearchIcon />,
|
||||||
spellCheck: false
|
spellCheck: false,
|
||||||
}}
|
}}
|
||||||
placeholder="Filter by member name"
|
placeholder="Filter by member name"
|
||||||
sx={{ m: 1, width: '15%' }}
|
sx={{ m: 1, width: "15%" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%' }}>
|
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "100%" }}>
|
||||||
{members.map((member: GangMember) => (
|
{members.map((member: GangMember) => (
|
||||||
<GangMemberUpgradePanel key={member.name} member={member} />
|
<GangMemberUpgradePanel key={member.name} member={member} />
|
||||||
))}
|
))}
|
||||||
|
@ -16,7 +16,7 @@ interface IProps {
|
|||||||
|
|
||||||
export function GangMemberCard(props: IProps): React.ReactElement {
|
export function GangMemberCard(props: IProps): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Box component={Paper} sx={{ width: 'auto' }}>
|
<Box component={Paper} sx={{ width: "auto" }}>
|
||||||
<Box sx={{ m: 1 }}>
|
<Box sx={{ m: 1 }}>
|
||||||
<ListItemText primary={<b>{props.member.name}</b>} />
|
<ListItemText primary={<b>{props.member.name}</b>} />
|
||||||
<GangMemberCardContent member={props.member} />
|
<GangMemberCardContent member={props.member} />
|
||||||
|
@ -26,15 +26,17 @@ export function GangMemberCardContent(props: IProps): React.ReactElement {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props.member.canAscend() && (
|
{props.member.canAscend() && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', my: 1 }}>
|
<Box sx={{ display: "flex", justifyContent: "space-between", my: 1 }}>
|
||||||
<Button onClick={() => setAscendOpen(true)} style={{ flexGrow: 1, borderRightWidth: 0 }}>Ascend</Button>
|
<Button onClick={() => setAscendOpen(true)} style={{ flexGrow: 1, borderRightWidth: 0 }}>
|
||||||
|
Ascend
|
||||||
|
</Button>
|
||||||
<AscensionModal
|
<AscensionModal
|
||||||
open={ascendOpen}
|
open={ascendOpen}
|
||||||
onClose={() => setAscendOpen(false)}
|
onClose={() => setAscendOpen(false)}
|
||||||
member={props.member}
|
member={props.member}
|
||||||
onAscend={() => setRerender((old) => !old)}
|
onAscend={() => setRerender((old) => !old)}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => setHelpOpen(true)} style={{ width: 'fit-content', borderLeftWidth: 0 }}>
|
<Button onClick={() => setHelpOpen(true)} style={{ width: "fit-content", borderLeftWidth: 0 }}>
|
||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<StaticModal open={helpOpen} onClose={() => setHelpOpen(false)}>
|
<StaticModal open={helpOpen} onClose={() => setHelpOpen(false)}>
|
||||||
@ -53,7 +55,7 @@ export function GangMemberCardContent(props: IProps): React.ReactElement {
|
|||||||
</StaticModal>
|
</StaticModal>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%', gap: 1 }}>
|
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "100%", gap: 1 }}>
|
||||||
<GangMemberStats member={props.member} />
|
<GangMemberStats member={props.member} />
|
||||||
<TaskSelector onTaskChange={() => setRerender((old) => !old)} member={props.member} />
|
<TaskSelector onTaskChange={() => setRerender((old) => !old)} member={props.member} />
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -20,7 +20,7 @@ export function GangMemberList(): React.ReactElement {
|
|||||||
|
|
||||||
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
setFilter(event.target.value.toLowerCase());
|
setFilter(event.target.value.toLowerCase());
|
||||||
}
|
};
|
||||||
|
|
||||||
const members = gang.members
|
const members = gang.members
|
||||||
.filter((member) => member && member.name.toLowerCase().includes(filter))
|
.filter((member) => member && member.name.toLowerCase().includes(filter))
|
||||||
@ -38,23 +38,18 @@ export function GangMemberList(): React.ReactElement {
|
|||||||
autoFocus
|
autoFocus
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <SearchIcon />,
|
startAdornment: <SearchIcon />,
|
||||||
spellCheck: false
|
spellCheck: false,
|
||||||
}}
|
}}
|
||||||
placeholder="Filter by member name"
|
placeholder="Filter by member name"
|
||||||
sx={{ m: 1, width: '15%' }}
|
sx={{ m: 1, width: "15%" }}
|
||||||
/>
|
/>
|
||||||
<OptionSwitch
|
<OptionSwitch
|
||||||
checked={ascendOnly}
|
checked={ascendOnly}
|
||||||
onChange={(newValue) => (setAscendOnly(newValue))}
|
onChange={(newValue) => setAscendOnly(newValue)}
|
||||||
text="Show only ascendable"
|
text="Show only ascendable"
|
||||||
tooltip={
|
tooltip={<>Filter the members list by whether or not the member can be ascended.</>}
|
||||||
<>
|
|
||||||
Filter the members list by whether or not the member
|
|
||||||
can be ascended.
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Box display="grid" sx={{ gridTemplateColumns: 'repeat(2, 1fr)' }}>
|
<Box display="grid" sx={{ gridTemplateColumns: "repeat(2, 1fr)" }}>
|
||||||
{members.map((member: GangMember) => (
|
{members.map((member: GangMember) => (
|
||||||
<GangMemberCard key={member.name} member={member} />
|
<GangMemberCard key={member.name} member={member} />
|
||||||
))}
|
))}
|
||||||
|
@ -32,7 +32,7 @@ export function GangRoot(): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Context.Gang.Provider value={gang}>
|
<Context.Gang.Provider value={gang}>
|
||||||
<Tabs variant="fullWidth" value={value} onChange={handleChange} sx={{ minWidth: 'fit-content', maxWidth: '45%' }}>
|
<Tabs variant="fullWidth" value={value} onChange={handleChange} sx={{ minWidth: "fit-content", maxWidth: "45%" }}>
|
||||||
<Tab label="Management" />
|
<Tab label="Management" />
|
||||||
<Tab label="Equipment" />
|
<Tab label="Equipment" />
|
||||||
<Tab label="Territory" />
|
<Tab label="Territory" />
|
||||||
|
@ -25,10 +25,10 @@ export function RecruitButton(props: IProps): React.ReactElement {
|
|||||||
const respect = gang.getRespectNeededToRecruitMember();
|
const respect = gang.getRespectNeededToRecruitMember();
|
||||||
return (
|
return (
|
||||||
<Box display="flex" alignItems="center" sx={{ mx: 1 }}>
|
<Box display="flex" alignItems="center" sx={{ mx: 1 }}>
|
||||||
<Button disabled>
|
<Button disabled>Recruit Gang Member</Button>
|
||||||
Recruit Gang Member
|
<Typography sx={{ ml: 1 }}>
|
||||||
</Button>
|
{numeralWrapper.formatRespect(respect)} respect needed to recruit next member
|
||||||
<Typography sx={{ ml: 1 }}>{numeralWrapper.formatRespect(respect)} respect needed to recruit next member</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,9 @@ export function TaskSelector(props: IProps): React.ReactElement {
|
|||||||
const gang = useGang();
|
const gang = useGang();
|
||||||
const [currentTask, setCurrentTask] = useState(props.member.task);
|
const [currentTask, setCurrentTask] = useState(props.member.task);
|
||||||
|
|
||||||
const contextMember = gang.members.find(member => member.name == props.member.name)
|
const contextMember = gang.members.find((member) => member.name == props.member.name);
|
||||||
if (contextMember &&
|
if (contextMember && contextMember.task != currentTask) {
|
||||||
contextMember.task != currentTask
|
setCurrentTask(contextMember.task);
|
||||||
) {
|
|
||||||
setCurrentTask(contextMember.task)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChange(event: SelectChangeEvent<string>): void {
|
function onChange(event: SelectChangeEvent<string>): void {
|
||||||
@ -39,7 +37,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Select onChange={onChange} value={currentTask} sx={{ width: '100%' }}>
|
<Select onChange={onChange} value={currentTask} sx={{ width: "100%" }}>
|
||||||
<MenuItem key={0} value={"Unassigned"}>
|
<MenuItem key={0} value={"Unassigned"}>
|
||||||
Unassigned
|
Unassigned
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -13,9 +13,7 @@ export const TerritoryInfoModal = ({ open, onClose }: IProps): React.ReactElemen
|
|||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose}>
|
<Modal open={open} onClose={onClose}>
|
||||||
<>
|
<>
|
||||||
<Typography variant='h4'>
|
<Typography variant="h4">Clashing</Typography>
|
||||||
Clashing
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
<Typography>
|
||||||
Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on
|
Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on
|
||||||
your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The
|
your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The
|
||||||
@ -29,26 +27,22 @@ export const TerritoryInfoModal = ({ open, onClose }: IProps): React.ReactElemen
|
|||||||
gang.
|
gang.
|
||||||
</Typography>
|
</Typography>
|
||||||
<br />
|
<br />
|
||||||
<Typography variant='h4'>
|
<Typography variant="h4">Territory</Typography>
|
||||||
Territory
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
<Typography>
|
||||||
The amount of territory you have affects all aspects of your Gang members' production, including money, respect,
|
The amount of territory you have affects all aspects of your Gang members' production, including money,
|
||||||
and wanted level. It is very beneficial to have high territory control.
|
respect, and wanted level. It is very beneficial to have high territory control.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
To increase your chances of winning territory, assign gang members to "Territory Warfare". This will build your
|
To increase your chances of winning territory, assign gang members to "Territory Warfare". This will build
|
||||||
gang power. Then, enable "Engage in Territory Warfare" to start fighting over territory.
|
your gang power. Then, enable "Engage in Territory Warfare" to start fighting over territory.
|
||||||
</Typography>
|
</Typography>
|
||||||
<br />
|
<br />
|
||||||
<Typography variant='h4'>
|
<Typography variant="h4">Territory Clash Chance</Typography>
|
||||||
Territory Clash Chance
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
<Typography>
|
||||||
This percentage represents the chance you have of 'clashing' with another gang. If you do not wish to
|
This percentage represents the chance you have of 'clashing' with another gang. If you do not wish to
|
||||||
gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.
|
gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
</Modal >
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -3,16 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import {
|
import { Container, Button, Paper, Box, Tooltip, Switch, FormControlLabel, Typography } from "@mui/material";
|
||||||
Container,
|
|
||||||
Button,
|
|
||||||
Paper,
|
|
||||||
Box,
|
|
||||||
Tooltip,
|
|
||||||
Switch,
|
|
||||||
FormControlLabel,
|
|
||||||
Typography
|
|
||||||
} from "@mui/material";
|
|
||||||
import { Help } from "@mui/icons-material";
|
import { Help } from "@mui/icons-material";
|
||||||
|
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
@ -41,35 +32,51 @@ export function TerritorySubpage(): React.ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Box component={Paper} sx={{ p: 1, mb: 1 }}>
|
<Box component={Paper} sx={{ p: 1, mb: 1 }}>
|
||||||
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
|
||||||
{gang.facName} (Your gang)
|
{gang.facName} (Your gang)
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Switch
|
control={
|
||||||
checked={gang.territoryWarfareEngaged}
|
<Switch
|
||||||
onChange={(event) => (gang.territoryWarfareEngaged = event.target.checked)}
|
checked={gang.territoryWarfareEngaged}
|
||||||
/>}
|
onChange={(event) => (gang.territoryWarfareEngaged = event.target.checked)}
|
||||||
label={<Tooltip
|
/>
|
||||||
title={<Typography>
|
}
|
||||||
Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance
|
label={
|
||||||
to gradually decrease until it reaches 0%.
|
<Tooltip
|
||||||
</Typography>}>
|
title={
|
||||||
<Typography>Engage in Territory Warfare</Typography>
|
<Typography>
|
||||||
</Tooltip>} />
|
Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance
|
||||||
|
to gradually decrease until it reaches 0%.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography>Engage in Territory Warfare</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Switch
|
control={
|
||||||
checked={gang.notifyMemberDeath}
|
<Switch
|
||||||
onChange={(event) => (gang.notifyMemberDeath = event.target.checked)}
|
checked={gang.notifyMemberDeath}
|
||||||
/>}
|
onChange={(event) => (gang.notifyMemberDeath = event.target.checked)}
|
||||||
label={<Tooltip
|
/>
|
||||||
title={<Typography>
|
}
|
||||||
If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies
|
label={
|
||||||
in a territory clash.
|
<Tooltip
|
||||||
</Typography>}>
|
title={
|
||||||
<Typography>Notify about Gang Member Deaths</Typography>
|
<Typography>
|
||||||
</Tooltip>} />
|
If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members
|
||||||
|
dies in a territory clash.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography>Notify about Gang Member Deaths</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Typography>
|
<Typography>
|
||||||
<b>Territory Clash Chance:</b> {numeralWrapper.formatPercentage(gang.territoryClashChance, 3)} <br />
|
<b>Territory Clash Chance:</b> {numeralWrapper.formatPercentage(gang.territoryClashChance, 3)} <br />
|
||||||
@ -77,13 +84,13 @@ export function TerritorySubpage(): React.ReactElement {
|
|||||||
<b>Territory:</b> {formatTerritory(AllGangs[gang.facName].territory)}% <br />
|
<b>Territory:</b> {formatTerritory(AllGangs[gang.facName].territory)}% <br />
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
|
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
|
||||||
{gangNames.map((name) => (
|
{gangNames.map((name) => (
|
||||||
<OtherGangTerritory key={name} name={name} />
|
<OtherGangTerritory key={name} name={name} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
<TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} />
|
<TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} />
|
||||||
</Container >
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function formatTerritory(n: number): string {
|
function formatTerritory(n: number): string {
|
||||||
@ -109,7 +116,7 @@ function OtherGangTerritory(props: ITerritoryProps): React.ReactElement {
|
|||||||
const clashVictoryChance = playerPower / (power + playerPower);
|
const clashVictoryChance = playerPower / (power + playerPower);
|
||||||
return (
|
return (
|
||||||
<Box component={Paper} sx={{ p: 1 }}>
|
<Box component={Paper} sx={{ p: 1 }}>
|
||||||
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
|
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
|
||||||
{props.name}
|
{props.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
|
@ -49,7 +49,8 @@ export function calculatePercentMoneyHacked(server: Server, player: IPlayer): nu
|
|||||||
|
|
||||||
const difficultyMult = (100 - server.hackDifficulty) / 100;
|
const difficultyMult = (100 - server.hackDifficulty) / 100;
|
||||||
const skillMult = (player.hacking - (server.requiredHackingSkill - 1)) / player.hacking;
|
const skillMult = (player.hacking - (server.requiredHackingSkill - 1)) / player.hacking;
|
||||||
const percentMoneyHacked = (difficultyMult * skillMult * player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney) / balanceFactor;
|
const percentMoneyHacked =
|
||||||
|
(difficultyMult * skillMult * player.hacking_money_mult * BitNodeMultipliers.ScriptHackMoney) / balanceFactor;
|
||||||
if (percentMoneyHacked < 0) {
|
if (percentMoneyHacked < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,13 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"
|
|||||||
import { ObjectValidator, minMax } from "../utils/Validator";
|
import { ObjectValidator, minMax } from "../utils/Validator";
|
||||||
|
|
||||||
export class HacknetNode implements IHacknetNode {
|
export class HacknetNode implements IHacknetNode {
|
||||||
|
|
||||||
static validationData: ObjectValidator<HacknetNode> = {
|
static validationData: ObjectValidator<HacknetNode> = {
|
||||||
cores: minMax(1, 1, HacknetNodeConstants.MaxCores),
|
cores: minMax(1, 1, HacknetNodeConstants.MaxCores),
|
||||||
level: minMax(1, 1, HacknetNodeConstants.MaxLevel),
|
level: minMax(1, 1, HacknetNodeConstants.MaxLevel),
|
||||||
ram: minMax(1, 1, HacknetNodeConstants.MaxRam),
|
ram: minMax(1, 1, HacknetNodeConstants.MaxRam),
|
||||||
onlineTimeSeconds: minMax(0, 0, Infinity),
|
onlineTimeSeconds: minMax(0, 0, Infinity),
|
||||||
totalMoneyGenerated: minMax(0, 0, Infinity)
|
totalMoneyGenerated: minMax(0, 0, Infinity),
|
||||||
}
|
};
|
||||||
|
|
||||||
// Node's number of cores
|
// Node's number of cores
|
||||||
cores = 1;
|
cores = 1;
|
||||||
|
@ -137,7 +137,7 @@ export function HacknetRoot(props: IProps): React.ReactElement {
|
|||||||
|
|
||||||
{hasHacknetServers(props.player) && <Button onClick={() => setOpen(true)}>Spend Hashes on Upgrades</Button>}
|
{hasHacknetServers(props.player) && <Button onClick={() => setOpen(true)}>Spend Hashes on Upgrades</Button>}
|
||||||
|
|
||||||
<Box sx={{ display: 'grid', width: 'fit-content', gridTemplateColumns: 'repeat(3, 1fr)' }}>{nodes}</Box>
|
<Box sx={{ display: "grid", width: "fit-content", gridTemplateColumns: "repeat(3, 1fr)" }}>{nodes}</Box>
|
||||||
<HashUpgradeModal open={open} onClose={() => setOpen(false)} />
|
<HashUpgradeModal open={open} onClose={() => setOpen(false)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -43,7 +43,7 @@ export function HacknetUpgradeElem(props: IProps): React.ReactElement {
|
|||||||
if (!res) {
|
if (!res) {
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
"Failed to purchase upgrade. This may be because you do not have enough hashes, " +
|
"Failed to purchase upgrade. This may be because you do not have enough hashes, " +
|
||||||
"or because you do not have access to the feature upgrade affects.",
|
"or because you do not have access to the feature upgrade affects.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
props.rerender();
|
props.rerender();
|
||||||
|
@ -7,6 +7,7 @@ import { random } from "../utils";
|
|||||||
import { interpolate } from "./Difficulty";
|
import { interpolate } from "./Difficulty";
|
||||||
import { BlinkingCursor } from "./BlinkingCursor";
|
import { BlinkingCursor } from "./BlinkingCursor";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -36,7 +37,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
|
|||||||
|
|
||||||
function press(this: Document, event: KeyboardEvent): void {
|
function press(this: Document, event: KeyboardEvent): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (event.key === "Backspace") return;
|
if (event.key === KEY.BACKSPACE) return;
|
||||||
const nextGuess = guess + event.key.toUpperCase();
|
const nextGuess = guess + event.key.toUpperCase();
|
||||||
if (!answer.startsWith(nextGuess)) props.onFailure();
|
if (!answer.startsWith(nextGuess)) props.onFailure();
|
||||||
else if (answer === nextGuess) props.onSuccess();
|
else if (answer === nextGuess) props.onSuccess();
|
||||||
|
@ -7,6 +7,7 @@ import { random } from "../utils";
|
|||||||
import { interpolate } from "./Difficulty";
|
import { interpolate } from "./Difficulty";
|
||||||
import { BlinkingCursor } from "./BlinkingCursor";
|
import { BlinkingCursor } from "./BlinkingCursor";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -29,28 +30,29 @@ const difficulties: {
|
|||||||
|
|
||||||
function generateLeftSide(difficulty: Difficulty): string {
|
function generateLeftSide(difficulty: Difficulty): string {
|
||||||
let str = "";
|
let str = "";
|
||||||
|
const options = [KEY.OPEN_BRACKET, KEY.LESS_THAN, KEY.OPEN_PARENTHESIS, KEY.OPEN_BRACE];
|
||||||
const length = random(difficulty.min, difficulty.max);
|
const length = random(difficulty.min, difficulty.max);
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
str += ["[", "<", "(", "{"][Math.floor(Math.random() * 4)];
|
str += options[Math.floor(Math.random() * 4)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChar(event: KeyboardEvent): string {
|
function getChar(event: KeyboardEvent): string {
|
||||||
if (event.key === ")") return ")";
|
if (event.key === KEY.CLOSE_PARENTHESIS) return KEY.CLOSE_PARENTHESIS;
|
||||||
if (event.key === "]") return "]";
|
if (event.key === KEY.CLOSE_BRACKET) return KEY.CLOSE_BRACKET;
|
||||||
if (event.key === "}") return "}";
|
if (event.key === KEY.CLOSE_BRACE) return KEY.CLOSE_BRACE;
|
||||||
if (event.key === ">") return ">";
|
if (event.key === KEY.GREATER_THAN) return KEY.GREATER_THAN;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function match(left: string, right: string): boolean {
|
function match(left: string, right: string): boolean {
|
||||||
return (
|
return (
|
||||||
(left === "[" && right === "]") ||
|
(left === KEY.OPEN_BRACKET && right === KEY.CLOSE_BRACKET) ||
|
||||||
(left === "<" && right === ">") ||
|
(left === KEY.LESS_THAN && right === KEY.GREATER_THAN) ||
|
||||||
(left === "(" && right === ")") ||
|
(left === KEY.OPEN_PARENTHESIS && right === KEY.CLOSE_PARENTHESIS) ||
|
||||||
(left === "{" && right === "}")
|
(left === KEY.OPEN_BRACE && right === KEY.CLOSE_BRACE)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import { KeyHandler } from "./KeyHandler";
|
|||||||
import { GameTimer } from "./GameTimer";
|
import { GameTimer } from "./GameTimer";
|
||||||
import { interpolate } from "./Difficulty";
|
import { interpolate } from "./Difficulty";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
|
import { downArrowSymbol, upArrowSymbol } from "../utils";
|
||||||
|
|
||||||
interface Difficulty {
|
interface Difficulty {
|
||||||
[key: string]: number;
|
[key: string]: number;
|
||||||
@ -34,15 +36,15 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
|
|||||||
function press(this: Document, event: KeyboardEvent): void {
|
function press(this: Document, event: KeyboardEvent): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const k = event.key;
|
const k = event.key;
|
||||||
if (k === " ") {
|
if (k === KEY.SPACE) {
|
||||||
if (positive.includes(choices[index])) props.onSuccess();
|
if (positive.includes(choices[index])) props.onSuccess();
|
||||||
else props.onFailure();
|
else props.onFailure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newIndex = index;
|
let newIndex = index;
|
||||||
if (["ArrowUp", "w", "ArrowRight", "d"].includes(k)) newIndex++;
|
if ([KEY.UP_ARROW, KEY.W, KEY.RIGHT_ARROW, KEY.D].map((key) => key as string).includes(k)) newIndex++;
|
||||||
if (["ArrowDown", "s", "ArrowLeft", "a"].includes(k)) newIndex--;
|
if ([KEY.DOWN_ARROW, KEY.S, KEY.LEFT_ARROW, KEY.A].map((key) => key as string).includes(k)) newIndex--;
|
||||||
while (newIndex < 0) newIndex += choices.length;
|
while (newIndex < 0) newIndex += choices.length;
|
||||||
while (newIndex > choices.length - 1) newIndex -= choices.length;
|
while (newIndex > choices.length - 1) newIndex -= choices.length;
|
||||||
setIndex(newIndex);
|
setIndex(newIndex);
|
||||||
@ -57,13 +59,13 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<Typography variant="h5" color="primary">
|
<Typography variant="h5" color="primary">
|
||||||
↑
|
{upArrowSymbol}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" color="primary">
|
<Typography variant="h5" color="primary">
|
||||||
{choices[index]}
|
{choices[index]}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" color="primary">
|
<Typography variant="h5" color="primary">
|
||||||
↓
|
{downArrowSymbol}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user