Compare commits

...

46 Commits
main ... main

Author SHA1 Message Date
3053112428 chore: auto-generate palladino's vendor manifest (#2160)
A bit ugly, but having the self test forces correctness.

Reviewed-on: OpenWF/SpaceNinjaServer#2160
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-15 05:53:10 -07:00
f448d03880 fix: 1999 bounty chemistry (#2164)
Closes #2162

Reviewed-on: OpenWF/SpaceNinjaServer#2164
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-15 05:05:35 -07:00
d00fbed46f fix: treating chemstry delta as absolute value (#2163)
Reviewed-on: OpenWF/SpaceNinjaServer#2163
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-15 05:05:18 -07:00
c283d61399 chore(webui): update to Spanish translation (#2165)
Reviewed-on: OpenWF/SpaceNinjaServer#2165
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-06-14 23:44:34 -07:00
12d09531b3 feat: add dev.keepVendorsExpired config option (#2161)
Reviewed-on: OpenWF/SpaceNinjaServer#2161
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-14 12:58:26 -07:00
b67ddf6df2 feat: handle classic syndicate alignments when trading in medals (#2157)
Reviewed-on: OpenWF/SpaceNinjaServer#2157
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-14 12:57:40 -07:00
8b27fcf459 chore(webui): add archon crystal upgrades to translation system (#2154)
Reviewed-on: OpenWF/SpaceNinjaServer#2154
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-13 19:57:19 -07:00
071ef528ea fix: syndicate rank up from negative levels (#2156)
For level <= 0, SacrificeLevel is the current level.

Reviewed-on: OpenWF/SpaceNinjaServer#2156
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-13 10:58:41 -07:00
71d1b6094c feat: randomise classic bounty xpAmounts (#2150)
Reviewed-on: OpenWF/SpaceNinjaServer#2150
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-13 04:46:18 -07:00
fcc11206cc fix: multiple syndicate level ups (#2152)
Regression from 54a73ad5d7eab867a1701ccf66d56446db96c226 because I forgot that levelIncrease could now be >1

Reviewed-on: OpenWF/SpaceNinjaServer#2152
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-13 04:46:04 -07:00
3c019e41b9 chore(webui): update to Spanish translation (#2151)
Reviewed-on: OpenWF/SpaceNinjaServer#2151
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-06-12 13:23:51 -07:00
54a73ad5d7 fix: syndicate initiation (#2149)
Was accidentially broken by 1979b20f8cc00b79f305478f1da3d69f90a3d43e

Reviewed-on: OpenWF/SpaceNinjaServer#2149
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-12 09:20:32 -07:00
62eeb313ec feat: nightwaveStandingMultiplier cheat (#2145)
Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com>
Co-authored-by: ny <64143453+nyaoouo@users.noreply.github.com>
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2145
Co-authored-by: nyaoouo <nyaoouo@noreply.localhost>
Co-committed-by: nyaoouo <nyaoouo@noreply.localhost>
2025-06-12 04:54:41 -07:00
62d4b9f6cb feat(webui): boosters (#2140)
Co-authored-by: ny <64143453+nyaoouo@users.noreply.github.com>
Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com>
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2140
Co-authored-by: nyaoouo <nyaoouo@noreply.localhost>
Co-committed-by: nyaoouo <nyaoouo@noreply.localhost>
2025-06-12 04:54:17 -07:00
1d813a1b1b fix: toStoreItem not converting boosters (#2147)
Bug reported via #2146

Reviewed-on: OpenWF/SpaceNinjaServer#2147
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-11 03:10:10 -07:00
4823406229 chore: don't fail missionInventoryUpdate on unknown items (#2144)
It's possible we started a mission, deleted an item in the webui, and then finished it. The mission completion is still valid, we just can't update that item.

Reviewed-on: OpenWF/SpaceNinjaServer#2144
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-11 02:32:51 -07:00
bdc41de8bb fix: set incubated pet as in stasis when one is already active (#2143)
Previously was kept in incubating state which is obviously wrong

Reviewed-on: OpenWF/SpaceNinjaServer#2143
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-11 02:31:14 -07:00
60236a1154 chore: update PE+ (#2142)
Closes #2141

Reviewed-on: OpenWF/SpaceNinjaServer#2142
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-10 09:48:13 -07:00
1979b20f8c fix: handle multiple syndicate title increases at once (#2139)
Only really possible with nightwave afaik. Bug reported via #2138.

Reviewed-on: OpenWF/SpaceNinjaServer#2139
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-10 05:49:43 -07:00
c736310ff3 feat: send clan search message when reaching MR 2 (#2136)
Closes #1960

Reviewed-on: OpenWF/SpaceNinjaServer#2136
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-09 11:03:56 -07:00
2b555a6456 chore(webui): update Chinese translation (#2137)
Reviewed-on: OpenWF/SpaceNinjaServer#2137
Co-authored-by: bishan178 <bishan178@noreply.localhost>
Co-committed-by: bishan178 <bishan178@noreply.localhost>
2025-06-09 08:20:34 -07:00
870c964854 feat: add eidolonOverride & vallisOverride to replace lockTime (#2135)
I think for now it's best to keep the client time somewhat in sync with the server/database time to avoid various issues.

Reviewed-on: OpenWF/SpaceNinjaServer#2135
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-09 06:54:58 -07:00
4535b193e0 chore: handle nightwaveOverride having an invalid value (#2133)
Reviewed-on: OpenWF/SpaceNinjaServer#2133
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-09 03:37:42 -07:00
943574bf3a chore(webui): update Chinese translation (#2134)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2134
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-06-08 11:14:28 -07:00
f9a4d48b4d chore: base all cycles on locked time if used (#2128)
Reviewed-on: OpenWF/SpaceNinjaServer#2128
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 16:46:45 -07:00
135b1e54fe feat: classic lich guess history (#2129)
Closes #2123

Reviewed-on: OpenWF/SpaceNinjaServer#2129
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 16:46:18 -07:00
b7c47b91ff chore: improve getItemCategoryByUniqueName (#2130)
unused function, but might as well make it at least half decent

Reviewed-on: OpenWF/SpaceNinjaServer#2130
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 16:46:00 -07:00
9def5c265e feat: kubrow & kavat incubation (#2131)
Closes #377

Reviewed-on: OpenWF/SpaceNinjaServer#2131
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 16:45:50 -07:00
65387ccdea feat(webui): disambiguate gear and resource with the same name for add items (#2127)
Closes #2097

Reviewed-on: OpenWF/SpaceNinjaServer#2127
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 02:17:27 -07:00
4118528603 chore: some minor improvements to nemesis mode=s (#2126)
Reviewed-on: OpenWF/SpaceNinjaServer#2126
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 02:17:05 -07:00
8ffbb308c5 fix: oull being considered an incorrect guess (#2125)
Closes #2121

Reviewed-on: OpenWF/SpaceNinjaServer#2125
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 02:16:50 -07:00
2e649cabf6 chore: handle purchasing decree from acrithis (#2124)
Closes #2112

Reviewed-on: OpenWF/SpaceNinjaServer#2124
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 02:16:33 -07:00
5c5296d565 feat: add nightwaveOverride to config (#2120)
Reviewed-on: OpenWF/SpaceNinjaServer#2120
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 02:16:19 -07:00
8f5f2fc206 chore: handle numbers in config administratorNames (#2117)
Reviewed-on: OpenWF/SpaceNinjaServer#2117
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-07 02:15:35 -07:00
0997f9567f fix: cap nemesis rank (#2122)
Re #2121

Reviewed-on: OpenWF/SpaceNinjaServer#2122
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-06 16:59:13 -07:00
be02435661 chore: handle addItem of /Lotus/Types/Items/Emotes/** based on path (#2116)
Closes #2114

Reviewed-on: OpenWF/SpaceNinjaServer#2116
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-06 16:29:25 -07:00
20c4092dfe fix: swapped operands (#2119)
Closes #2118

Reviewed-on: OpenWF/SpaceNinjaServer#2119
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-06 13:13:36 -07:00
01492f4f16 fix: swapped operands (#2115)
Closes #2113

Reviewed-on: OpenWF/SpaceNinjaServer#2115
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-06 08:59:22 -07:00
d739945a1d fix: check that syndicateMissionId is not undefined (#2110)
Closes #2111

Reviewed-on: OpenWF/SpaceNinjaServer#2110
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-03 10:40:28 -07:00
d43e39d7b5 fix(webui): error when an unknown suit is max rank (#2109)
Reviewed-on: OpenWF/SpaceNinjaServer#2109
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-02 01:44:10 -07:00
b0499a62aa chore: use 'git checkout -f' instead of 'git reset --hard' to avoid loss 2025-06-01 12:16:16 +02:00
8f02bd1509 fix: avoid addition by undefined when adding skill points (#2108)
Reviewed-on: OpenWF/SpaceNinjaServer#2108
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-01 03:12:14 -07:00
32f4c5105a feat: add update and start script for linux (#2107)
Reviewed-on: OpenWF/SpaceNinjaServer#2107
Co-authored-by: Sainan <sainan@calamity.gg>
Co-committed-by: Sainan <sainan@calamity.gg>
2025-06-01 03:12:07 -07:00
28da982c80 feat: renamePet (#2106)
Reviewed-on: OpenWF/SpaceNinjaServer#2106
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-30 11:42:45 -07:00
ae1850d6cd chore: update PE+ (#2105)
Reviewed-on: OpenWF/SpaceNinjaServer#2105
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-05-27 03:58:12 -07:00
b90bdd2783 chore: update french translation (#2104)
Reviewed-on: OpenWF/SpaceNinjaServer#2104
Co-authored-by: Vitruvio <vitruvio@noreply.localhost>
Co-committed-by: Vitruvio <vitruvio@noreply.localhost>
2025-05-26 01:46:19 -07:00
44 changed files with 1463 additions and 595 deletions

View File

@ -14,4 +14,22 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`. - `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`. - `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE. - `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
- `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
- `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
- `RadioLegionIntermission11Syndicate` for Nora's Mix Vol. 7
- `RadioLegionIntermission10Syndicate` for Nora's Mix Vol. 6
- `RadioLegionIntermission9Syndicate` for Nora's Mix Vol. 5
- `RadioLegionIntermission8Syndicate` for Nora's Mix Vol. 4
- `RadioLegionIntermission7Syndicate` for Nora's Mix Vol. 3
- `RadioLegionIntermission6Syndicate` for Nora's Mix Vol. 2
- `RadioLegionIntermission5Syndicate` for Nora's Mix Vol. 1
- `RadioLegionIntermission4Syndicate` for Nora's Choice
- `RadioLegionIntermission3Syndicate` for Intermission III
- `RadioLegion3Syndicate` for Glassmaker
- `RadioLegionIntermission2Syndicate` for Intermission II
- `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six

View File

@ -3,7 +3,7 @@
echo Updating SpaceNinjaServer... echo Updating SpaceNinjaServer...
git fetch --prune git fetch --prune
git stash git stash
git reset --hard origin/main git checkout -f origin/main
if exist static\data\0\ ( if exist static\data\0\ (
echo Updating stripped assets... echo Updating stripped assets...

23
UPDATE AND START SERVER.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
echo "Updating SpaceNinjaServer..."
git fetch --prune
git stash
git checkout -f origin/main
if [ -d "static/data/0/" ]; then
echo "Updating stripped assets..."
cd static/data/0/
git pull
cd ../../../
fi
echo "Updating dependencies..."
npm i --omit=dev
npm run build
if [ $? -eq 0 ]; then
npm run start
echo "SpaceNinjaServer seems to have crashed."
fi

View File

@ -50,11 +50,17 @@
"noDojoResearchTime": false, "noDojoResearchTime": false,
"fastClanAscension": false, "fastClanAscension": false,
"spoofMasteryRank": -1, "spoofMasteryRank": -1,
"nightwaveStandingMultiplier": 1,
"worldState": { "worldState": {
"creditBoost": false, "creditBoost": false,
"affinityBoost": false, "affinityBoost": false,
"resourceBoost": false, "resourceBoost": false,
"starDays": true, "starDays": true,
"lockTime": 0 "eidolonOverride": "",
"vallisOverride": "",
"nightwaveOverride": ""
},
"dev": {
"keepVendorsExpired": false
} }
} }

8
package-lock.json generated
View File

@ -18,7 +18,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.64", "warframe-public-export-plus": "^0.5.66",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@ -3814,9 +3814,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.64", "version": "0.5.66",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.64.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.66.tgz",
"integrity": "sha512-JyHRtYumfwQ1Iog2unzlBWfQHJlZER+iUISquyFFv0Qqtv2QsNzFv2AbV7sCaqgDcE8tw6e5/YqGgfI0m403/g==" "integrity": "sha512-AU7XQA96OfYrLm2RioCwDjjdI3IrsmUiqebXyE+bpM0iST+4x/NHu8LTRT4Oygfo/2OBtDYhib7G6re0EeAe5g=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -25,7 +25,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.64", "warframe-public-export-plus": "^0.5.66",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"

View File

@ -0,0 +1,27 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const adoptPetController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "KubrowPets");
const data = getJSONfromString<IAdoptPetRequest>(String(req.body));
const details = inventory.KubrowPets.id(data.petId)!.Details!;
details.Name = data.name;
await inventory.save();
res.json({
petId: data.petId,
newName: data.name
} satisfies IAdoptPetResponse);
};
interface IAdoptPetRequest {
petId: string;
name: string;
}
interface IAdoptPetResponse {
petId: string;
newName: string;
}

View File

@ -17,7 +17,7 @@ import {
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid2 } from "@/src/helpers/inventoryHelpers"; import { toOid2 } from "@/src/helpers/inventoryHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { IRecipe } from "warframe-public-export-plus"; import { IRecipe } from "warframe-public-export-plus";
@ -105,7 +105,21 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
...updateCurrency(inventory, cost, true) ...updateCurrency(inventory, cost, true)
}; };
} }
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
pet.Details!.HatchDate = new Date();
}
let canSetActive = true;
for (const pet of inventory.KubrowPets) {
if (pet.Details!.Status == Status.StatusAvailable) {
canSetActive = false;
break;
}
}
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...(await addItem( ...(await addItem(
@ -118,7 +132,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
)) ))
}; };
} }
if (config.claimingBlueprintRefundsIngredients) { if (
config.claimingBlueprintRefundsIngredients &&
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
) {
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe); await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
} }
await inventory.save(); await inventory.save();

View File

@ -30,15 +30,14 @@ export const fishmongerController: RequestHandler = async (req, res) => {
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 }); miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
} }
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
let affiliationMod; if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding);
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: { InventoryChanges: {
MiscItems: miscItemChanges MiscItems: miscItemChanges
}, },
SyndicateTag: syndicateTag, SyndicateTag: syndicateTag,
StandingChange: affiliationMod?.Standing || 0 StandingChange: gainedStanding
}); });
}; };

View File

@ -2,6 +2,7 @@ import { RequestHandler } from "express";
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService"; import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { config } from "@/src/services/configService";
export const getVendorInfoController: RequestHandler = async (req, res) => { export const getVendorInfoController: RequestHandler = async (req, res) => {
let manifest = getVendorManifestByTypeName(req.query.vendor as string); let manifest = getVendorManifestByTypeName(req.query.vendor as string);
@ -14,6 +15,14 @@ export const getVendorInfoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
manifest = applyStandingToVendorManifest(inventory, manifest); manifest = applyStandingToVendorManifest(inventory, manifest);
if (config.dev?.keepVendorsExpired) {
manifest = {
VendorInfo: {
...manifest.VendorInfo,
Expiry: { $date: { $numberLong: "0" } }
}
};
}
} }
res.json(manifest); res.json(manifest);

View File

@ -310,7 +310,7 @@ export const getInventoryResponse = async (
// Fix nemesis for older versions // Fix nemesis for older versions
if ( if (
inventoryResponse.Nemesis && inventoryResponse.Nemesis &&
version_compare(getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild, buildLabel) < 0 version_compare(buildLabel, getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild) < 0
) { ) {
inventoryResponse.Nemesis = undefined; inventoryResponse.Nemesis = undefined;
} }

View File

@ -1,12 +1,18 @@
import { version_compare } from "@/src/helpers/inventoryHelpers"; import { version_compare } from "@/src/helpers/inventoryHelpers";
import { import {
consumeModCharge, consumeModCharge,
decodeNemesisGuess,
encodeNemesisGuess, encodeNemesisGuess,
getInfNodes, getInfNodes,
getKnifeUpgrade, getKnifeUpgrade,
getNemesisManifest, getNemesisManifest,
getNemesisPasscode, getNemesisPasscode,
getNemesisPasscodeModTypes, getNemesisPasscodeModTypes,
GUESS_CORRECT,
GUESS_INCORRECT,
GUESS_NEUTRAL,
GUESS_NONE,
GUESS_WILDCARD,
IKnifeResponse IKnifeResponse
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
@ -82,7 +88,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
} }
} else { } else {
for (let i = 0; i != 3; ++i) { for (let i = 0; i != 3; ++i) {
if (body.guess[i] == passcode[i]) { if (body.guess[i] == passcode[i] || body.guess[i] == GUESS_WILDCARD) {
++guessResult; ++guessResult;
} }
} }
@ -97,18 +103,29 @@ export const nemesisController: RequestHandler = async (req, res) => {
if (inventory.Nemesis!.Faction == "FC_INFESTATION") { if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf]; const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
const passcode = getNemesisPasscode(inventory.Nemesis!)[0]; const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
// Add to GuessHistory const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
const result1 = passcode == guess[0] ? 0 : 1; const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
const result2 = passcode == guess[1] ? 0 : 1;
const result3 = passcode == guess[2] ? 0 : 1;
inventory.Nemesis!.GuessHistory.push( inventory.Nemesis!.GuessHistory.push(
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3) encodeNemesisGuess([
{
symbol: guess[0],
result: result1
},
{
symbol: guess[1],
result: result2
},
{
symbol: guess[2],
result: result3
}
])
); );
// Increase antivirus if correct antivirus mod is installed // Increase antivirus if correct antivirus mod is installed
const response: IKnifeResponse = {}; const response: IKnifeResponse = {};
if (result1 == 0 || result2 == 0 || result3 == 0) { if (result1 == GUESS_CORRECT || result2 == GUESS_CORRECT || result3 == GUESS_CORRECT) {
let antivirusGain = 5; let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid); const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
@ -149,18 +166,48 @@ export const nemesisController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.json(response); res.json(response);
} else { } else {
const passcode = getNemesisPasscode(inventory.Nemesis!); // For first guess, create a new entry.
if (passcode[body.position] != body.guess) { if (body.position == 0) {
res.end(); inventory.Nemesis!.GuessHistory.push(
} else { encodeNemesisGuess([
inventory.Nemesis!.Rank += 1; {
inventory.Nemesis!.InfNodes = getInfNodes( symbol: GUESS_NONE,
getNemesisManifest(inventory.Nemesis!.manifest), result: GUESS_NEUTRAL
inventory.Nemesis!.Rank },
{
symbol: GUESS_NONE,
result: GUESS_NEUTRAL
},
{
symbol: GUESS_NONE,
result: GUESS_NEUTRAL
}
])
); );
await inventory.save();
res.json({ RankIncrease: 1 });
} }
// Evaluate guess
const correct =
body.guess == GUESS_WILDCARD || getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess;
// Update entry
const guess = decodeNemesisGuess(
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1]
);
guess[body.position].symbol = body.guess;
guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
// Increase rank if incorrect
let RankIncrease: number | undefined;
if (!correct) {
RankIncrease = 1;
const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
}
await inventory.save();
res.json({ RankIncrease });
} }
} else if ((req.query.mode as string) == "rs") { } else if ((req.query.mode as string) == "rs") {
// report spawn; POST but no application data in body // report spawn; POST but no application data in body
@ -170,11 +217,14 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({ LastEnc: inventory.Nemesis!.LastEnc }); res.json({ LastEnc: inventory.Nemesis!.LastEnc });
} else if ((req.query.mode as string) == "s") { } else if ((req.query.mode as string) == "s") {
const inventory = await getInventory(account._id.toString(), "Nemesis"); const inventory = await getInventory(account._id.toString(), "Nemesis");
if (inventory.Nemesis) {
logger.warn(`overwriting an existing nemesis as a new one is being requested`);
}
const body = getJSONfromString<INemesisStartRequest>(String(req.body)); const body = getJSONfromString<INemesisStartRequest>(String(req.body));
body.target.fp = BigInt(body.target.fp); body.target.fp = BigInt(body.target.fp);
const manifest = getNemesisManifest(body.target.manifest); const manifest = getNemesisManifest(body.target.manifest);
if (account.BuildLabel && version_compare(manifest.minBuild, account.BuildLabel) < 0) { if (account.BuildLabel && version_compare(account.BuildLabel, manifest.minBuild) < 0) {
logger.warn( logger.warn(
`client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.` `client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.`
); );
@ -185,13 +235,15 @@ export const nemesisController: RequestHandler = async (req, res) => {
const weapons: readonly string[] = manifest.weapons; const weapons: readonly string[] = manifest.weapons;
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1); const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
weaponIdx = initialWeaponIdx; weaponIdx = initialWeaponIdx;
do { if (body.target.DisallowedWeapons) {
const weapon = weapons[weaponIdx]; do {
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) { const weapon = weapons[weaponIdx];
break; if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
} break;
weaponIdx = (weaponIdx + 1) % weapons.length; }
} while (weaponIdx != initialWeaponIdx); weaponIdx = (weaponIdx + 1) % weapons.length;
} while (weaponIdx != initialWeaponIdx);
}
} }
inventory.Nemesis = { inventory.Nemesis = {
@ -212,10 +264,10 @@ export const nemesisController: RequestHandler = async (req, res) => {
GuessHistory: [], GuessHistory: [],
Hints: [], Hints: [],
HintProgress: 0, HintProgress: 0,
Weakened: body.target.Weakened, Weakened: false,
PrevOwners: 0, PrevOwners: 0,
HenchmenKilled: 0, HenchmenKilled: 0,
SecondInCommand: body.target.SecondInCommand, SecondInCommand: false,
MissionCount: 0, MissionCount: 0,
LastEnc: 0 LastEnc: 0
}; };
@ -276,7 +328,7 @@ interface INemesisStartRequest {
KillingSuit: string; KillingSuit: string;
killingDamageType: number; killingDamageType: number;
ShoulderHelmet: string; ShoulderHelmet: string;
DisallowedWeapons: string[]; DisallowedWeapons?: string[];
WeaponIdx: number; WeaponIdx: number;
AgentIdx: number; AgentIdx: number;
BirthNode: string; BirthNode: string;

View File

@ -0,0 +1,23 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const renamePetController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree");
const data = getJSONfromString<IRenamePetRequest>(String(req.body));
const details = inventory.KubrowPets.id(data.petId)!.Details!;
details.Name = data.name;
const currencyChanges = updateCurrency(inventory, 15, true);
await inventory.save();
res.json({
...data,
inventoryChanges: currencyChanges
});
};
interface IRenamePetRequest {
petId: string;
name: string;
}

View File

@ -24,7 +24,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
inventory.DialogueHistory.Dialogues ??= []; inventory.DialogueHistory.Dialogues ??= [];
const dialogue = getDialogue(inventory, request.DialogueName); const dialogue = getDialogue(inventory, request.DialogueName);
dialogue.Rank = request.Rank; dialogue.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry; dialogue.Chemistry += request.Chemistry;
dialogue.QueuedDialogues = request.QueuedDialogues; dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) { for (const bool of request.Booleans) {
dialogue.Booleans.push(bool); dialogue.Booleans.push(bool);

View File

@ -3,12 +3,14 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getRecipe } from "@/src/services/itemDataService"; import { getRecipe } from "@/src/services/itemDataService";
import { addItem, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addItem, addKubrowPet, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
import { ExportWeapons } from "warframe-public-export-plus"; import { ExportWeapons } from "warframe-public-export-plus";
import { getRandomElement } from "@/src/services/rngService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
interface IStartRecipeRequest { interface IStartRecipeRequest {
RecipeName: string; RecipeName: string;
@ -42,24 +44,35 @@ export const startRecipeController: RequestHandler = async (req, res) => {
for (let i = 0; i != recipe.ingredients.length; ++i) { for (let i = 0; i != recipe.ingredients.length; ++i) {
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") { if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory; if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
if (category != "LongGuns" && category != "Pistols" && category != "Melee") { const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
throw new Error(`unexpected equipment ingredient type: ${category}`); if (index != -1) {
inventory.KubrowPetEggs!.splice(index, 1);
}
} else {
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
throw new Error(`unexpected equipment ingredient type: ${category}`);
}
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
if (equipmentIndex == -1) {
throw new Error(`could not find equipment item to use for recipe`);
}
pr[category] ??= [];
pr[category].push(inventory[category][equipmentIndex]);
inventory[category].splice(equipmentIndex, 1);
freeUpSlot(inventory, InventorySlot.WEAPONS);
} }
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
if (equipmentIndex == -1) {
throw new Error(`could not find equipment item to use for recipe`);
}
pr[category] ??= [];
pr[category].push(inventory[category][equipmentIndex]);
inventory[category].splice(equipmentIndex, 1);
freeUpSlot(inventory, InventorySlot.WEAPONS);
} else { } else {
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1); await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
} }
} }
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { let inventoryChanges: IInventoryChanges | undefined;
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const spectreLoadout: ISpectreLoadout = { const spectreLoadout: ISpectreLoadout = {
ItemType: recipe.resultType, ItemType: recipe.resultType,
Suits: "", Suits: "",
@ -116,5 +129,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.json({ RecipeId: toOid(pr._id) }); res.json({ RecipeId: toOid(pr._id), InventoryChanges: inventoryChanges });
}; };

View File

@ -3,7 +3,7 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus"; import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItem, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { toStoreItem } from "@/src/services/itemDataService"; import { toStoreItem } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
@ -18,80 +18,83 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1]; syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
} }
const level = data.SacrificeLevel - (syndicate.Title ?? 0); const oldLevel = syndicate.Title ?? 0;
const levelIncrease = data.SacrificeLevel - oldLevel;
if (levelIncrease < 0) {
throw new Error(`syndicate sacrifice can not decrease level`);
}
if (levelIncrease > 1 && !data.AllowMultiple) {
throw new Error(`desired syndicate level is an increase of ${levelIncrease}, max. allowed increase is 1`);
}
const res: ISyndicateSacrificeResponse = { const res: ISyndicateSacrificeResponse = {
AffiliationTag: data.AffiliationTag, AffiliationTag: data.AffiliationTag,
InventoryChanges: {}, InventoryChanges: {},
Level: data.SacrificeLevel, Level: data.SacrificeLevel,
LevelIncrease: level <= 0 ? 1 : level, LevelIncrease: data.SacrificeLevel < 0 ? 1 : levelIncrease,
NewEpisodeReward: false NewEpisodeReward: false
}; };
// Process sacrifices and rewards for every level we're reaching
const manifest = ExportSyndicates[data.AffiliationTag]; const manifest = ExportSyndicates[data.AffiliationTag];
let sacrifice: ISyndicateSacrifice | undefined; for (let level = oldLevel + Math.min(levelIncrease, 1); level <= data.SacrificeLevel; ++level) {
let reward: string | undefined; let sacrifice: ISyndicateSacrifice | undefined;
if (data.SacrificeLevel == 0) { if (level == 0) {
sacrifice = manifest.initiationSacrifice; sacrifice = manifest.initiationSacrifice;
reward = manifest.initiationReward; if (manifest.initiationReward) {
syndicate.Initiated = true; combineInventoryChanges(
} else { res.InventoryChanges,
sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice; (await handleStoreItemAcquisition(manifest.initiationReward, inventory)).InventoryChanges
} );
if (sacrifice) {
res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) };
const miscItemChanges = sacrifice.items.map(x => ({
ItemType: x.ItemType,
ItemCount: x.ItemCount * -1
}));
addMiscItems(inventory, miscItemChanges);
res.InventoryChanges.MiscItems = miscItemChanges;
}
syndicate.Title ??= 0;
syndicate.Title += 1;
if (reward) {
combineInventoryChanges(
res.InventoryChanges,
(await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
);
}
// Quacks like a nightwave syndicate?
if (manifest.dailyChallenges) {
const title = manifest.titles!.find(x => x.level == syndicate.Title);
if (title) {
res.NewEpisodeReward = true;
let rewardType: string;
let rewardCount: number;
if (title.storeItemReward) {
rewardType = title.storeItemReward;
rewardCount = 1;
} else {
rewardType = toStoreItem(title.reward!.ItemType);
rewardCount = title.reward!.ItemCount;
} }
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount)) syndicate.Initiated = true;
.InventoryChanges; } else {
if (Object.keys(rewardInventoryChanges).length == 0) { sacrifice = manifest.titles?.find(x => x.level == level)?.sacrifice;
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
} }
} else {
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) { if (sacrifice) {
syndicate.FreeFavorsEarned ??= []; updateCurrency(inventory, sacrifice.credits, false, res.InventoryChanges);
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
syndicate.FreeFavorsEarned.push(syndicate.Title); for (const item of sacrifice.items) {
addMiscItem(inventory, item.ItemType, item.ItemCount * -1, res.InventoryChanges);
}
}
// Quacks like a nightwave syndicate?
if (manifest.dailyChallenges) {
const title = manifest.titles!.find(x => x.level == level);
if (title) {
res.NewEpisodeReward = true;
let rewardType: string;
let rewardCount: number;
if (title.storeItemReward) {
rewardType = title.storeItemReward;
rewardCount = 1;
} else {
rewardType = toStoreItem(title.reward!.ItemType);
rewardCount = title.reward!.ItemCount;
}
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
.InventoryChanges;
if (Object.keys(rewardInventoryChanges).length == 0) {
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
addMiscItem(inventory, nightwaveCredsItemType, 50, rewardInventoryChanges);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
}
} else {
if (level > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == level)) {
syndicate.FreeFavorsEarned ??= [];
if (!syndicate.FreeFavorsEarned.includes(level)) {
syndicate.FreeFavorsEarned.push(level);
}
} }
} }
} }
// Commit
syndicate.Title = data.SacrificeLevel < 0 ? data.SacrificeLevel + 1 : data.SacrificeLevel;
await inventory.save(); await inventory.save();
response.json(res); response.json(res);

View File

@ -5,7 +5,7 @@ import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTy
import { IOid } from "@/src/types/commonTypes"; import { IOid } from "@/src/types/commonTypes";
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus"; import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
export const syndicateStandingBonusController: RequestHandler = async (req, res) => { export const syndicateStandingBonusController: RequestHandler = async (req, res) => {
@ -54,13 +54,14 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 }; inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
} }
const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true); const affiliationMods: IAffiliationMods[] = [];
addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, affiliationMods, true);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
AffiliationMods: [affiliationMod] AffiliationMods: affiliationMods
}); });
}; };

View File

@ -35,6 +35,17 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
inventory.PlayerLevel += 1; inventory.PlayerLevel += 1;
inventory.TradesRemaining += 1; inventory.TradesRemaining += 1;
if (inventory.PlayerLevel == 2) {
await createMessage(accountId, [
{
sndr: "/Lotus/Language/Game/Maroo",
msg: "/Lotus/Language/Clan/MarooClanSearchDesc",
sub: "/Lotus/Language/Clan/MarooClanSearchTitle",
icon: "/Lotus/Interface/Icons/Npcs/Maroo.png"
}
]);
}
await createMessage(accountId, [ await createMessage(accountId, [
{ {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",

View File

@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
import { import {
ExportArcanes, ExportArcanes,
ExportAvionics, ExportAvionics,
ExportBoosters,
ExportCustoms, ExportCustoms,
ExportDrones, ExportDrones,
ExportGear, ExportGear,
@ -19,12 +20,12 @@ import {
ExportWeapons, ExportWeapons,
TRelicQuality TRelicQuality
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json"; import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
interface ListedItem { interface ListedItem {
uniqueName: string; uniqueName: string;
name: string; name: string;
subtype?: string;
fusionLimit?: number; fusionLimit?: number;
exalted?: string[]; exalted?: string[];
badReason?: "starter" | "frivolous" | "notraw"; badReason?: "starter" | "frivolous" | "notraw";
@ -34,7 +35,6 @@ interface ListedItem {
} }
interface ItemLists { interface ItemLists {
archonCrystalUpgrades: Record<string, string>;
uniqueLevelCaps: Record<string, number>; uniqueLevelCaps: Record<string, number>;
Suits: ListedItem[]; Suits: ListedItem[];
LongGuns: ListedItem[]; LongGuns: ListedItem[];
@ -54,6 +54,7 @@ interface ItemLists {
KubrowPets: ListedItem[]; KubrowPets: ListedItem[];
EvolutionProgress: ListedItem[]; EvolutionProgress: ListedItem[];
mods: ListedItem[]; mods: ListedItem[];
Boosters: ListedItem[];
} }
const relicQualitySuffixes: Record<TRelicQuality, string> = { const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -66,7 +67,6 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
const getItemListsController: RequestHandler = (req, response) => { const getItemListsController: RequestHandler = (req, response) => {
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en"); const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
const res: ItemLists = { const res: ItemLists = {
archonCrystalUpgrades,
uniqueLevelCaps: ExportMisc.uniqueLevelCaps, uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
Suits: [], Suits: [],
LongGuns: [], LongGuns: [],
@ -85,7 +85,8 @@ const getItemListsController: RequestHandler = (req, response) => {
QuestKeys: [], QuestKeys: [],
KubrowPets: [], KubrowPets: [],
EvolutionProgress: [], EvolutionProgress: [],
mods: [] mods: [],
Boosters: []
}; };
for (const [uniqueName, item] of Object.entries(ExportWarframes)) { for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({ res[item.productCategory].push({
@ -175,7 +176,8 @@ const getItemListsController: RequestHandler = (req, response) => {
) { ) {
res.miscitems.push({ res.miscitems.push({
uniqueName: uniqueName, uniqueName: uniqueName,
name: name name: name,
subtype: "Resource"
}); });
} }
} }
@ -193,7 +195,8 @@ const getItemListsController: RequestHandler = (req, response) => {
for (const [uniqueName, item] of Object.entries(ExportGear)) { for (const [uniqueName, item] of Object.entries(ExportGear)) {
res.miscitems.push({ res.miscitems.push({
uniqueName: uniqueName, uniqueName: uniqueName,
name: getString(item.name, lang) name: getString(item.name, lang),
subtype: "Gear"
}); });
} }
const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang); const recipeNameTemplate = getString("/Lotus/Language/Items/BlueprintAndItem", lang);
@ -293,6 +296,13 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const item of Object.values(ExportBoosters)) {
res.Boosters.push({
uniqueName: item.typeName,
name: getString(item.name, lang)
});
}
response.json(res); response.json(res);
}; };

View File

@ -0,0 +1,45 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { ExportBoosters } from "warframe-public-export-plus";
const I32_MAX = 0x7fffffff;
export const setBoosterController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as { ItemType: string; ExpiryDate: number }[];
const inventory = await getInventory(accountId, "Boosters");
const boosters = inventory.Boosters;
if (
requests.some(request => {
if (typeof request.ItemType !== "string") return true;
if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
return true;
if (typeof request.ExpiryDate !== "number") return true;
if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
return false;
})
) {
res.status(400).send("Invalid ItemType provided.");
return;
}
const now = Math.floor(Date.now() / 1000);
for (const { ItemType, ExpiryDate } of requests) {
if (ExpiryDate < now) {
// remove expired boosters
const index = boosters.findIndex(item => item.ItemType === ItemType);
if (index !== -1) {
boosters.splice(index, 1);
}
} else {
const boosterItem = boosters.find(item => item.ItemType === ItemType);
if (boosterItem) {
boosterItem.ExpiryDate = ExpiryDate;
} else {
boosters.push({ ItemType, ExpiryDate });
}
}
}
await inventory.save();
res.end();
};

View File

@ -237,7 +237,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFacti
return passcode; return passcode;
}; };
const reqiuemMods: readonly string[] = [ const requiemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
@ -263,29 +263,51 @@ export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNeme
const passcode = getNemesisPasscode(nemesis); const passcode = getNemesisPasscode(nemesis);
return nemesis.Faction == "FC_INFESTATION" return nemesis.Faction == "FC_INFESTATION"
? passcode.map(i => antivirusMods[i]) ? passcode.map(i => antivirusMods[i])
: passcode.map(i => reqiuemMods[i]); : passcode.map(i => requiemMods[i]);
}; };
export const encodeNemesisGuess = ( // Symbols; 0-7 are the normal requiem mods.
symbol1: number, export const GUESS_NONE = 8;
result1: number, export const GUESS_WILDCARD = 9;
symbol2: number,
result2: number, // Results; there are 3, 4, 5 as well which are more muted versions but unused afaik.
symbol3: number, export const GUESS_NEUTRAL = 0;
result3: number export const GUESS_INCORRECT = 1;
): number => { export const GUESS_CORRECT = 2;
interface NemesisPositionGuess {
symbol: number;
result: number;
}
export type NemesisGuess = [NemesisPositionGuess, NemesisPositionGuess, NemesisPositionGuess];
export const encodeNemesisGuess = (guess: NemesisGuess): number => {
return ( return (
(symbol1 & 0xf) | (guess[0].symbol & 0xf) |
((result1 & 3) << 12) | ((guess[0].result & 3) << 12) |
((symbol2 << 4) & 0xff) | ((guess[1].symbol << 4) & 0xff) |
((result2 << 14) & 0xffff) | ((guess[1].result << 14) & 0xffff) |
((symbol3 & 0xf) << 8) | ((guess[2].symbol & 0xf) << 8) |
((result3 & 3) << 16) ((guess[2].result & 3) << 16)
); );
}; };
export const decodeNemesisGuess = (val: number): number[] => { export const decodeNemesisGuess = (val: number): NemesisGuess => {
return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3]; return [
{
symbol: val & 0xf,
result: (val >> 12) & 3
},
{
symbol: (val & 0xff) >> 4,
result: (val & 0xffff) >> 14
},
{
symbol: (val >> 8) & 0xf,
result: (val >> 16) & 3
}
];
}; };
export interface IKnifeResponse { export interface IKnifeResponse {

View File

@ -10,3 +10,14 @@ export const getMaxStanding = (syndicate: ISyndicate, title: number): number =>
} }
return syndicate.titles.find(x => x.level == title)!.maxStanding; return syndicate.titles.find(x => x.level == title)!.maxStanding;
}; };
export const getMinStanding = (syndicate: ISyndicate, title: number): number => {
if (!syndicate.titles) {
// LibrarySyndicate
return 0;
}
if (title == 0) {
return syndicate.titles.find(x => x.level == -1)!.maxStanding;
}
return syndicate.titles.find(x => x.level == title)!.minStanding;
};

View File

@ -1097,7 +1097,8 @@ const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
LongGuns: { type: [EquipmentSchema], default: undefined }, LongGuns: { type: [EquipmentSchema], default: undefined },
Pistols: { type: [EquipmentSchema], default: undefined }, Pistols: { type: [EquipmentSchema], default: undefined },
Melee: { type: [EquipmentSchema], default: undefined }, Melee: { type: [EquipmentSchema], default: undefined },
SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined } SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined },
KubrowPet: { type: Schema.Types.ObjectId, default: undefined }
}, },
{ id: false } { id: false }
); );
@ -1115,6 +1116,7 @@ pendingRecipeSchema.set("toJSON", {
delete returnedObject.Pistols; delete returnedObject.Pistols;
delete returnedObject.Melees; delete returnedObject.Melees;
delete returnedObject.SuitToUnbrand; delete returnedObject.SuitToUnbrand;
delete returnedObject.KubrowPet;
(returnedObject as IPendingRecipeClient).CompletionDate = { (returnedObject as IPendingRecipeClient).CompletionDate = {
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() } $date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
}; };

View File

@ -9,6 +9,7 @@ import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserCo
import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController"; import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController";
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController"; import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
import { addToGuildController } from "@/src/controllers/api/addToGuildController"; import { addToGuildController } from "@/src/controllers/api/addToGuildController";
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController"; import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
import { archonFusionController } from "@/src/controllers/api/archonFusionController"; import { archonFusionController } from "@/src/controllers/api/archonFusionController";
import { artifactsController } from "@/src/controllers/api/artifactsController"; import { artifactsController } from "@/src/controllers/api/artifactsController";
@ -107,6 +108,7 @@ import { removeFriendGetController, removeFriendPostController } from "@/src/con
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController"; import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController"; import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController"; import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
import { renamePetController } from "@/src/controllers/api/renamePetController";
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController"; import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController"; import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController"; import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
@ -225,6 +227,7 @@ apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
apiRouter.post("/addPendingFriend.php", addPendingFriendController); apiRouter.post("/addPendingFriend.php", addPendingFriendController);
apiRouter.post("/addToAlliance.php", addToAllianceController); apiRouter.post("/addToAlliance.php", addToAllianceController);
apiRouter.post("/addToGuild.php", addToGuildController); apiRouter.post("/addToGuild.php", addToGuildController);
apiRouter.post("/adoptPet.php", adoptPetController);
apiRouter.post("/arcaneCommon.php", arcaneCommonController); apiRouter.post("/arcaneCommon.php", arcaneCommonController);
apiRouter.post("/archonFusion.php", archonFusionController); apiRouter.post("/archonFusion.php", archonFusionController);
apiRouter.post("/artifacts.php", artifactsController); apiRouter.post("/artifacts.php", artifactsController);
@ -294,6 +297,7 @@ apiRouter.post("/releasePet.php", releasePetController);
apiRouter.post("/removeFriend.php", removeFriendPostController); apiRouter.post("/removeFriend.php", removeFriendPostController);
apiRouter.post("/removeFromGuild.php", removeFromGuildController); apiRouter.post("/removeFromGuild.php", removeFromGuildController);
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController); apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
apiRouter.post("/renamePet.php", renamePetController);
apiRouter.post("/rerollRandomMod.php", rerollRandomModController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController); apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveDialogue.php", saveDialogueController);

View File

@ -23,6 +23,7 @@ import { setEvolutionProgressController } from "@/src/controllers/custom/setEvol
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { setBoosterController } from "../controllers/custom/setBoosterController";
const customRouter = express.Router(); const customRouter = express.Router();
@ -46,6 +47,7 @@ customRouter.post("/addXp", addXpController);
customRouter.post("/import", importController); customRouter.post("/import", importController);
customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController);
customRouter.get("/config", getConfigDataController); customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController); customRouter.post("/config", updateConfigDataController);

View File

@ -56,12 +56,18 @@ interface IConfig {
noDojoResearchTime?: boolean; noDojoResearchTime?: boolean;
fastClanAscension?: boolean; fastClanAscension?: boolean;
spoofMasteryRank?: number; spoofMasteryRank?: number;
nightwaveStandingMultiplier?: number;
worldState?: { worldState?: {
creditBoost?: boolean; creditBoost?: boolean;
affinityBoost?: boolean; affinityBoost?: boolean;
resourceBoost?: boolean; resourceBoost?: boolean;
starDays?: boolean; starDays?: boolean;
lockTime?: number; eidolonOverride?: string;
vallisOverride?: string;
nightwaveOverride?: string;
};
dev?: {
keepVendorsExpired?: boolean;
}; };
} }

View File

@ -27,9 +27,21 @@ fs.watchFile(configPath, () => {
}); });
export const validateConfig = (): void => { export const validateConfig = (): void => {
if (typeof config.administratorNames == "string") { let modified = false;
logger.info(`Updating config.json to make administratorNames an array.`); if (config.administratorNames) {
config.administratorNames = [config.administratorNames]; if (!Array.isArray(config.administratorNames)) {
config.administratorNames = [config.administratorNames];
modified = true;
}
for (let i = 0; i != config.administratorNames.length; ++i) {
if (typeof config.administratorNames[i] != "string") {
config.administratorNames[i] = String(config.administratorNames[i]);
modified = true;
}
}
}
if (modified) {
logger.info(`Updating config.json to fix some issues with it.`);
void saveConfig(); void saveConfig();
} }
}; };

View File

@ -82,10 +82,11 @@ import { handleBundleAcqusition } from "./purchaseService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService"; import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
import { createMessage } from "./inboxService"; import { createMessage } from "./inboxService";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper"; import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService"; import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers"; import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
import { TAccountDocument } from "./loginService"; import { TAccountDocument } from "./loginService";
import { unixTimesInMs } from "../constants/timeConstants";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -722,6 +723,10 @@ export const addItem = async (
} }
break; break;
case "Boons":
// Can purchase /Lotus/Upgrades/Boons/DuviriVendorBoonItem from Acrithis, doesn't need to be added to inventory.
return {};
case "Stickers": case "Stickers":
{ {
const entry = inventory.RawUpgrades.find(x => x.ItemType == typeName); const entry = inventory.RawUpgrades.find(x => x.ItemType == typeName);
@ -776,7 +781,9 @@ export const addItem = async (
typeName.substr(1).split("/")[3] == "CatbrowPet" || typeName.substr(1).split("/")[3] == "CatbrowPet" ||
typeName.substr(1).split("/")[3] == "KubrowPet" typeName.substr(1).split("/")[3] == "KubrowPet"
) { ) {
return addKubrowPet(inventory, typeName, undefined, premiumPurchase); if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
}
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) { } else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
if (!seed) { if (!seed) {
throw new Error(`Expected crew member to have a seed`); throw new Error(`Expected crew member to have a seed`);
@ -791,6 +798,12 @@ export const addItem = async (
} }
break; break;
} }
case "Items": {
if (typeName.substr(1).split("/")[3] == "Emotes") {
return addCustomization(inventory, typeName);
}
break;
}
case "NeutralCreatures": { case "NeutralCreatures": {
if (inventory.Horses.length != 0) { if (inventory.Horses.length != 0) {
logger.warn("refusing to add Horse because account already has one"); logger.warn("refusing to add Horse because account already has one");
@ -1015,12 +1028,13 @@ export const addSpaceSuit = (
export const addKubrowPet = ( export const addKubrowPet = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
kubrowPetName: string, kubrowPetName: string,
details: IKubrowPetDetailsDatabase | undefined, details?: IKubrowPetDetailsDatabase,
premiumPurchase: boolean, premiumPurchase: boolean = false,
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => { ): IInventoryChanges => {
combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase)); combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
// TODO: When incubating, this should only be given when claiming the recipe.
const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined; const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
const exalted = kubrowPet?.exalted ?? []; const exalted = kubrowPet?.exalted ?? [];
for (const specialItem of exalted) { for (const specialItem of exalted) {
@ -1069,11 +1083,11 @@ export const addKubrowPet = (
details = { details = {
Name: "", Name: "",
IsPuppy: false, IsPuppy: !premiumPurchase,
HasCollar: true, HasCollar: true,
PrintsRemaining: 2, PrintsRemaining: 3,
Status: Status.StatusStasis, Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000), HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
IsMale: !!getRandomInt(0, 1), IsMale: !!getRandomInt(0, 1),
Size: getRandomInt(70, 100) / 100, Size: getRandomInt(70, 100) / 100,
DominantTraits: traits, DominantTraits: traits,
@ -1188,8 +1202,10 @@ export const addStanding = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
syndicateTag: string, syndicateTag: string,
gainedStanding: number, gainedStanding: number,
isMedallion: boolean = false affiliationMods: IAffiliationMods[] = [],
): IAffiliationMods => { isMedallion: boolean = false,
propagateAlignments: boolean = true
): void => {
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag); let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
const syndicateMeta = ExportSyndicates[syndicateTag]; const syndicateMeta = ExportSyndicates[syndicateTag];
@ -1201,6 +1217,10 @@ export const addStanding = (
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0); const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing; if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
if (syndicate.Title == -2 && syndicate.Standing + gainedStanding < -71000) {
gainedStanding = -71000 + syndicate.Standing;
}
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) { if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) { if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin); gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
@ -1209,10 +1229,27 @@ export const addStanding = (
} }
syndicate.Standing += gainedStanding; syndicate.Standing += gainedStanding;
return { const affiliationMod: IAffiliationMods = {
Tag: syndicateTag, Tag: syndicateTag,
Standing: gainedStanding Standing: gainedStanding
}; };
affiliationMods.push(affiliationMod);
if (syndicateMeta.alignments) {
if (propagateAlignments) {
for (const [tag, factor] of Object.entries(syndicateMeta.alignments)) {
addStanding(inventory, tag, gainedStanding * factor, affiliationMods, isMedallion, false);
}
} else {
while (syndicate.Standing < getMinStanding(syndicateMeta, syndicate.Title ?? 0)) {
syndicate.Title ??= 0;
syndicate.Title -= 1;
affiliationMod.Title ??= 0;
affiliationMod.Title -= 1;
logger.debug(`${syndicateTag} is decreasing to title ${syndicate.Title} after applying alignment`);
}
}
}
}; };
// TODO: AffiliationMods support (Nightwave). // TODO: AffiliationMods support (Nightwave).
@ -1511,7 +1548,8 @@ export const applyClientEquipmentUpdates = (
gearArray.forEach(({ ItemId, XP, InfestationDate }) => { gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
const item = category.id(fromOid(ItemId)); const item = category.id(fromOid(ItemId));
if (!item) { if (!item) {
throw new Error(`No item with id ${fromOid(ItemId)} in ${categoryName}`); logger.warn(`Skipping unknown ${categoryName} item: id ${fromOid(ItemId)} not found`);
return;
} }
if (XP) { if (XP) {
@ -1758,13 +1796,14 @@ export const addChallenges = (
}) - 1 }) - 1
]; ];
} }
affiliation.Standing += meta.standing!;
const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1);
affiliation.Standing += standingToAdd;
if (affiliationMods.length == 0) { if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: nightwaveSyndicateTag }); affiliationMods.push({ Tag: nightwaveSyndicateTag });
} }
affiliationMods[0].Standing ??= 0; affiliationMods[0].Standing ??= 0;
affiliationMods[0].Standing += meta.standing!; affiliationMods[0].Standing += standingToAdd;
} }
} }
} }

View File

@ -1,5 +1,4 @@
import { IKeyChainRequest } from "@/src/types/requestTypes"; import { IKeyChainRequest } from "@/src/types/requestTypes";
import { getIndexAfter } from "@/src/helpers/stringHelpers";
import { import {
dict_de, dict_de,
dict_en, dict_en,
@ -53,20 +52,32 @@ export const getRecipeByResult = (resultType: string): IRecipe | undefined => {
return Object.values(ExportRecipes).find(x => x.resultType == resultType); return Object.values(ExportRecipes).find(x => x.resultType == resultType);
}; };
export const getItemCategoryByUniqueName = (uniqueName: string): string => { export const getItemCategoryByUniqueName = (uniqueName: string): string | undefined => {
//Lotus/Types/Items/MiscItems/PolymerBundle if (uniqueName in ExportCustoms) {
return ExportCustoms[uniqueName].productCategory;
let splitWord = "Items/";
if (!uniqueName.includes("/Items/")) {
splitWord = "/Types/";
} }
if (uniqueName in ExportDrones) {
const index = getIndexAfter(uniqueName, splitWord); return "Drones";
if (index === -1) {
throw new Error(`error parsing item category ${uniqueName}`);
} }
const category = uniqueName.substring(index).split("/")[0]; if (uniqueName in ExportKeys) {
return category; return "LevelKeys";
}
if (uniqueName in ExportGear) {
return "Consumables";
}
if (uniqueName in ExportResources) {
return ExportResources[uniqueName].productCategory;
}
if (uniqueName in ExportSentinels) {
return ExportSentinels[uniqueName].productCategory;
}
if (uniqueName in ExportWarframes) {
return ExportWarframes[uniqueName].productCategory;
}
if (uniqueName in ExportWeapons) {
return ExportWeapons[uniqueName].productCategory;
}
return undefined;
}; };
export const getItemName = (uniqueName: string): string | undefined => { export const getItemName = (uniqueName: string): string | undefined => {
@ -222,7 +233,7 @@ export const isStoreItem = (type: string): boolean => {
}; };
export const toStoreItem = (type: string): string => { export const toStoreItem = (type: string): string => {
if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) { if (type.startsWith("/Lotus/Types/Boosters/")) {
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type); const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type);
if (boosterEntry) { if (boosterEntry) {
return boosterEntry[0]; return boosterEntry[0];

View File

@ -66,15 +66,7 @@ import {
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel"; import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService";
getLiteSortie,
getSortie,
getWorldState,
idToBountyCycle,
idToDay,
idToWeek,
pushClassicBounties
} from "./worldStateService";
import { config } from "./configService"; import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { ISyndicateMissionInfo } from "../types/worldStateTypes"; import { ISyndicateMissionInfo } from "../types/worldStateTypes";
@ -326,8 +318,8 @@ export const addMissionInventoryUpdates = async (
break; break;
} }
case "PlayerSkillGains": { case "PlayerSkillGains": {
inventory.PlayerSkills.LPP_SPACE += value.LPP_SPACE; inventory.PlayerSkills.LPP_SPACE += value.LPP_SPACE ?? 0;
inventory.PlayerSkills.LPP_DRIFTER += value.LPP_DRIFTER; inventory.PlayerSkills.LPP_DRIFTER += value.LPP_DRIFTER ?? 0;
break; break;
} }
case "CustomMarkers": { case "CustomMarkers": {
@ -1182,14 +1174,12 @@ export const addMissionRewards = async (
if (nodeIndex !== -1) inventory.Nemesis.InfNodes.splice(nodeIndex, 1); if (nodeIndex !== -1) inventory.Nemesis.InfNodes.splice(nodeIndex, 1);
if (inventory.Nemesis.InfNodes.length <= 0) { if (inventory.Nemesis.InfNodes.length <= 0) {
const manifest = getNemesisManifest(inventory.Nemesis.manifest);
if (inventory.Nemesis.Faction != "FC_INFESTATION") { if (inventory.Nemesis.Faction != "FC_INFESTATION") {
inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, 4); inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, manifest.systemIndexes.length - 1);
inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank; inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank;
} }
inventory.Nemesis.InfNodes = getInfNodes( inventory.Nemesis.InfNodes = getInfNodes(manifest, inventory.Nemesis.Rank);
getNemesisManifest(inventory.Nemesis.manifest),
inventory.Nemesis.Rank
);
} }
if (inventory.Nemesis.Faction == "FC_INFESTATION") { if (inventory.Nemesis.Faction == "FC_INFESTATION") {
@ -1207,7 +1197,9 @@ export const addMissionRewards = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = rewardInfo.jobId.split("_"); const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = rewardInfo.jobId.split("_");
const syndicateMissions: ISyndicateMissionInfo[] = []; const syndicateMissions: ISyndicateMissionInfo[] = [];
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId)); if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
}
const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId); const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry && syndicateEntry.Jobs) { if (syndicateEntry && syndicateEntry.Jobs) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!]; let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
@ -1236,19 +1228,18 @@ export const addMissionRewards = async (
SyndicateXPItemReward = medallionAmount; SyndicateXPItemReward = medallionAmount;
} else { } else {
if (rewardInfo.JobTier! >= 0) { if (rewardInfo.JobTier! >= 0) {
AffiliationMods.push( addStanding(
addStanding( inventory,
inventory, syndicateEntry.Tag,
syndicateEntry.Tag, Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)),
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)) AffiliationMods
)
); );
} else { } else {
if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) { if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) {
AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000)); addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
} }
if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) { if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) {
AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 5000)); addStanding(inventory, syndicateEntry.Tag, 5000, AffiliationMods);
} }
if ( if (
[ [
@ -1259,7 +1250,7 @@ export const addMissionRewards = async (
"Heists/HeistExploiterBountyOne" "Heists/HeistExploiterBountyOne"
].some(ending => jobType.endsWith(ending)) ].some(ending => jobType.endsWith(ending))
) { ) {
AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000)); addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
} }
} }
} }
@ -1267,9 +1258,9 @@ export const addMissionRewards = async (
} }
if (rewardInfo.challengeMissionId) { if (rewardInfo.challengeMissionId) {
const [syndicateTag, tierStr, chemistryStr] = rewardInfo.challengeMissionId.split("_"); const [syndicateTag, tierStr, chemistryBuddyStr] = rewardInfo.challengeMissionId.split("_");
const tier = Number(tierStr); const tier = Number(tierStr);
const chemistry = Number(chemistryStr); const chemistryBuddy = Number(chemistryBuddyStr);
const isSteelPath = missions?.Tier; const isSteelPath = missions?.Tier;
if (syndicateTag === "ZarimanSyndicate") { if (syndicateTag === "ZarimanSyndicate") {
let medallionAmount = tier + 1; let medallionAmount = tier + 1;
@ -1284,24 +1275,21 @@ export const addMissionRewards = async (
let standingAmount = (tier + 1) * 1000; let standingAmount = (tier + 1) * 1000;
if (tier > 5) standingAmount = 7500; // InfestedLichBounty if (tier > 5) standingAmount = 7500; // InfestedLichBounty
if (isSteelPath) standingAmount *= 1.5; if (isSteelPath) standingAmount *= 1.5;
AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount)); addStanding(inventory, syndicateTag, standingAmount, AffiliationMods);
} }
if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) { if (syndicateTag == "HexSyndicate" && tier < 6) {
const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed; const buddy = chemistryBuddies[chemistryBuddy];
const { nodes, buddies } = getHexBounties(seed);
const buddy = buddies[tier];
logger.debug(`Hex seed is ${seed}, giving chemistry for ${buddy}`);
if (missions?.Tag != nodes[tier]) {
logger.warn(
`Uh-oh, tier ${tier} bounty should've been on ${nodes[tier]} but you were just on ${missions?.Tag}`
);
}
const tomorrowAt0Utc = config.noKimCooldowns
? Date.now()
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
const dialogue = getDialogue(inventory, buddy); const dialogue = getDialogue(inventory, buddy);
dialogue.Chemistry += chemistry; if (Date.now() >= dialogue.BountyChemExpiry.getTime()) {
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc); logger.debug(`Giving 20 chemistry for ${buddy}`);
const tomorrowAt0Utc = config.noKimCooldowns
? Date.now()
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
dialogue.Chemistry += 20;
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc);
} else {
logger.debug(`Already got today's chemistry for ${buddy}`);
}
} }
if (isSteelPath) { if (isSteelPath) {
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1); await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
@ -1556,7 +1544,9 @@ function getRandomMissionDrops(
let isEndlessJob = false; let isEndlessJob = false;
if (syndicateMissionId) { if (syndicateMissionId) {
const syndicateMissions: ISyndicateMissionInfo[] = []; const syndicateMissions: ISyndicateMissionInfo[] = [];
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId)); if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
}
const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId); const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry && syndicateEntry.Jobs) { if (syndicateEntry && syndicateEntry.Jobs) {
let job = syndicateEntry.Jobs[RewardInfo.JobTier!]; let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
@ -1863,7 +1853,16 @@ const libraryPersonalTargetToAvatar: Record<string, string> = {
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar" "/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
}; };
const node_excluded_buddies: Record<string, string> = { const chemistryBuddies: readonly string[] = [
"/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
"/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
];
/*const node_excluded_buddies: Record<string, string> = {
SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue", SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue", SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue", SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
@ -1913,4 +1912,4 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } =>
} }
} }
return { nodes, buddies }; return { nodes, buddies };
}; };*/

View File

@ -6,7 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportVendors, IRange, IVendor } from "warframe-public-export-plus"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
@ -21,7 +21,6 @@ import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorIn
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
@ -47,7 +46,6 @@ const rawVendorManifests: IVendorManifest[] = [
DuviriAcrithisVendorManifest, DuviriAcrithisVendorManifest,
EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabsCommisionsManifest,
EntratiLabsEntratiLabVendorManifest, EntratiLabsEntratiLabVendorManifest,
HubsIronwakeDondaVendorManifest, // uses preprocessing
HubsRailjackCrewMemberVendorManifest, HubsRailjackCrewMemberVendorManifest,
MaskSalesmanManifest, MaskSalesmanManifest,
Nova1999ConquestShopManifest, Nova1999ConquestShopManifest,
@ -83,10 +81,6 @@ const generatableVendors: IGeneratableVendorInfo[] = [
cycleOffset: 1744934400_000, cycleOffset: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
} }
// {
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
// TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest"
// }
]; ];
const getVendorOid = (typeName: string): string => { const getVendorOid = (typeName: string): string => {
@ -261,13 +255,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration); const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
const manifest = ExportVendors[vendorInfo.TypeName]; const manifest = ExportVendors[vendorInfo.TypeName];
const offersToAdd = []; const offersToAdd: IVendorOffer[] = [];
if ( if (!manifest.isOneBinPerCycle) {
manifest.numItems &&
(manifest.numItems.minValue != manifest.numItems.maxValue ||
manifest.items.length != manifest.numItems.minValue) &&
!manifest.isOneBinPerCycle
) {
const remainingItemCapacity: Record<string, number> = {}; const remainingItemCapacity: Record<string, number> = {};
for (const item of manifest.items) { for (const item of manifest.items) {
remainingItemCapacity[item.storeItem] = 1 + item.duplicates; remainingItemCapacity[item.storeItem] = 1 + item.duplicates;
@ -275,35 +264,52 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
for (const offer of info.ItemManifest) { for (const offer of info.ItemManifest) {
remainingItemCapacity[offer.StoreItem] -= 1; remainingItemCapacity[offer.StoreItem] -= 1;
} }
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) {
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
// TODO: Consider per-bin item limits while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider item probability weightings // TODO: Consider per-bin item limits
const item = rng.randomElement(manifest.items)!; // TODO: Consider item probability weightings
if (remainingItemCapacity[item.storeItem] != 0) { const item = rng.randomElement(manifest.items)!;
remainingItemCapacity[item.storeItem] -= 1; if (remainingItemCapacity[item.storeItem] != 0) {
offersToAdd.push(item); remainingItemCapacity[item.storeItem] -= 1;
offersToAdd.push(item);
}
} }
} else {
for (const item of manifest.items) {
if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
remainingItemCapacity[item.storeItem] -= 1;
offersToAdd.push(item);
}
}
for (const e of Object.entries(remainingItemCapacity)) {
const item = manifest.items.find(x => x.storeItem == e[0])!;
if (!item.alwaysOffered) {
while (e[1] != 0) {
e[1] -= 1;
offersToAdd.push(item);
}
}
}
for (const item of manifest.items) {
if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) {
remainingItemCapacity[item.storeItem] -= 1;
offersToAdd.push(item);
}
}
offersToAdd.reverse();
} }
} else { } else {
let binThisCycle; const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
if (manifest.isOneBinPerCycle) {
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
}
for (const rawItem of manifest.items) { for (const rawItem of manifest.items) {
if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) { if (rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem); offersToAdd.push(rawItem);
} }
} }
// For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception.
if (!manifest.isOneBinPerCycle) {
offersToAdd.reverse();
}
} }
const cycleStart = cycleOffset + cycleIndex * cycleDuration; const cycleStart = cycleOffset + cycleIndex * cycleDuration;
for (const rawItem of offersToAdd) { for (const rawItem of offersToAdd) {
const durationHoursRange = toRange(rawItem.durationHours); const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration);
const expiry = const expiry =
cycleStart + cycleStart +
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour; rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
@ -388,4 +394,17 @@ if (isDev) {
) { ) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
} }
const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")!
.VendorInfo.ItemManifest;
if (
pall.length != 5 ||
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
}
} }

View File

@ -19,6 +19,7 @@ import {
IWorldState IWorldState
} from "../types/worldStateTypes"; } from "../types/worldStateTypes";
import { version_compare } from "../helpers/inventoryHelpers"; import { version_compare } from "../helpers/inventoryHelpers";
import { logger } from "../utils/logger";
const sortieBosses = [ const sortieBosses = [
"SORTIE_BOSS_HYENA", "SORTIE_BOSS_HYENA",
@ -166,8 +167,8 @@ const microplanetEndlessJobs = [
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0 const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
const isBeforeNextExpectedWorldStateRefresh = (date: number): boolean => { const isBeforeNextExpectedWorldStateRefresh = (nowMs: number, thenMs: number): boolean => {
return Date.now() + 300_000 > date; return nowMs + 300_000 > thenMs;
}; };
const getSortieTime = (day: number): number => { const getSortieTime = (day: number): number => {
@ -348,6 +349,7 @@ interface IRotatingSeasonChallengePools {
daily: string[]; daily: string[];
weekly: string[]; weekly: string[];
hardWeekly: string[]; hardWeekly: string[];
hasWeeklyPermanent: boolean;
} }
const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => { const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
@ -359,7 +361,12 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/") && x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/") &&
!x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent") !x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
), ),
hardWeekly: syndicate.weeklyChallenges!.filter(x => x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")) hardWeekly: syndicate.weeklyChallenges!.filter(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
),
hasWeeklyPermanent: !!syndicate.weeklyChallenges!.find(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
)
}; };
}; };
@ -416,35 +423,67 @@ const pushWeeklyActs = (
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 0)); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 0));
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 1)); activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 1));
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 2)); if (pools.hasWeeklyPermanent) {
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 3)); activeChallenges.push({
activeChallenges.push({ _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") }, Activation: { $date: { $numberLong: weekStart.toString() } },
Activation: { $date: { $numberLong: weekStart.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions"
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" });
}); activeChallenges.push({
activeChallenges.push({ _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") }, Activation: { $date: { $numberLong: weekStart.toString() } },
Activation: { $date: { $numberLong: weekStart.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus"
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" });
}); activeChallenges.push({
activeChallenges.push({ _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") }, Activation: { $date: { $numberLong: weekStart.toString() } },
Activation: { $date: { $numberLong: weekStart.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" });
}); activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 2));
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 3));
} else {
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 2));
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 3));
activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 4));
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 5));
activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 6));
}
}; };
const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => {
const step = minXp < 1000 ? 1 : 10;
const totalDeciXp = rng.randomInt(minXp / step, maxXp / step);
const xpAmounts: number[] = [];
if (stageCount < 4) {
const perStage = Math.ceil(totalDeciXp / stageCount) * step;
for (let i = 0; i != stageCount; ++i) {
xpAmounts.push(perStage);
}
} else {
const perStage = Math.ceil(Math.round(totalDeciXp * 0.667) / (stageCount - 1)) * step;
for (let i = 0; i != stageCount - 1; ++i) {
xpAmounts.push(perStage);
}
xpAmounts.push(Math.ceil(totalDeciXp * 0.332) * step);
}
return xpAmounts;
};
// Test vectors:
//console.log(generateXpAmounts(new SRng(1337n), 5, 5000, 5000)); // [840, 840, 840, 840, 1660]
//console.log(generateXpAmounts(new SRng(1337n), 3, 40, 40)); // [14, 14, 14]
//console.log(generateXpAmounts(new SRng(1337n), 5, 150, 150)); // [25, 25, 25, 25, 50]
//console.log(generateXpAmounts(new SRng(1337n), 4, 10, 10)); // [2, 2, 2, 4]
//console.log(generateXpAmounts(new SRng(1337n), 4, 15, 15)); // [4, 4, 4, 5]
//console.log(generateXpAmounts(new SRng(1337n), 4, 20, 20)); // [5, 5, 5, 7]
export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], bountyCycle: number): void => { export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], bountyCycle: number): void => {
const table = String.fromCharCode(65 + (bountyCycle % 3)); const table = String.fromCharCode(65 + (bountyCycle % 3));
const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3)); const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3));
const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2)); const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2));
// TODO: xpAmounts need to be calculated based on the jobType somehow?
const seed = new SRng(bountyCycle).randomInt(0, 100_000); const seed = new SRng(bountyCycle).randomInt(0, 100_000);
const bountyCycleStart = bountyCycle * 9000000; const bountyCycleStart = bountyCycle * 9000000;
const bountyCycleEnd = bountyCycleStart + 9000000; const bountyCycleEnd = bountyCycleStart + 9000000;
@ -467,7 +506,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
maxEnemyLevel: 15, maxEnemyLevel: 15,
xpAmounts: [430, 430, 430] xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElement(eidolonJobs),
@ -475,7 +514,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 10, minEnemyLevel: 10,
maxEnemyLevel: 30, maxEnemyLevel: 30,
xpAmounts: [620, 620, 620] xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElement(eidolonJobs),
@ -483,7 +522,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 20, minEnemyLevel: 20,
maxEnemyLevel: 40, maxEnemyLevel: 40,
xpAmounts: [670, 670, 670, 990] xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElement(eidolonJobs),
@ -491,7 +530,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 30, minEnemyLevel: 30,
maxEnemyLevel: 50, maxEnemyLevel: 50,
xpAmounts: [570, 570, 570, 570, 1110] xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElement(eidolonJobs),
@ -499,7 +538,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 5, masteryReq: 5,
minEnemyLevel: 40, minEnemyLevel: 40,
maxEnemyLevel: 60, maxEnemyLevel: 60,
xpAmounts: [740, 740, 740, 740, 1450] xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
}, },
{ {
jobType: rng.randomElement(eidolonJobs), jobType: rng.randomElement(eidolonJobs),
@ -515,7 +554,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 50, minEnemyLevel: 50,
maxEnemyLevel: 70, maxEnemyLevel: 70,
xpAmounts: [840, 840, 840, 840, 1650] xpAmounts: generateXpAmounts(rng, 5, 4500, 5000)
} }
] ]
}); });
@ -539,7 +578,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
maxEnemyLevel: 15, maxEnemyLevel: 15,
xpAmounts: [340, 340, 340] xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
@ -547,7 +586,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 10, minEnemyLevel: 10,
maxEnemyLevel: 30, maxEnemyLevel: 30,
xpAmounts: [660, 660, 660] xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
@ -555,7 +594,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 20, minEnemyLevel: 20,
maxEnemyLevel: 40, maxEnemyLevel: 40,
xpAmounts: [610, 610, 610, 900] xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
@ -563,7 +602,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 30, minEnemyLevel: 30,
maxEnemyLevel: 50, maxEnemyLevel: 50,
xpAmounts: [600, 600, 600, 600, 1170] xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
@ -571,7 +610,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 5, masteryReq: 5,
minEnemyLevel: 40, minEnemyLevel: 40,
maxEnemyLevel: 60, maxEnemyLevel: 60,
xpAmounts: [690, 690, 690, 690, 1350] xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
}, },
{ {
jobType: rng.randomElement(venusJobs), jobType: rng.randomElement(venusJobs),
@ -587,7 +626,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 50, minEnemyLevel: 50,
maxEnemyLevel: 70, maxEnemyLevel: 70,
xpAmounts: [780, 780, 780, 780, 1540] xpAmounts: generateXpAmounts(rng, 5, 4500, 5000)
} }
] ]
}); });
@ -611,7 +650,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 0, masteryReq: 0,
minEnemyLevel: 5, minEnemyLevel: 5,
maxEnemyLevel: 15, maxEnemyLevel: 15,
xpAmounts: [5, 5, 5] xpAmounts: generateXpAmounts(rng, 3, 12, 18)
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElement(microplanetJobs),
@ -619,7 +658,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 1, masteryReq: 1,
minEnemyLevel: 15, minEnemyLevel: 15,
maxEnemyLevel: 25, maxEnemyLevel: 25,
xpAmounts: [12, 12, 12] xpAmounts: generateXpAmounts(rng, 3, 24, 36)
}, },
{ {
jobType: rng.randomElement(microplanetEndlessJobs), jobType: rng.randomElement(microplanetEndlessJobs),
@ -636,7 +675,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 2, masteryReq: 2,
minEnemyLevel: 30, minEnemyLevel: 30,
maxEnemyLevel: 40, maxEnemyLevel: 40,
xpAmounts: [17, 17, 17, 25] xpAmounts: generateXpAmounts(rng, 4, 72, 88)
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElement(microplanetJobs),
@ -644,7 +683,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
masteryReq: 3, masteryReq: 3,
minEnemyLevel: 40, minEnemyLevel: 40,
maxEnemyLevel: 60, maxEnemyLevel: 60,
xpAmounts: [22, 22, 22, 22, 43] xpAmounts: generateXpAmounts(rng, 5, 115, 135)
}, },
{ {
jobType: rng.randomElement(microplanetJobs), jobType: rng.randomElement(microplanetJobs),
@ -924,15 +963,70 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
}; };
}; };
const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
if (config.worldState?.eidolonOverride) {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
if (config.worldState.eidolonOverride == "day") {
if (
//timeSecs < eidolonCycleStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000)
) {
return false;
}
} else {
if (
timeSecs < eidolonCycleNightStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
) {
return false;
}
}
}
if (config.worldState?.vallisOverride) {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleEnd = vallisCycleStart + 1600;
const vallisCycleColdStart = vallisCycleStart + 400;
if (config.worldState.vallisOverride == "cold") {
if (
timeSecs < vallisCycleColdStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
) {
return false;
}
} else {
if (
//timeSecs < vallisCycleStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000)
) {
return false;
}
}
}
return true;
};
export const getWorldState = (buildLabel?: string): IWorldState => { export const getWorldState = (buildLabel?: string): IWorldState => {
const day = Math.trunc((Date.now() - EPOCH) / 86400000); let timeSecs = Math.round(Date.now() / 1000);
while (!doesTimeSatsifyConstraints(timeSecs)) {
timeSecs -= 60;
}
const timeMs = timeSecs * 1000;
const day = Math.trunc((timeMs - EPOCH) / 86400000);
const week = Math.trunc(day / 7); const week = Math.trunc(day / 7);
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const worldState: IWorldState = { const worldState: IWorldState = {
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel, BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
Time: config.worldState?.lockTime || Math.round(Date.now() / 1000), Time: timeSecs,
Goals: [], Goals: [],
Alerts: [], Alerts: [],
Sorties: [], Sorties: [],
@ -986,11 +1080,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day - 2)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day - 2));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day - 1)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day - 1));
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day - 0)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day - 0));
if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) { if (isBeforeNextExpectedWorldStateRefresh(timeMs, EPOCH + (day + 1) * 86400000)) {
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day + 1)); worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day + 1));
} }
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week); pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week);
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) { if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week + 1); pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week + 1);
} }
} }
@ -999,7 +1093,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new SRng(week).randomInt(0, 0xff_ffff); worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new SRng(week).randomInt(0, 0xff_ffff);
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation // Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
let bountyCycle = Math.trunc(Date.now() / 9000000); let bountyCycle = Math.trunc(timeSecs / 9000);
let bountyCycleEnd: number | undefined; let bountyCycleEnd: number | undefined;
do { do {
const bountyCycleStart = bountyCycle * 9000000; const bountyCycleStart = bountyCycle * 9000000;
@ -1030,7 +1124,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}); });
pushClassicBounties(worldState.SyndicateMissions, bountyCycle); pushClassicBounties(worldState.SyndicateMissions, bountyCycle);
} while (isBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle); } while (isBeforeNextExpectedWorldStateRefresh(timeMs, bountyCycleEnd) && ++bountyCycle);
if (config.worldState?.creditBoost) { if (config.worldState?.creditBoost) {
worldState.GlobalUpgrades.push({ worldState.GlobalUpgrades.push({
@ -1073,15 +1167,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
{ {
const rollover = getSortieTime(day); const rollover = getSortieTime(day);
if (Date.now() < rollover) { if (timeMs < rollover) {
worldState.Sorties.push(getSortie(day - 1)); worldState.Sorties.push(getSortie(day - 1));
} }
if (isBeforeNextExpectedWorldStateRefresh(rollover)) { if (isBeforeNextExpectedWorldStateRefresh(timeMs, rollover)) {
worldState.Sorties.push(getSortie(day)); worldState.Sorties.push(getSortie(day));
} }
// The client does not seem to respect activation for classic syndicate missions, so only pushing current ones. // The client does not seem to respect activation for classic syndicate missions, so only pushing current ones.
const sdy = Date.now() >= rollover ? day : day - 1; const sdy = timeMs >= rollover ? day : day - 1;
const rng = new SRng(sdy); const rng = new SRng(sdy);
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48049", "ArbitersSyndicate"); pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48049", "ArbitersSyndicate");
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4804a", "CephalonSudaSyndicate"); pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4804a", "CephalonSudaSyndicate");
@ -1093,7 +1187,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// Archon Hunt cycling every week // Archon Hunt cycling every week
worldState.LiteSorties.push(getLiteSortie(week)); worldState.LiteSorties.push(getLiteSortie(week));
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) { if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
worldState.LiteSorties.push(getLiteSortie(week + 1)); worldState.LiteSorties.push(getLiteSortie(week + 1));
} }
@ -1130,12 +1224,12 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// 1999 Calendar Season cycling every week + YearIteration every 4 weeks // 1999 Calendar Season cycling every week + YearIteration every 4 weeks
worldState.KnownCalendarSeasons.push(getCalendarSeason(week)); worldState.KnownCalendarSeasons.push(getCalendarSeason(week));
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) { if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1)); worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1));
} }
// Sentient Anomaly cycling every 30 minutes // Sentient Anomaly cycling every 30 minutes
const halfHour = Math.trunc(Date.now() / (unixTimesInMs.hour / 2)); const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2));
const tmp = { const tmp = {
cavabegin: "1690761600", cavabegin: "1690761600",
PurchasePlatformLockEnabled: true, PurchasePlatformLockEnabled: true,
@ -1258,6 +1352,15 @@ export const isArchwingMission = (node: IRegion): boolean => {
}; };
export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string | undefined => { export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string | undefined => {
if (config.worldState?.nightwaveOverride) {
if (config.worldState.nightwaveOverride in nightwaveTagToSeason) {
return config.worldState.nightwaveOverride;
}
logger.warn(`ignoring invalid config value for worldState.nightwaveOverride`, {
value: config.worldState.nightwaveOverride,
valid_values: Object.keys(nightwaveTagToSeason)
});
}
if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) { if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) {
return "RadioLegionIntermission13Syndicate"; return "RadioLegionIntermission13Syndicate";
} }
@ -1268,6 +1371,20 @@ export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string
}; };
const nightwaveTagToSeason: Record<string, number> = { const nightwaveTagToSeason: Record<string, number> = {
RadioLegionIntermission13Syndicate: 15, RadioLegionIntermission13Syndicate: 15, // Nora's Mix Vol. 9
RadioLegionIntermission12Syndicate: 14 RadioLegionIntermission12Syndicate: 14, // Nora's Mix Vol. 8
RadioLegionIntermission11Syndicate: 13, // Nora's Mix Vol. 7
RadioLegionIntermission10Syndicate: 12, // Nora's Mix Vol. 6
RadioLegionIntermission9Syndicate: 11, // Nora's Mix Vol. 5
RadioLegionIntermission8Syndicate: 10, // Nora's Mix Vol. 4
RadioLegionIntermission7Syndicate: 9, // Nora's Mix Vol. 3
RadioLegionIntermission6Syndicate: 8, // Nora's Mix Vol. 2
RadioLegionIntermission5Syndicate: 7, // Nora's Mix Vol. 1
RadioLegionIntermission4Syndicate: 6, // Nora's Choice
RadioLegionIntermission3Syndicate: 5, // Intermission III
RadioLegion3Syndicate: 4, // Glassmaker
RadioLegionIntermission2Syndicate: 3, // Intermission II
RadioLegion2Syndicate: 2, // The Emissary
RadioLegionIntermissionSyndicate: 1, // Intermission I
RadioLegionSyndicate: 0 // The Wolf of Saturn Six
}; };

View File

@ -765,7 +765,8 @@ export interface IKubrowPetDetailsClient extends Omit<IKubrowPetDetailsDatabase,
export enum Status { export enum Status {
StatusAvailable = "STATUS_AVAILABLE", StatusAvailable = "STATUS_AVAILABLE",
StatusStasis = "STATUS_STASIS" StatusStasis = "STATUS_STASIS",
StatusIncubating = "STATUS_INCUBATING"
} }
export interface ILastSortieRewardClient { export interface ILastSortieRewardClient {
@ -929,10 +930,14 @@ export interface IPendingRecipeDatabase {
Pistols?: IEquipmentDatabase[]; Pistols?: IEquipmentDatabase[];
Melee?: IEquipmentDatabase[]; Melee?: IEquipmentDatabase[];
SuitToUnbrand?: Types.ObjectId; SuitToUnbrand?: Types.ObjectId;
KubrowPet?: Types.ObjectId;
} }
export interface IPendingRecipeClient export interface IPendingRecipeClient
extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand"> { extends Omit<
IPendingRecipeDatabase,
"CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand" | "KubrowPet"
> {
CompletionDate: IMongoDate; CompletionDate: IMongoDate;
} }

View File

@ -96,7 +96,7 @@ export type IMissionInventoryUpdateRequest = {
FpsSamples: number; FpsSamples: number;
EvolutionProgress?: IEvolutionProgress[]; EvolutionProgress?: IEvolutionProgress[];
FocusXpIncreases?: number[]; FocusXpIncreases?: number[];
PlayerSkillGains: IPlayerSkills; PlayerSkillGains: Partial<IPlayerSkills>;
CustomMarkers?: ICustomMarkers[]; CustomMarkers?: ICustomMarkers[];
LoreFragmentScans?: ILoreFragmentScan[]; LoreFragmentScans?: ILoreFragmentScan[];
VoidTearParticipantsCurrWave?: { VoidTearParticipantsCurrWave?: {

View File

@ -1,125 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5dbb4c41e966f7886c3ce939"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament",
"ItemPrices": [
{
"ItemCount": 25,
"ItemType": "/Lotus/Types/Items/MiscItems/PrimeBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e945f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e9468"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits",
"ItemPrices": [
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e9469"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 35000,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e946a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e946b"
}
}
],
"PropertyTextHash": "62B64A8065B7C0FA345895D4BC234621",
"Expiry": {
"$date": {
"$numberLong": "604800000"
}
}
}
}

View File

@ -1,83 +0,0 @@
{
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": "+20% Energy from Health pickups, +20% Health from Energy pickups",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": "+30% Energy from Health pickups, +30% Health from Energy pickups",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": "+25% Melee Critical Damage",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": "+37.5% Melee Critical Damage",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": "+25% Primary Status Chance",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": "+37.5% Primary Status Chance",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": "+25% Secondary Critical Chance",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": "+37.5% Secondary Critical Chance",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": "+10% Ability Duration",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": "+15% Ability Duration",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": "+10% Ability Strength",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": "+15% Ability Strength",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": "+150 Armor",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": "+225 Armor",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": "+5 Shields on inflicting Blast Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": "+7.5 Shields on inflicting Blast Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": "+25% Casting Speed",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": "+37.5% Casting Speed",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": "+10% Ability Damage on enemies affected by Corrosion Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": "+15% Ability Damage on enemies affected by Corrosion Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": "Increase max stacks of Corrosion Status by +2",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": "Increase max stacks of Corrosion Status by +3",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": "+25% Melee Critical Damage (Doubles over 500 Energy)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": "+37% Melee Critical Damage (Doubles over 500 Energy)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": "+30% Primary Electricity Damage (+10% per additional Shard)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": "+45% Primary Electricity Damage (+15% per additional Shard)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": "+10% Ability Damage on enemies affected by Electricity Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": "+15% Ability Damage on enemies affected by Electricity Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": "+50 Energy Max",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": "+75 Energy Max",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": "+50% Energy Orb Effectiveness",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": "+75% Energy Orb Effectiveness",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": "+100% Health Orb Effectiveness",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": "+150% Health Orb Effectiveness",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": "+150 Health",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": "+225 Health",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": "+1 Health per enemy killed with Blast Damage (Max 300 Health)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": "+2 Health per enemy killed with Blast Damage (Max 450 Health)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": "+15% Parkour Velocity",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": "+22.5% Parkour Velocity",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": "+10% Ability Damage on enemies affected by Radiation Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": "+15% Ability Damage on enemies affected by Radiation Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": "+5 Health Regen/s",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": "+7.5 Health Regen/s",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": "+150 Shield",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": "+225 Shield",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": "+30% Energy on Spawn",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": "+45% Energy on Spawn",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": "+30% Toxin Status Effect Damage",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": "+45% Toxin Status Effect Damage",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": "+2 Health on damaging enemies with Toxin Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": "+3 Health on damaging enemies with Toxin Status",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": "+1% Secondary Critical Chance per Heat-affected enemy killed (Max 50%)",
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": "+1.5% Secondary Critical Chance per Heat-affected enemy killed (Max 75%)",
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": "+7.5% Ability Range",
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": "+5% Ability Efficiency",
"/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": "+0.5 Energy Regen/s",
"/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": "+5m Enemy Radar",
"/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": "+7m Loot Radar",
"/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": "+15% Ammo Max",
"/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": "-3% Enemy Armor",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": "100% Primary and Secondary Magazine Refill on Mercy",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": "100% chance to drop a Health Orb on Mercy",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": "50% chance to drop an Energy Orb on Mercy",
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": "+50% to retry on Hacking failure",
"/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": "75% Damage Reduction while Hacking",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": "Mercy Kills reduce Companion Recovery by 15s",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": "+60% Parkour Speed after a Mercy for 15s",
"/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": "+8s to Hacking",
"/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": "Shock enemies within 20m while Hacking",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": "50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy",
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": "Unlock 5 lockers within 20m after Hacking",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": "Blind enemies within 18m on Mercy",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": "100% chance for next ability cast to gain +50% Ability Strength on Mercy",
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": "+75% Sprint Speed for 15s after Hacking",
"/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": "Speed of Mercy Kills increased by 50%",
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": "Invisible for 15 seconds after hacking"
}

View File

@ -416,6 +416,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_Boosters"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
<input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
<tbody id="Boosters-list"></tbody>
</table>
</div>
</div>
</div>
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
@ -722,6 +736,10 @@
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label> <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" /> <input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" />
</div> </div>
<div class="form-group mt-2">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" value="1" />
</div>
<button class="btn btn-primary mt-3" type="submit" data-loc="cheats_saveSettings"></button> <button class="btn btn-primary mt-3" type="submit" data-loc="cheats_saveSettings"></button>
</form> </form>
</div> </div>
@ -804,6 +822,7 @@
<datalist id="datalist-ModularParts-CATBROW_MUTAGEN"></datalist> <datalist id="datalist-ModularParts-CATBROW_MUTAGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist> <datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist> <datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<datalist id="datalist-Boosters"></datalist>
<script src="/webui/libs/jquery-3.6.0.min.js"></script> <script src="/webui/libs/jquery-3.6.0.min.js"></script>
<script src="/webui/libs/whirlpool-js.min.js"></script> <script src="/webui/libs/whirlpool-js.min.js"></script>
<script src="/webui/libs/single.js"></script> <script src="/webui/libs/single.js"></script>

View File

@ -213,6 +213,86 @@ function fetchItemList() {
document.getElementById("changeSyndicate").innerHTML = ""; document.getElementById("changeSyndicate").innerHTML = "";
document.getElementById("changeSyndicate").appendChild(syndicateNone); document.getElementById("changeSyndicate").appendChild(syndicateNone);
// prettier-ignore
data.archonCrystalUpgrades = {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": loc("upgrade_Equilibrium").split("|VAL|").join("20"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": loc("upgrade_Equilibrium").split("|VAL|").join("30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": loc("upgrade_MeleeCritDamage").split("|VAL|").join("25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": loc("upgrade_MeleeCritDamage").split("|VAL|").join("37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": loc("upgrade_PrimaryStatusChance").split("|VAL|").join("25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": loc("upgrade_PrimaryStatusChance").split("|VAL|").join("37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": loc("upgrade_SecondaryCritChance").split("|VAL|").join("25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": loc("upgrade_SecondaryCritChance").split("|VAL|").join("37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmourMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmourMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": loc("upgrade_WarframeBlastProc").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": loc("upgrade_WarframeBlastProc").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("37.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": loc("upgrade_WarframeCorrosiveDamageBoost").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": loc("upgrade_WarframeCorrosiveDamageBoost").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": loc("upgrade_WarframeCorrosiveStack").split("|VAL|").join("2"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": loc("upgrade_WarframeCorrosiveStack").split("|VAL|").join("3"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": loc("upgrade_WarframeCritDamageBoost").split("|VAL|").join("25"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": loc("upgrade_WarframeCritDamageBoost").split("|VAL|").join("37"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": loc("upgrade_WarframeElectricDamage").split("|VAL1|").join("30").split("|VAL2|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": loc("upgrade_WarframeElectricDamage").split("|VAL1|").join("45").split("|VAL2|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": loc("upgrade_WarframeElectricDamageBoost").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": loc("upgrade_WarframeElectricDamageBoost").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": loc("upgrade_WarframeEnergyMax").split("|VAL|").join("50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": loc("upgrade_WarframeEnergyMax").split("|VAL|").join("75"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": loc("upgrade_WarframeGlobeEffectEnergy").split("|VAL|").join("50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": loc("upgrade_WarframeGlobeEffectEnergy").split("|VAL|").join("75"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": loc("upgrade_WarframeGlobeEffectHealth").split("|VAL|").join("100"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": loc("upgrade_WarframeGlobeEffectHealth").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": loc("upgrade_WarframeHealthMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": loc("upgrade_WarframeHealthMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": loc("upgrade_WarframeHPBoostFromImpact").split("|VAL1|").join("1").split("|VAL2|").join("300"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": loc("upgrade_WarframeHPBoostFromImpact").split("|VAL1|").join("2").split("|VAL2|").join("450"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("22.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeRegen").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeRegen").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": loc("upgrade_WarframeShieldMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": loc("upgrade_WarframeShieldMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("45"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": loc("upgrade_WarframeToxinDamage").split("|VAL|").join("30"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": loc("upgrade_WarframeToxinDamage").split("|VAL|").join("45"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": loc("upgrade_WarframeToxinHeal").split("|VAL|").join("2"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": loc("upgrade_WarframeToxinHeal").split("|VAL|").join("3"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": loc("upgrade_WeaponCritBoostFromHeat").split("|VAL1|").join("1").split("|VAL2|").join("50"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": loc("upgrade_WeaponCritBoostFromHeat").split("|VAL1|").join("1.5").split("|VAL2|").join("75"),
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": loc("upgrade_AvatarAbilityRange"),
"/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": loc("upgrade_AvatarAbilityEfficiency"),
"/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": loc("upgrade_AvatarEnergyRegen"),
"/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": loc("upgrade_AvatarEnemyRadar"),
"/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": loc("upgrade_AvatarLootRadar"),
"/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": loc("upgrade_WeaponAmmoMax"),
"/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": loc("upgrade_EnemyArmorReductionAura"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": loc("upgrade_OnExecutionAmmo"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": loc("upgrade_OnExecutionHealthDrop"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": loc("upgrade_OnExecutionEnergyDrop"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": loc("upgrade_OnFailHackReset"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": loc("upgrade_DamageReductionOnHack"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": loc("upgrade_OnExecutionReviveCompanion"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": loc("upgrade_OnExecutionParkourSpeed"),
"/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": loc("upgrade_AvatarTimeLimitIncrease"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": loc("upgrade_ElectrifyOnHack"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": loc("upgrade_OnExecutionTerrify"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": loc("upgrade_OnHackLockers"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": loc("upgrade_OnExecutionBlind"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": loc("upgrade_OnExecutionDrainPower"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": loc("upgrade_OnHackSprintSpeed"),
"/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": loc("upgrade_SwiftExecute"),
"/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": loc("upgrade_OnHackInvis"),
};
window.archonCrystalUpgrades = data.archonCrystalUpgrades; window.archonCrystalUpgrades = data.archonCrystalUpgrades;
// Add mods mising in data sources // Add mods mising in data sources
@ -312,7 +392,7 @@ function fetchItemList() {
document.getElementById("changeSyndicate").appendChild(option); document.getElementById("changeSyndicate").appendChild(option);
}); });
} else { } else {
const nameSet = new Set(); const nameToItems = {};
items.forEach(item => { items.forEach(item => {
item.name = item.name.replace(/<.+>/g, "").trim(); item.name = item.name.replace(/<.+>/g, "").trim();
if ("badReason" in item) { if ("badReason" in item) {
@ -322,6 +402,11 @@ function fetchItemList() {
item.name += " " + loc("code_badItem"); item.name += " " + loc("code_badItem");
} }
} }
nameToItems[item.name] ??= [];
nameToItems[item.name].push(item);
});
items.forEach(item => {
if (type == "ModularParts") { if (type == "ModularParts") {
const supportedModularParts = [ const supportedModularParts = [
"LWPT_HB_DECK", "LWPT_HB_DECK",
@ -360,15 +445,26 @@ function fetchItemList() {
.appendChild(option); .appendChild(option);
} }
} else if (item.badReason != "notraw") { } else if (item.badReason != "notraw") {
if (nameSet.has(item.name)) { const ambiguous = nameToItems[item.name].length > 1;
//console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`); let canDisambiguate = true;
} else { if (ambiguous) {
nameSet.add(item.name); for (const i2 of nameToItems[item.name]) {
if (!i2.subtype) {
canDisambiguate = false;
break;
}
}
}
if (!ambiguous || canDisambiguate || nameToItems[item.name][0] == item) {
const option = document.createElement("option"); const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName); option.setAttribute("data-key", item.uniqueName);
option.value = item.name; option.value = item.name;
if (ambiguous && canDisambiguate) {
option.value += " (" + item.subtype + ")";
}
document.getElementById("datalist-" + type).appendChild(option); document.getElementById("datalist-" + type).appendChild(option);
} else {
//console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`);
} }
} }
itemMap[item.uniqueName] = { ...item, type }; itemMap[item.uniqueName] = { ...item, type };
@ -476,7 +572,7 @@ function updateInventory() {
} }
let anyExaltedMissingXP = false; let anyExaltedMissingXP = false;
if (item.XP >= maxXP && "exalted" in itemMap[item.ItemType]) { if (item.XP >= maxXP && item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) { for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType); const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) { if (exaltedItem) {
@ -995,6 +1091,63 @@ function updateInventory() {
} }
} }
document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? ""; document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
document.getElementById("Boosters-list").innerHTML = "";
const now = Math.floor(Date.now() / 1000);
data.Boosters.forEach(({ ItemType, ExpiryDate }) => {
if (ExpiryDate < now) {
// Booster has expired, skip it
return;
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[ItemType]?.name ?? ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000);
const inlineForm = document.createElement("form");
const input = document.createElement("input");
inlineForm.style.display = "inline-block";
inlineForm.onsubmit = function (event) {
event.preventDefault();
doChangeBoosterExpiry(ItemType, input);
};
input.type = "datetime-local";
input.classList.add("form-control");
input.classList.add("form-control-sm");
input.value = timeString;
let changed = false;
input.onchange = function () {
changed = true;
};
input.onblur = function () {
if (changed) {
doChangeBoosterExpiry(ItemType, input);
}
};
inlineForm.appendChild(input);
td.appendChild(inlineForm);
const removeButton = document.createElement("a");
removeButton.title = loc("code_remove");
removeButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
removeButton.href = "#";
removeButton.onclick = function (event) {
event.preventDefault();
setBooster(ItemType, 0);
};
td.appendChild(removeButton);
tr.appendChild(td);
}
document.getElementById("Boosters-list").appendChild(tr);
});
}); });
}); });
} }
@ -2011,3 +2164,77 @@ function handleModularSelection(category) {
}); });
}); });
} }
function setBooster(ItemType, ExpiryDate, callback) {
revalidateAuthz(() => {
$.post({
url: "/custom/setBooster?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ExpiryDate
}
])
}).done(function () {
updateInventory();
if (callback) callback();
});
});
}
function doAcquireBoosters() {
const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
if (!uniqueName) {
$("#acquire-type-Boosters").addClass("is-invalid").focus();
return;
}
const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days
setBooster(uniqueName, ExpiryDate, () => {
$("#acquire-type-Boosters").val("");
updateInventory();
});
}
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value);
// cast local datetime string to unix timestamp
const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000;
if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus();
return false;
}
setBooster(ItemType, ExpiryDate);
return true;
}
function formatDatetime(fmt, date) {
if (typeof date === "number") date = new Date(date);
return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => {
switch (match) {
case "%Y":
return date.getFullYear().toString();
case "%y":
return date.getFullYear().toString().slice(-2);
case "%m":
return (date.getMonth() + 1).toString().padStart(2, "0");
case "%D":
case "%d":
return date.getDate().toString().padStart(2, "0");
case "%H":
return date.getHours().toString().padStart(2, "0");
case "%h":
return (date.getHours() % 12).toString().padStart(2, "0");
case "%M":
return date.getMinutes().toString().padStart(2, "0");
case "%S":
case "%s":
return date.getSeconds().toString().padStart(2, "0");
case "%P":
case "%p":
return date.getHours() < 12 ? "am" : "pm";
default:
return match;
}
});
}

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`, quests_completeAll: `Alle Quests abschließen`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`, cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`,
cheats_fastClanAscension: `Schneller Clan-Aufstieg`, cheats_fastClanAscension: `Schneller Clan-Aufstieg`,
cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_saveSettings: `Einstellungen speichern`, cheats_saveSettings: `Einstellungen speichern`,
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
@ -173,5 +175,57 @@ dict = {
cheats_none: `Keines`, cheats_none: `Keines`,
import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`, import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
import_submit: `Absenden`, import_submit: `Absenden`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -97,6 +97,7 @@ dict = {
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
inventory_Boosters: `Boosters`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Complete All Quests`, quests_completeAll: `Complete All Quests`,
@ -162,6 +163,7 @@ dict = {
cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_noDojoResearchTime: `No Dojo Research Time`,
cheats_fastClanAscension: `Fast Clan Ascension`, cheats_fastClanAscension: `Fast Clan Ascension`,
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`,
cheats_saveSettings: `Save Settings`, cheats_saveSettings: `Save Settings`,
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,
@ -172,5 +174,57 @@ dict = {
cheats_none: `None`, cheats_none: `None`,
import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.`, import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.`,
import_submit: `Submit`, import_submit: `Submit`,
upgrade_Equilibrium: `+|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `+|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `+|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `+|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `+|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `+|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `+|VAL| Armor`,
upgrade_WarframeBlastProc: `+|VAL| Shields on inflicting Blast Status`,
upgrade_WarframeCastingSpeed: `+|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `+|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `+|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `+|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `+|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `+|VAL| Shield`,
upgrade_WarframeStartingEnergy: `+|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `+|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `+|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `+5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `+0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `+5m Enemy Radar`,
upgrade_AvatarLootRadar: `+7m Loot Radar`,
upgrade_WeaponAmmoMax: `+15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `-3% Enemy Armor`,
upgrade_OnExecutionAmmo: `100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `100% chance to drop a Health Orb on Mercy`,
upgrade_OnExecutionEnergyDrop: `50% chance to drop an Energy Orb on Mercy`,
upgrade_OnFailHackReset: `+50% to retry on Hacking failure`,
upgrade_DamageReductionOnHack: `75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `+60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `s to Hacking`,
upgrade_ElectrifyOnHack: `Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `+75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `Speed of Mercy Kills increased by 50%`,
upgrade_OnHackInvis: `Invisible for 15 seconds after hacking`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
inventory_Boosters: `Potenciadores`,
quests_list: `Misiones`, quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`, quests_completeAll: `Completar todas las misiones`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`,
cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_fastClanAscension: `Ascenso rápido del clan`,
cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`,
cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`,
cheats_saveSettings: `Guardar configuración`, cheats_saveSettings: `Guardar configuración`,
cheats_account: `Cuenta`, cheats_account: `Cuenta`,
cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,
@ -173,5 +175,57 @@ dict = {
cheats_none: `Ninguno`, cheats_none: `Ninguno`,
import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador <b>serán sobrescritos</b> en tu cuenta.`, import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador <b>serán sobrescritos</b> en tu cuenta.`,
import_submit: `Enviar`, import_submit: `Enviar`,
upgrade_Equilibrium: `+|VAL|% de Energía al recoger salud, +|VAL|% de Salud al recoger energía`,
upgrade_MeleeCritDamage: `+|VAL|% de daño crítico cuerpo a cuerpo`,
upgrade_PrimaryStatusChance: `+|VAL|% de probabilidad de estado en armas primarias`,
upgrade_SecondaryCritChance: `+|VAL|% de probabilidad crítica en armas secundarias`,
upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`,
upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`,
upgrade_WarframeArmourMax: `+|VAL| de armadura`,
upgrade_WarframeBlastProc: `+|VAL| de escudos al infligir estado de explosión`,
upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`,
upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% de daño crítico cuerpo a cuerpo (se duplica con más de 500 de energía)`,
upgrade_WarframeElectricDamage: `+|VAL1|% de daño eléctrico en armas primarias (+|VAL2|% por fragmento adicional)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado eléctrico`,
upgrade_WarframeEnergyMax: `+|VAL| de energía máxima`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% de efectividad de orbes de energía`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% de efectividad de orbes de salud`,
upgrade_WarframeHealthMax: `+|VAL| de salud máxima`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| de salud por enemigo eliminado con daño explosivo (máximo |VAL2| de salud)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de velocidad de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado radiactivo`,
upgrade_WarframeRegen: `+|VAL| de regeneración de salud por segundo`,
upgrade_WarframeShieldMax: `+|VAL| de escudo`,
upgrade_WarframeStartingEnergy: `+|VAL|% de energía al reaparecer`,
upgrade_WarframeToxinDamage: `+|VAL|% de daño por efecto de estado tóxico`,
upgrade_WarframeToxinHeal: `+|VAL| de salud al dañar enemigos con estado tóxico`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% de probabilidad crítica secundaria por enemigo afectado por calor eliminado (máx. |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% de alcance de habilidades`,
upgrade_AvatarAbilityEfficiency: `+5% de eficiencia de habilidades`,
upgrade_AvatarEnergyRegen: `+0.5 de regeneración de energía por segundo`,
upgrade_AvatarEnemyRadar: `+5m de radar de enemigos`,
upgrade_AvatarLootRadar: `+7m de radar de botín`,
upgrade_WeaponAmmoMax: `+15% de munición máxima`,
upgrade_EnemyArmorReductionAura: `-3% de armadura enemiga`,
upgrade_OnExecutionAmmo: `Recarga al 100% el cargador primario y secundario tras ejecución (Misericordia)`,
upgrade_OnExecutionHealthDrop: `100% de probabilidad de soltar un orbe de salud tras ejecución (Misericordia)`,
upgrade_OnExecutionEnergyDrop: `50% de probabilidad de soltar un orbe de energía tras ejecución (Misericordia)`,
upgrade_OnFailHackReset: `+50% de probabilidad de reintento al fallar un hackeo`,
upgrade_DamageReductionOnHack: `75% de reducción de daño al hackear`,
upgrade_OnExecutionReviveCompanion: `Las ejecuciones reducen el tiempo de recuperación del compañero en 15s`,
upgrade_OnExecutionParkourSpeed: `+60% de velocidad de parkour durante 15s tras una ejecución`,
upgrade_AvatarTimeLimitIncrease: `+|VAL|s al tiempo de hackeo`,
upgrade_ElectrifyOnHack: `Electrocuta a los enemigos en un radio de 20m al hackear`,
upgrade_OnExecutionTerrify: `50% de probabilidad de que enemigos en un radio de 15m entren en pánico por 8s tras una ejecución`,
upgrade_OnHackLockers: `Desbloquea 5 casilleros en un radio de 20m tras hackear`,
upgrade_OnExecutionBlind: `Ciega a los enemigos en un radio de 18m tras una ejecución`,
upgrade_OnExecutionDrainPower: `100% de probabilidad de que la siguiente habilidad tenga +50% de fuerza tras una ejecución`,
upgrade_OnHackSprintSpeed: `+75% de velocidad de carrera durante 15s después de hackear`,
upgrade_SwiftExecute: `Velocidad de ejecuciones aumentada en un 50%`,
upgrade_OnHackInvis: `Invisible durante 15 segundos después de hackear`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -34,8 +34,8 @@ dict = {
code_rerollsNumber: `Nombre de rerolls`, code_rerollsNumber: `Nombre de rerolls`,
code_viewStats: `Voir les stats`, code_viewStats: `Voir les stats`,
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`, code_rankUp: `Monter de rang`,
code_rankDown: `[UNTRANSLATED] Rank down`, code_rankDown: `Baisser de rang`,
code_count: `Quantité`, code_count: `Quantité`,
code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
@ -59,7 +59,7 @@ dict = {
login_emailLabel: `Email`, login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`, login_passwordLabel: `Mot de passe`,
login_loginButton: `Connexion`, login_loginButton: `Connexion`,
login_registerButton: `[UNTRANSLATED] Register`, login_registerButton: `S'enregistrer`,
navbar_logout: `Déconnexion`, navbar_logout: `Déconnexion`,
navbar_renameAccount: `Renommer le compte`, navbar_renameAccount: `Renommer le compte`,
navbar_deleteAccount: `Supprimer le compte`, navbar_deleteAccount: `Supprimer le compte`,
@ -83,21 +83,22 @@ dict = {
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moas`, inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bêtes`, inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`, inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`, inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`, inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
inventory_bulkRankUpSuits: `Toutes les Warframes rang max`, inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes rang max`, inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings rang max`, inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`,
inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing rang max`, inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing au rang max`,
inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`, inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
quests_list: `Quêtes`, quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`, quests_completeAll: `Compléter toutes les quêtes`,
@ -117,9 +118,9 @@ dict = {
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`, mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`, mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, mods_removeUnranked: `Retirer les mods sans rang`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`, mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`, cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
cheats_server: `Serveur`, cheats_server: `Serveur`,
cheats_skipTutorial: `Passer le tutoriel`, cheats_skipTutorial: `Passer le tutoriel`,
@ -131,9 +132,9 @@ dict = {
cheats_infiniteEndo: `Endo infini`, cheats_infiniteEndo: `Endo infini`,
cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteRegalAya: `Aya Raffiné infini`,
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`, cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`,
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`, cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`, cheats_dontSubtractConsumables: `Ne pas retirer de consommables`,
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`, cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`, cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`,
@ -151,11 +152,11 @@ dict = {
cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`, cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`, cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`, cheats_skipClanKeyCrafting: `Passer le craft de la clé de clan`,
cheats_noDojoRoomBuildStage: `Aucune attente (construction des salles)`, cheats_noDojoRoomBuildStage: `Aucune attente (construction des salles)`,
cheats_noDojoDecoBuildStage: `Aucune attente (construction des décorations)`, cheats_noDojoDecoBuildStage: `Aucune attente (construction des décorations)`,
cheats_fastDojoRoomDestruction: `Destruction de salle instantanée (Dojo)`, cheats_fastDojoRoomDestruction: `Destruction de salle instantanée (Dojo)`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `Ascension de clan rapide`, cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_saveSettings: `Sauvegarder les paramètres`, cheats_saveSettings: `Sauvegarder les paramètres`,
cheats_account: `Compte`, cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
@ -173,5 +175,57 @@ dict = {
cheats_none: `Aucun`, cheats_none: `Aucun`,
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`, import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
import_submit: `Soumettre`, import_submit: `Soumettre`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`, inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
quests_list: `Квесты`, quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`, quests_completeAll: `Завершить все квесты`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`, cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
cheats_fastClanAscension: `Мгновенное Вознесение Клана`, cheats_fastClanAscension: `Мгновенное Вознесение Клана`,
cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_saveSettings: `Сохранить настройки`, cheats_saveSettings: `Сохранить настройки`,
cheats_account: `Аккаунт`, cheats_account: `Аккаунт`,
cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,
@ -173,5 +175,57 @@ dict = {
cheats_none: `Отсутствует`, cheats_none: `Отсутствует`,
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
import_submit: `Отправить`, import_submit: `Отправить`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };

View File

@ -1,4 +1,4 @@
// Chinese translation by meb154 // Chinese translation by meb154 & bishan178
dict = { dict = {
general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`,
general_addButton: `添加`, general_addButton: `添加`,
@ -18,7 +18,7 @@ dict = {
code_kDrive: `K式悬浮板`, code_kDrive: `K式悬浮板`,
code_legendaryCore: `传奇核心`, code_legendaryCore: `传奇核心`,
code_traumaticPeculiar: `创伤怪奇`, code_traumaticPeculiar: `创伤怪奇`,
code_starter: `|MOD| (有瑕疵的)`, code_starter: `|MOD|(有瑕疵的)`,
code_badItem: `(Imposter)`, code_badItem: `(Imposter)`,
code_maxRank: `满级`, code_maxRank: `满级`,
code_rename: `重命名`, code_rename: `重命名`,
@ -28,7 +28,7 @@ dict = {
code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`, code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`,
code_noEquipmentToRankUp: `没有可升级的装备。`, code_noEquipmentToRankUp: `没有可升级的装备。`,
code_succAdded: `已成功添加。`, code_succAdded: `已成功添加。`,
code_succRemoved: `[UNTRANSLATED] Successfully removed.`, code_succRemoved: `已成功移除。`,
code_buffsNumber: `增益数量`, code_buffsNumber: `增益数量`,
code_cursesNumber: `负面数量`, code_cursesNumber: `负面数量`,
code_rerollsNumber: `洗卡次数`, code_rerollsNumber: `洗卡次数`,
@ -39,27 +39,27 @@ dict = {
code_count: `数量`, code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusAllUnlocked: `所有专精学派均已解锁。`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`,
code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗`, code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗?`,
code_succImport: `导入成功。`, code_succImport: `导入成功。`,
code_gild: `镀金`, code_gild: `镀金`,
code_moa: `恐鸟`, code_moa: `恐鸟`,
code_zanuka: `猎犬`, code_zanuka: `猎犬`,
code_stage: `[UNTRANSLATED] Stage`, code_stage: `阶段`,
code_complete: `[UNTRANSLATED] Complete`, code_complete: `完成`,
code_nextStage: `[UNTRANSLATED] Next stage`, code_nextStage: `下一阶段`,
code_prevStage: `[UNTRANSLATED] Previous stage`, code_prevStage: `上一阶段`,
code_reset: `[UNTRANSLATED] Reset`, code_reset: `重置`,
code_setInactive: `[UNTRANSLATED] Make the quest inactive`, code_setInactive: `使任务处于未激活状态`,
code_completed: `[UNTRANSLATED] Completed`, code_completed: `已完成`,
code_active: `[UNTRANSLATED] Active`, code_active: `正在执行`,
code_pigment: `颜料`, code_pigment: `颜料`,
code_mature: `[UNTRANSLATED] Mature for combat`, code_mature: `成长并战备`,
code_unmature: `[UNTRANSLATED] Regress genetic aging`, code_unmature: `逆转衰老基因`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`,
login_emailLabel: `电子邮箱`, login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`, login_passwordLabel: `密码`,
login_loginButton: `登录`, login_loginButton: `登录`,
login_registerButton: `[UNTRANSLATED] Register`, login_registerButton: `注册账号`,
navbar_logout: `退出登录`, navbar_logout: `退出登录`,
navbar_renameAccount: `重命名账户`, navbar_renameAccount: `重命名账户`,
navbar_deleteAccount: `删除账户`, navbar_deleteAccount: `删除账户`,
@ -82,22 +82,23 @@ dict = {
inventory_operatorAmps: `增幅器`, inventory_operatorAmps: `增幅器`,
inventory_hoverboards: `K式悬浮板`, inventory_hoverboards: `K式悬浮板`,
inventory_moaPets: `恐鸟`, inventory_moaPets: `恐鸟`,
inventory_kubrowPets: `[UNTRANSLATED] Beasts`, inventory_kubrowPets: `动物同伴`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_evolutionProgress: `灵化之源进度`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失Archwing`, inventory_bulkAddSpaceSuits: `添加缺失Archwing`,
inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`,
inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinels: `添加缺失守护`,
inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`,
inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`,
inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`,
inventory_Boosters: `加成器`,
quests_list: `任务`, quests_list: `任务`,
quests_completeAll: `完成所有任务`, quests_completeAll: `完成所有任务`,
@ -111,15 +112,15 @@ dict = {
currency_owned: `当前拥有 |COUNT|。`, currency_owned: `当前拥有 |COUNT|。`,
powersuit_archonShardsLabel: `执刑官源力石槽位`, powersuit_archonShardsLabel: `执刑官源力石槽位`,
powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`, powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`, powersuit_archonShardsDescription2: `请注意, 在加载时, 每个执政官源力石都需要一定的时间来生效。`,
mods_addRiven: `添加裂罅MOD`, mods_addRiven: `添加裂罅MOD`,
mods_fingerprint: `印记`, mods_fingerprint: `印记`,
mods_fingerprintHelp: `需要印记相关的帮助?`, mods_fingerprintHelp: `需要印记相关的帮助?`,
mods_rivens: `裂罅MOD`, mods_rivens: `裂罅MOD`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`, mods_addMissingUnrankedMods: `添加所有缺失的Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, mods_removeUnranked: `删除所有未升级的Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`, mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`,
cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`, cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`,
cheats_server: `服务器`, cheats_server: `服务器`,
cheats_skipTutorial: `跳过教程`, cheats_skipTutorial: `跳过教程`,
@ -131,47 +132,100 @@ dict = {
cheats_infiniteEndo: `无限内融核心`, cheats_infiniteEndo: `无限内融核心`,
cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteRegalAya: `无限御品阿耶`,
cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_infiniteHelminthMaterials: `无限Helminth材料`,
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`, cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`,
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`, cheats_dontSubtractConsumables: `消耗物品使用时无损耗`,
cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`, cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`,
cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllSkins: `解锁所有外观`,
cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`,
cheats_unlockAllDecoRecipes: `[UNTRANSLATED] Unlock All Dojo Deco Recipes`, cheats_unlockAllDecoRecipes: `解锁所有道场配方`,
cheats_universalPolarityEverywhere: `全局万用极性`, cheats_universalPolarityEverywhere: `全局万用极性`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`, cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`,
cheats_unlockExilusEverywhere: `全物品自带适配器`, cheats_unlockExilusEverywhere: `全物品自带适配器`,
cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`, cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`,
cheats_noDailyStandingLimits: `无每日声望限制`, cheats_noDailyStandingLimits: `无每日声望限制`,
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`, cheats_noDailyFocusLimit: `指挥官专精无每日获取上限`,
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`, cheats_noArgonCrystalDecay: `氩结晶无衰变`,
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`, cheats_noMasteryRankUpCooldown: `段位考核无冷却时间`,
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `商城或商人无购买限制`,
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`, cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`,
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`, cheats_noKimCooldowns: `无 KIM 冷却时间`,
cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, cheats_syndicateMissionsRepeatable: `集团任务可重复`,
cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `资源提取器不会损毁`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`, cheats_skipClanKeyCrafting: `跳过氏族钥匙制作, 进入道场无需氏族钥匙`,
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`, cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`, cheats_noDojoDecoBuildStage: `道场装饰建造立即完成`,
cheats_fastDojoRoomDestruction: `快速拆除道场房间`, cheats_fastDojoRoomDestruction: `快速拆除道场房间`,
cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchCosts: `无视道场研究消耗`,
cheats_noDojoResearchTime: `无视道场研究时间`, cheats_noDojoResearchTime: `无视道场研究时间`,
cheats_fastClanAscension: `快速升级氏族`, cheats_fastClanAscension: `快速升级氏族`,
cheats_spoofMasteryRank: `伪造精通段位(-1为禁用`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`,
cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`,
cheats_saveSettings: `保存设置`, cheats_saveSettings: `保存设置`,
cheats_account: `账户`, cheats_account: `账户`,
cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_unlockAllFocusSchools: `解锁所有专精学派`,
cheats_helminthUnlockAll: `完全升级Helminth`, cheats_helminthUnlockAll: `完全升级Helminth`,
cheats_intrinsicsUnlockAll: `[UNTRANSLATED] Max Rank All Intrinsics`, cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`, cheats_changeButton: `更改`,
cheats_none: ``, cheats_none: ``,
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`,
import_submit: `提交`, import_submit: `提交`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
prettier_sucks_ass: `` prettier_sucks_ass: ``
}; };