Compare commits

...

31 Commits

Author SHA1 Message Date
00cea6788e fix: sync config with db only after connection is established (#2346)
The error message had regressed in the case where connection could not be established.

Reviewed-on: OpenWF/SpaceNinjaServer#2346
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 15:08:36 -07:00
58bdb2d2ec chore(webui): improving inconsistent, long string (#2344)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2344
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-06-28 10:28:09 -07:00
c4c622d82b feat: baro's void surplus (#2334)
Closes #2284

Reviewed-on: OpenWF/SpaceNinjaServer#2334
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:50:40 -07:00
44a129ab0b feat: create genetic imprint (#2337)
Re #2212

Reviewed-on: OpenWF/SpaceNinjaServer#2337
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:48:12 -07:00
5a7caa5ba9 feat: disableDailyTribute config (#2338)
Reviewed-on: OpenWF/SpaceNinjaServer#2338
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:47:39 -07:00
a9c5e30994 chore: deal with visiting navigation not resyncing inventory (#2340)
Closes #2339

Reviewed-on: OpenWF/SpaceNinjaServer#2340
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:46:45 -07:00
f0547cb9e6 chore(webui): update Chinese translation (#2341)
Reviewed-on: OpenWF/SpaceNinjaServer#2341
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-28 09:45:45 -07:00
ef3d3b92c7 feat: darvo deal (#2261)
Closes #2260

Reviewed-on: OpenWF/SpaceNinjaServer#2261
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:39:41 -07:00
a9359bd989 feat(webui): the circuit override (#2335)
Re #2312

Reviewed-on: OpenWF/SpaceNinjaServer#2335
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 20:28:44 -07:00
3d21813a79 fix(vscode): update launch.json 2025-06-28 01:28:45 +02:00
7cad831702 chore: update PE+ (#2336)
Closes #2332

Reviewed-on: OpenWF/SpaceNinjaServer#2336
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 15:16:22 -07:00
0f2b6c68cd chore: use some instead of find 2025-06-27 19:29:55 +02:00
4fcac6dc37 feat: duviri murmur reward tiers (#2331)
Reviewed-on: OpenWF/SpaceNinjaServer#2331
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:22:00 -07:00
d2cae012a7 chore: add operation eight claw trophies to allDecoRecipes (#2330)
Closes #2295

Reviewed-on: OpenWF/SpaceNinjaServer#2330
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:48 -07:00
b36d524953 feat: personal deco capacity costs (#2329)
Closes #2278

Reviewed-on: OpenWF/SpaceNinjaServer#2329
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:27 -07:00
abb5b8880f chore(webui): keep config in sync with multiple tabs (#2325)
Adding "wsid" to uniquely identify a given tab (by the websocket connection) so we can avoid needless refreshing on the same tab.

Closes #2316

Reviewed-on: OpenWF/SpaceNinjaServer#2325
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:05 -07:00
4895b4630b feat: credit boosters (+ daily first win) (#2324)
Daily first win is kinda weird because the client doesn't even seem to acknowledge it.

Also fixed missionCompletionCredits being added to inventory inconsistently (sometimes once, sometimes twice).

Closes #1086
Closes #2322

Reviewed-on: OpenWF/SpaceNinjaServer#2324
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:20:37 -07:00
690b872b5e chore(webui): update Chinese translation (#2328)
Reviewed-on: OpenWF/SpaceNinjaServer#2328
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-26 22:26:35 -07:00
d77fe60cd8 fix(webui): apply consistent margins (#2327)
Reviewed-on: OpenWF/SpaceNinjaServer#2327
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 22:26:22 -07:00
3cae42c7d6 fix: acrithis vendor freezing with fullyStockedVendors (#2326)
Permanent offers were not satisfying bin constraints

Reviewed-on: OpenWF/SpaceNinjaServer#2326
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 22:25:31 -07:00
bbccee0637 fix: ignore purchaseQuantity for login reward items (#2321)
cryotic amount should not be multiplied by 3000...

Reviewed-on: OpenWF/SpaceNinjaServer#2321
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:40:06 -07:00
31e24c27ad chore: ignore invalid item ids in saveLoadout (#2320)
With the 'IsNew' flag + webui delete item, this is quite easy to trigger and shouldn't prevent the other changes from going through.

Reviewed-on: OpenWF/SpaceNinjaServer#2320
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:39:46 -07:00
4acd87aae6 chore: handle CalendarProgress in updateChallengeProgress 2025-06-26 19:35:03 -07:00
d8ff601be7 fix: array out of bounds when processing CalendarProgress 2025-06-26 19:35:03 -07:00
d79e7c0274 feat(webui): world state config (#2318)
Re #2312. Will need some follow-up considerations for circuit game modes.

Reviewed-on: OpenWF/SpaceNinjaServer#2318
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:32:53 -07:00
4f1f9592b0 chore: use chokidar for configWatcherService (#2315)
It's just a lot snappier + works flawlessly under Bun.

Reviewed-on: OpenWF/SpaceNinjaServer#2315
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 15:30:01 -07:00
764cdd1ab8 feat: worldState.allTheFissures (#2313)
Closes #2294

Reviewed-on: OpenWF/SpaceNinjaServer#2313
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:32:26 -07:00
0ba641a2ac chore: update PE+ (#2311)
Closes #2309

Reviewed-on: OpenWF/SpaceNinjaServer#2311
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:31:52 -07:00
eb7b51852b fix: use exact quantity when adding gear items by StoreItem (#2310)
Closes #2304

Reviewed-on: OpenWF/SpaceNinjaServer#2310
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:31:40 -07:00
a3be376489 chore(webui): add Thalys to Incarnon List (#2299)
Closes #2298

Reviewed-on: OpenWF/SpaceNinjaServer#2299
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-06-26 11:02:40 -07:00
d94cd38120 chore(webui): update Chinese translation (#2291)
Reviewed-on: OpenWF/SpaceNinjaServer#2291
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-26 06:44:03 -07:00
61 changed files with 1516 additions and 398 deletions

View File

@ -11,17 +11,17 @@
"node": true
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/restrict-plus-operands": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/restrict-template-expressions": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"no-loss-of-precision": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-explicit-any": "error",
"no-loss-of-precision": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-base-to-string": "off",
"no-case-declarations": "error",
"prettier/prettier": "error",

3
.vscode/launch.json vendored
View File

@ -8,8 +8,7 @@
"type": "node",
"request": "launch",
"name": "Debug and Watch",
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
"args": ["${workspaceFolder}/src/index.ts"],
"args": ["${workspaceFolder}/scripts/dev.js"],
"console": "integratedTerminal"
}
]

View File

@ -34,4 +34,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six
- `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`)
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.

View File

@ -58,6 +58,7 @@
"fastClanAscension": false,
"missionsCanGiveAllRelics": false,
"unlockAllSimarisResearchEntries": false,
"disableDailyTribute": false,
"spoofMasteryRank": -1,
"nightwaveStandingMultiplier": 1,
"unfaithfulBugFixes": {
@ -74,7 +75,9 @@
"vallisOverride": "",
"duviriOverride": "",
"nightwaveOverride": "",
"circuitGameModes": null
"allTheFissures": "",
"circuitGameModes": null,
"darvoStockMultiplier": 1
},
"dev": {
"keepVendorsExpired": false

12
package-lock.json generated
View File

@ -14,6 +14,7 @@
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
"json-with-bigint": "^3.4.4",
@ -22,7 +23,7 @@
"ncp": "^2.0.0",
"typescript": "^5.5",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.71",
"warframe-public-export-plus": "^0.5.74",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
@ -31,7 +32,6 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"chokidar": "^4.0.3",
"eslint": "^8",
"eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3",
@ -997,7 +997,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
@ -2803,7 +2802,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
@ -3388,9 +3386,9 @@
}
},
"node_modules/warframe-public-export-plus": {
"version": "0.5.71",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.71.tgz",
"integrity": "sha512-TCS2wPRsBzuURJlIMDhygAHaLsKVZ7dGuC73WZ/iMyn3gKVwA98nnaIj24D+UceWS08fwq4ilWAfUzHJd6X29A=="
"version": "0.5.74",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.74.tgz",
"integrity": "sha512-pA7dxA0lKn9w/2Sc97oxnn+CEzL1SrT9XriNLTDF4Xp+2SBEpGcfbqbdR9ljPQJopIbrc9Zy02R+uBQVomcwyA=="
},
"node_modules/warframe-riven-info": {
"version": "0.1.2",

View File

@ -28,6 +28,7 @@
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
"json-with-bigint": "^3.4.4",
@ -36,7 +37,7 @@
"ncp": "^2.0.0",
"typescript": "^5.5",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.71",
"warframe-public-export-plus": "^0.5.74",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
@ -45,7 +46,6 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"chokidar": "^4.0.3",
"eslint": "^8",
"eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3",

View File

@ -1,16 +1,12 @@
import { getAccountForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
const data = Buffer.from([
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
0x3a, 0x31, 0x0a
]);
res.writeHead(200, {
"Content-Type": "text/html",
"Content-Length": data.length
});
res.end(data);
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const today = Math.trunc(Date.now() / 86400000) * 86400;
if (account.DailyFirstWinDate != today) {
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
} else {
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
}
};
export { checkDailyMissionBonusController };

View File

@ -13,7 +13,8 @@ import {
addItem,
addRecipes,
occupySlot,
combineInventoryChanges
combineInventoryChanges,
addKubrowPetPrint
} from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
@ -119,6 +120,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
}
}
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = {
...InventoryChanges,

View File

@ -1,8 +1,10 @@
import { DailyDeal } from "@/src/models/worldStateModel";
import { RequestHandler } from "express";
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
res.json({
StoreItem: req.query.productName,
AmountSold: 0
AmountSold: dailyDeal.AmountSold
});
};

View File

@ -9,15 +9,26 @@ import {
updateCurrency
} from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IOid } from "@/src/types/commonTypes";
import { IInventoryChanges, IPurchaseParams, PurchaseSource } from "@/src/types/purchaseTypes";
import { IPurchaseParams, IPurchaseResponse, PurchaseSource } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
const checkPurchaseParams = (params: IPurchaseParams): boolean => {
switch (params.Source) {
case PurchaseSource.Market:
return params.UsePremium;
case PurchaseSource.DailyDeal:
return true;
}
return false;
};
export const giftingController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGiftingRequest>(String(req.body));
if (data.PurchaseParams.Source != PurchaseSource.Market || !data.PurchaseParams.UsePremium) {
if (!checkPurchaseParams(data.PurchaseParams)) {
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
}
@ -58,16 +69,19 @@ export const giftingController: RequestHandler = async (req, res) => {
}
senderInventory.GiftsRemaining -= 1;
const inventoryChanges: IInventoryChanges = updateCurrency(
senderInventory,
data.PurchaseParams.ExpectedPrice,
true
);
const response: IPurchaseResponse = {
InventoryChanges: {}
};
if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
} else {
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
}
if (data.PurchaseParams.StoreItem in ExportBundles) {
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
if (bundle.giftingBonus) {
combineInventoryChanges(
inventoryChanges,
response.InventoryChanges,
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
);
}
@ -99,9 +113,7 @@ export const giftingController: RequestHandler = async (req, res) => {
}
]);
res.json({
InventoryChanges: inventoryChanges
});
res.json(response);
};
interface IGiftingRequest {

View File

@ -24,6 +24,8 @@ import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { Ship } from "@/src/models/shipModel";
import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
import { Inbox } from "@/src/models/inboxModel";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { DailyDeal } from "@/src/models/worldStateModel";
export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request);
@ -37,6 +39,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
// Handle daily reset
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
const today = Math.trunc(Date.now() / 86400000);
for (const key of allDailyAffiliationKeys) {
inventory[key] = 16000 + inventory.PlayerLevel * 500;
}
@ -47,12 +51,12 @@ export const inventoryController: RequestHandler = async (request, response) =>
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
if (inventory.NextRefill) {
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
const daysPassed = today - lastLoginDay;
if (config.noArgonCrystalDecay) {
inventory.FoundToday = undefined;
} else {
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
const today = Math.trunc(Date.now() / 86400000);
const daysPassed = today - lastLoginDay;
for (let i = 0; i != daysPassed; ++i) {
const numArgonCrystals =
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
@ -84,11 +88,29 @@ export const inventoryController: RequestHandler = async (request, response) =>
inventory.FoundToday = undefined;
}
}
if (inventory.UsedDailyDeals.length != 0) {
if (daysPassed == 1) {
const todayAt0Utc = today * 86400000;
const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour));
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
const darvoOid =
((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
const deal = await DailyDeal.findById(darvoOid);
if (deal) {
inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us
} else {
inventory.UsedDailyDeals = [];
}
} else {
inventory.UsedDailyDeals = [];
}
}
}
cleanupInventory(inventory);
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
//await inventory.save();
}
@ -291,9 +313,6 @@ export const getInventoryResponse = async (
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
}
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
inventoryResponse.LastInventorySync = undefined;
// Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true;

View File

@ -8,6 +8,7 @@ import {
setAccountGotLoginRewardToday
} from "@/src/services/loginRewardService";
import { getInventory } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService";
export const loginRewardsController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
@ -15,7 +16,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
if (today == account.LastLoginRewardDate) {
if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
res.json({
DailyTributeInfo: {
IsMilestoneDay: isMilestoneDay,

View File

@ -88,7 +88,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
AffiliationMods,
SyndicateXPItemReward,
ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion);
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();

View File

@ -57,7 +57,11 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
component.DecoCapacity -= meta.capacityCost;
}
} else {
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
if (!itemType || meta.dojoCapacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
component.DecoCapacity -= meta.dojoCapacityCost;
if (deco.Sockets !== undefined) {
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
1;

View File

@ -11,6 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => {
const inventory = await getInventory(accountId);
const response = await handlePurchase(purchaseRequest, inventory);
await inventory.save();
//console.log(JSON.stringify(response, null, 2));
res.json(response);
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -45,9 +45,9 @@ export const startRecipeController: RequestHandler = async (req, res) => {
for (let i = 0; i != recipe.ingredients.length; ++i) {
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
if (index != -1) {
inventory.KubrowPetEggs!.splice(index, 1);
inventory.KubrowPetEggs.splice(index, 1);
}
} else {
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
@ -72,6 +72,10 @@ export const startRecipeController: RequestHandler = async (req, res) => {
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_DISTILL_PRINT") {
pr.KubrowPet = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length]);
const pet = inventory.KubrowPets.id(pr.KubrowPet)!;
pet.Details!.PrintsRemaining -= 1;
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const spectreLoadout: ISpectreLoadout = {
ItemType: recipe.resultType,

View File

@ -1,7 +1,7 @@
import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountForRequest } from "@/src/services/loginService";
import { addChallenges, getInventory } from "@/src/services/inventoryService";
import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService";
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
import { IAffiliationMods } from "@/src/types/purchaseTypes";
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
@ -25,13 +25,17 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
);
}
for (const [key, value] of getEntriesUnsafe(challenges)) {
if (value === undefined) {
logger.error(`Challenge progress update key ${key} has no value`);
continue;
}
switch (key) {
case "ChallengesFixVersion":
inventory.ChallengesFixVersion = value;
break;
case "SeasonChallengeHistory":
value!.forEach(({ challenge, id }) => {
value.forEach(({ challenge, id }) => {
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
if (itemIndex !== -1) {
inventory.SeasonChallengeHistory[itemIndex].id = id;
@ -41,6 +45,10 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
});
break;
case "CalendarProgress":
addCalendarProgress(inventory, value);
break;
case "ChallengeProgress":
case "SeasonChallengeCompletions":
case "ChallengePTS":
@ -63,5 +71,6 @@ interface IUpdateChallengeProgressRequest {
ChallengeProgress?: IChallengeProgress[];
SeasonChallengeHistory?: ISeasonChallenge[];
SeasonChallengeCompletions?: ISeasonChallenge[];
CalendarProgress?: { challenge: string }[];
crossPlaySetting?: string;
}

View File

@ -26,7 +26,7 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
if (mission.Completes == 0) {
mission.Completes++;
if (node.missionReward) {
addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
addFixedLevelRewards(node.missionReward, MissionRewards);
}
}
mission.Tier = 1;

View File

@ -0,0 +1,44 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWatcherService";
import { sendWsBroadcastExcept } from "@/src/services/webService";
export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
const responseData: Record<string, boolean | string | number | null> = {};
for (const id of req.body as string[]) {
const [obj, idx] = configIdToIndexable(id);
responseData[id] = obj[idx] ?? null;
}
res.json(responseData);
} else {
res.status(401).end();
}
};
export const setConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
for (const [id, value] of Object.entries(req.body as Record<string, boolean | string | number>)) {
const [obj, idx] = configIdToIndexable(id);
obj[idx] = value;
}
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
await saveConfig();
res.end();
} else {
res.status(401).end();
}
};
const configIdToIndexable = (id: string): [Record<string, boolean | string | number | undefined>, string] => {
let obj = config as unknown as Record<string, never>;
const arr = id.split(".");
while (arr.length > 1) {
obj = obj[arr[0]];
arr.splice(0, 1);
}
return [obj, arr[0]];
};

View File

@ -1,14 +0,0 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
const getConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
res.json(config);
} else {
res.status(401).end();
}
};
export { getConfigDataController };

View File

@ -55,6 +55,7 @@ interface ItemLists {
EvolutionProgress: ListedItem[];
mods: ListedItem[];
Boosters: ListedItem[];
//circuitGameModes: ListedItem[];
}
const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -64,6 +65,10 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
VPQ_PLATINUM: " [Exceptional]"
};
/*const toTitleCase = (str: string): string => {
return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
};*/
const getItemListsController: RequestHandler = (req, response) => {
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
const res: ItemLists = {
@ -87,6 +92,36 @@ const getItemListsController: RequestHandler = (req, response) => {
EvolutionProgress: [],
mods: [],
Boosters: []
/*circuitGameModes: [
{
uniqueName: "Survival",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang))
},
{
uniqueName: "VoidFlood",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang))
},
{
uniqueName: "Excavation",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang))
},
{
uniqueName: "Defense",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang))
},
{
uniqueName: "Exterminate",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang))
},
{
uniqueName: "Assassination",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang))
},
{
uniqueName: "Alchemy",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang))
}
]*/
};
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({

View File

@ -23,9 +23,9 @@ export const setBoosterController: RequestHandler = async (req, res) => {
res.status(400).send("Invalid ItemType provided.");
return;
}
const now = Math.floor(Date.now() / 1000);
const now = Math.trunc(Date.now() / 1000);
for (const { ItemType, ExpiryDate } of requests) {
if (ExpiryDate < now) {
if (ExpiryDate <= now) {
// remove expired boosters
const index = boosters.findIndex(item => item.ItemType === ItemType);
if (index !== -1) {

View File

@ -1,21 +0,0 @@
import { RequestHandler } from "express";
import { saveConfig } from "@/src/services/configWatcherService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { config, IConfig } from "@/src/services/configService";
export const updateConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
const data = req.body as IUpdateConfigDataRequest;
config[data.key] = data.value;
await saveConfig();
res.end();
} else {
res.status(401).end();
}
};
interface IUpdateConfigDataRequest {
key: keyof IConfig;
value: never;
}

View File

@ -1,15 +1,19 @@
import { RequestHandler } from "express";
import { getWorldState, populateFissures } from "@/src/services/worldStateService";
import { getWorldState, populateDailyDeal, populateFissures } from "@/src/services/worldStateService";
import { version_compare } from "@/src/helpers/inventoryHelpers";
export const worldStateController: RequestHandler = async (req, res) => {
const buildLabel = req.query.buildLabel as string | undefined;
const worldState = getWorldState(buildLabel);
const populatePromises = [populateDailyDeal(worldState)];
// Omitting void fissures for versions prior to Dante Unbound to avoid script errors.
if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) {
await populateFissures(worldState);
populatePromises.push(populateFissures(worldState));
}
await Promise.all(populatePromises);
res.json(worldState);
};

View File

@ -28,12 +28,13 @@ import { updateWorldStateCollections } from "./services/worldStateService";
JSON.stringify = JSONStringify;
validateConfig();
syncConfigWithDatabase();
mongoose
.connect(config.mongodbUrl)
.then(() => {
logger.info("Connected to MongoDB");
syncConfigWithDatabase();
startWebServer();
void updateWorldStateCollections();

View File

@ -91,7 +91,7 @@ import {
ICrewMemberSkillEfficiency,
ICrewMemberDatabase,
ICrewMemberClient,
ISortieRewardAttenuation,
IRewardAttenuation,
IInvasionProgressDatabase,
IInvasionProgressClient,
IAccolades,
@ -99,7 +99,9 @@ import {
ILotusCustomization,
IEndlessXpReward,
IPersonalGoalProgressDatabase,
IPersonalGoalProgressClient
IPersonalGoalProgressClient,
IKubrowPetPrintClient,
IKubrowPetPrintDatabase
} from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes";
import {
@ -1008,6 +1010,27 @@ const traitsSchema = new Schema<ITraits>(
{ _id: false }
);
const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
ItemType: String,
Name: String,
IsMale: Boolean,
Size: Number,
DominantTraits: traitsSchema,
RecessiveTraits: traitsSchema
});
kubrowPetPrintSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const db = obj as IKubrowPetPrintDatabase;
const client = obj as IKubrowPetPrintClient;
client.ItemId = toOid(db._id);
delete obj._id;
delete obj.__v;
}
});
const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
{
Name: String,
@ -1394,10 +1417,10 @@ lastSortieRewardSchema.set("toJSON", {
}
});
const sortieRewardAttenutationSchema = new Schema<ISortieRewardAttenuation>(
const rewardAttenutationSchema = new Schema<IRewardAttenuation>(
{
Tag: String,
Atten: Number
Tag: { type: String, required: true },
Atten: { type: Number, required: true }
},
{ _id: false }
);
@ -1511,7 +1534,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
KubrowPetEggs: [kubrowPetEggSchema],
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
//KubrowPetPrints: [Schema.Types.Mixed],
KubrowPetPrints: [kubrowPetPrintSchema],
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc
Consumables: [typeCountSchema],
@ -1625,6 +1648,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PendingSpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
SpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
//Darvo Deal
UsedDailyDeals: [String],
//New Quest Email
EmailItems: [typeCountSchema],
@ -1640,7 +1666,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CompletedSorties: [String],
LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
SortieRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
// Resource Extractor Drones
Drones: [droneSchema],
@ -1741,7 +1767,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//ChallengeInstanceStates: [Schema.Types.Mixed],
RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
//Robotics: [Schema.Types.Mixed],
//UsedDailyDeals: [Schema.Types.Mixed],
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },
HasResetAccount: { type: Boolean, default: false },
@ -1780,7 +1805,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined },
ClaimedJunctionChallengeRewards: { type: [String], default: undefined }
ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }
},
{ timestamps: { createdAt: "Created", updatedAt: false } }
);
@ -1850,6 +1877,7 @@ export type InventoryDocumentProps = {
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
KubrowPetPrints: Types.DocumentArray<IKubrowPetPrintDatabase>;
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
// eslint-disable-next-line @typescript-eslint/no-empty-object-type

View File

@ -25,7 +25,8 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
LastLogin: { type: Date, default: 0 },
LatestEventMessageDate: { type: Date, default: 0 },
LastLoginRewardDate: { type: Number, default: 0 },
LoginDays: { type: Number, default: 1 }
LoginDays: { type: Number, default: 1 },
DailyFirstWinDate: { type: Number, default: 0 }
},
opts
);

View File

@ -1,4 +1,4 @@
import { IFissureDatabase } from "@/src/types/worldStateTypes";
import { IDailyDealDatabase, IFissureDatabase } from "@/src/types/worldStateTypes";
import { model, Schema } from "mongoose";
const fissureSchema = new Schema<IFissureDatabase>({
@ -12,3 +12,19 @@ const fissureSchema = new Schema<IFissureDatabase>({
fissureSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries.
export const Fissure = model<IFissureDatabase>("Fissure", fissureSchema);
const dailyDealSchema = new Schema<IDailyDealDatabase>({
StoreItem: { type: String, required: true },
Activation: { type: Date, required: true },
Expiry: { type: Date, required: true },
Discount: { type: Number, required: true },
OriginalPrice: { type: Number, required: true },
SalePrice: { type: Number, required: true },
AmountTotal: { type: Number, required: true },
AmountSold: { type: Number, required: true }
});
dailyDealSchema.index({ StoreItem: 1 }, { unique: true });
dailyDealSchema.index({ Expiry: 1 }, { expireAfterSeconds: 86400 });
export const DailyDeal = model<IDailyDealDatabase>("DailyDeal", dailyDealSchema);

View File

@ -25,8 +25,7 @@ import { manageQuestsController } from "@/src/controllers/custom/manageQuestsCon
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { setBoosterController } from "@/src/controllers/custom/setBoosterController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { getConfigController, setConfigController } from "@/src/controllers/custom/configController";
const customRouter = express.Router();
@ -55,7 +54,7 @@ customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController);
customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController);
customRouter.post("/getConfig", getConfigController);
customRouter.post("/setConfig", setConfigController);
export { customRouter };

View File

@ -65,6 +65,7 @@ export interface IConfig {
fastClanAscension?: boolean;
missionsCanGiveAllRelics?: boolean;
unlockAllSimarisResearchEntries?: boolean;
disableDailyTribute?: boolean;
spoofMasteryRank?: number;
nightwaveStandingMultiplier?: number;
unfaithfulBugFixes?: {
@ -81,7 +82,9 @@ export interface IConfig {
vallisOverride?: string;
duviriOverride?: string;
nightwaveOverride?: string;
allTheFissures?: string;
circuitGameModes?: string[];
darvoStockMultiplier?: number;
};
dev?: {
keepVendorsExpired?: boolean;

View File

@ -1,4 +1,4 @@
import fs from "fs";
import chokidar from "chokidar";
import fsPromises from "fs/promises";
import { logger } from "../utils/logger";
import { config, configPath, loadConfig } from "./configService";
@ -6,12 +6,7 @@ import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./w
import { Inbox } from "../models/inboxModel";
let amnesia = false;
fs.watchFile(configPath, (now, then) => {
// https://github.com/oven-sh/bun/issues/20542
if (process.versions.bun && now.mtimeMs == then.mtimeMs) {
return;
}
chokidar.watch(configPath).on("change", () => {
if (amnesia) {
amnesia = false;
} else {

View File

@ -349,7 +349,8 @@ export const removeDojoDeco = (
component.DecoCapacity! += meta.capacityCost;
}
} else {
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
component.DecoCapacity! += meta.dojoCapacityCost!;
if (deco.Sockets !== undefined) {
addVaultFusionTreasures(guild, [
{

View File

@ -296,6 +296,12 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
db[key] = client[key];
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
}
}
if (client.XPInfo !== undefined) {
db.XPInfo = client.XPInfo;
}

View File

@ -29,7 +29,8 @@ import {
ICalendarProgress,
INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint,
IDialogueDatabase
IDialogueDatabase,
IKubrowPetPrintClient
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -43,6 +44,7 @@ import {
} from "../types/inventoryTypes/commonInventoryTypes";
import {
ExportArcanes,
ExportBoosters,
ExportBundles,
ExportChallenges,
ExportCustoms,
@ -424,7 +426,6 @@ export const addItem = async (
ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg",
_id: new Types.ObjectId()
};
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push(egg);
changes.push({
ItemType: egg.ItemType,
@ -499,6 +500,7 @@ export const addItem = async (
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
if (!exactQuantity) {
quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
logger.debug(`non-exact acquisition of ${typeName}; factored quantity is ${quantity}`);
}
const consumablesChanges = [
{
@ -670,6 +672,17 @@ export const addItem = async (
return await addEmailItem(inventory, typeName);
}
// Boosters are an odd case. They're only added like this via Baro's Void Surplus afaik.
{
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == typeName);
if (boosterEntry) {
addBooster(typeName, quantity, inventory);
return {
Boosters: [{ ItemType: typeName, ExpiryDate: quantity }]
};
}
}
// Path-based duck typing
switch (typeName.substr(1).split("/")[1]) {
case "Powersuits":
@ -783,7 +796,11 @@ export const addItem = async (
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
typeName.substr(1).split("/")[3] == "KubrowPet"
) {
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
if (
typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
typeName != "/Lotus/Types/Game/KubrowPet/BlankTraitPrint" &&
typeName != "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
) {
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
}
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
@ -1047,8 +1064,13 @@ export const addKubrowPet = (
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
if (!details) {
let traits: ITraits;
const isCatbrow = [
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
].includes(kubrowPetName);
let traits: ITraits;
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
traits = {
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
@ -1063,12 +1085,7 @@ export const addKubrowPet = (
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
};
} else {
const isCatbrow = [
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
].includes(kubrowPetName);
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
traits = {
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
@ -1087,7 +1104,7 @@ export const addKubrowPet = (
Name: "",
IsPuppy: !premiumPurchase,
HasCollar: true,
PrintsRemaining: 3,
PrintsRemaining: isCatbrow ? 3 : 2,
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
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),
@ -1111,6 +1128,26 @@ export const addKubrowPet = (
return inventoryChanges;
};
export const addKubrowPetPrint = (
inventory: TInventoryDatabaseDocument,
pet: IEquipmentDatabase,
inventoryChanges: IInventoryChanges
): void => {
inventoryChanges.KubrowPetPrints ??= [];
inventoryChanges.KubrowPetPrints.push(
inventory.KubrowPetPrints[
inventory.KubrowPetPrints.push({
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint",
Name: pet.Details!.Name,
IsMale: pet.Details!.IsMale,
Size: pet.Details!.Size,
DominantTraits: pet.Details!.DominantTraits,
RecessiveTraits: pet.Details!.RecessiveTraits
}) - 1
].toJSON<IKubrowPetPrintClient>()
);
};
export const updateSlots = (
inventory: TInventoryDatabaseDocument,
slotName: SlotNames,
@ -1329,7 +1366,7 @@ export const addCustomization = (
customizationName: string,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (!inventory.FlavourItems.find(x => x.ItemType == customizationName)) {
if (!inventory.FlavourItems.some(x => x.ItemType == customizationName)) {
const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizationName }) - 1;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
inventoryChanges.FlavourItems ??= [];
@ -1345,7 +1382,7 @@ export const addSkin = (
typeName: string,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
} else {
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
@ -1831,6 +1868,15 @@ export const addChallenges = (
return affiliationMods;
};
export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value: { challenge: string }[]): void => {
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
};
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
const { Missions } = inventory;
const itemIndex = Missions.findIndex(item => item.Tag === Tag);

View File

@ -144,7 +144,8 @@ export const claimLoginReward = async (
case "RT_STORE_ITEM":
case "RT_RECIPE":
case "RT_RANDOM_RECIPE":
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount)).InventoryChanges;
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount, undefined, true))
.InventoryChanges;
case "RT_CREDITS":
return updateCurrency(inventory, -reward.Amount, false);

View File

@ -14,6 +14,7 @@ import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/servi
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import {
addBooster,
addCalendarProgress,
addChallenges,
addConsumables,
addCrewShipAmmo,
@ -33,10 +34,8 @@ import {
addSkin,
addStanding,
applyClientEquipmentUpdates,
checkCalendarChallengeCompletion,
combineInventoryChanges,
generateRewardSeed,
getCalendarProgress,
getDialogue,
giveNemesisPetRecipe,
giveNemesisWeaponRecipe,
@ -235,7 +234,7 @@ export const addMissionInventoryUpdates = async (
}
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
if (value === undefined) {
logger.error(`Inventory update key ${key} has no value `);
logger.error(`Inventory update key ${key} has no value`);
continue;
}
switch (key) {
@ -526,7 +525,6 @@ export const addMissionInventoryUpdates = async (
}
case "KubrowPetEggs": {
for (const egg of value) {
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push({
ItemType: egg.ItemType,
_id: new Types.ObjectId()
@ -671,12 +669,7 @@ export const addMissionInventoryUpdates = async (
break;
}
case "CalendarProgress": {
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
x => x.events[0].challenge == value[value.length - 1].challenge
);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
addCalendarProgress(inventory, value);
break;
}
case "duviriCaveOffers": {
@ -968,6 +961,7 @@ const droptableAliases: Record<string, string> = {
//TODO: return type of partial missioninventoryupdate response
export const addMissionRewards = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
{
wagerTier: wagerTier,
@ -1015,13 +1009,17 @@ export const addMissionRewards = async (
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
if (fixedLevelRewards.levelKeyRewards) {
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo);
missionCompletionCredits += addFixedLevelRewards(
fixedLevelRewards.levelKeyRewards,
MissionRewards,
rewardInfo
);
}
if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) {
//quest stage completion credit rewards
if (reward.rewardType == "RT_CREDITS") {
missionCompletionCredits += reward.amount; // will be added to inventory in addCredits
missionCompletionCredits += reward.amount;
continue;
}
MissionRewards.push({
@ -1050,12 +1048,11 @@ export const addMissionRewards = async (
) {
const levelCreditReward = getLevelCreditRewards(node);
missionCompletionCredits += levelCreditReward;
inventory.RegularCredits += levelCreditReward;
logger.debug(`levelCreditReward ${levelCreditReward}`);
}
if (node.missionReward) {
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo);
missionCompletionCredits += addFixedLevelRewards(node.missionReward, MissionRewards, rewardInfo);
}
if (rewardInfo.sortieTag == "Mission1") {
@ -1165,7 +1162,9 @@ export const addMissionRewards = async (
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
}
const credits = addCredits(inventory, {
inventory.RegularCredits += missionCompletionCredits;
const credits = await addCredits(account, inventory, {
missionCompletionCredits,
missionDropCredits: creditDrops ?? 0,
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
@ -1388,48 +1387,61 @@ export const addMissionRewards = async (
};
};
//creditBonus is not entirely accurate.
//TODO: consider ActiveBoosters
export const addCredits = (
export const addCredits = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
{
missionDropCredits,
missionCompletionCredits,
rngRewardCredits
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
): IMissionCredits => {
const hasDailyCreditBonus = true;
const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
): Promise<IMissionCredits> => {
const finalCredits: IMissionCredits = {
MissionCredits: [missionDropCredits, missionDropCredits],
CreditBonus: [missionCompletionCredits, missionCompletionCredits],
TotalCredits: [totalCredits, totalCredits]
CreditsBonus: [missionCompletionCredits, missionCompletionCredits],
TotalCredits: [0, 0]
};
if (hasDailyCreditBonus) {
const today = Math.trunc(Date.now() / 86400000) * 86400;
if (account.DailyFirstWinDate != today) {
account.DailyFirstWinDate = today;
await account.save();
logger.debug(`daily first win, doubling missionCompletionCredits (${missionCompletionCredits})`);
finalCredits.DailyMissionBonus = true;
inventory.RegularCredits += missionCompletionCredits;
finalCredits.CreditBonus[1] *= 2;
finalCredits.MissionCredits[1] *= 2;
finalCredits.TotalCredits[1] *= 2;
finalCredits.CreditsBonus[1] *= 2;
}
if (!hasDailyCreditBonus) {
return finalCredits;
const totalCredits = finalCredits.MissionCredits[1] + finalCredits.CreditsBonus[1] + rngRewardCredits;
finalCredits.TotalCredits = [totalCredits, totalCredits];
if (config.worldState?.creditBoost) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
return { ...finalCredits, DailyMissionBonus: true };
const now = Math.trunc(Date.now() / 1000); // TOVERIFY: Should we maybe subtract mission time as to apply credit boosters that expired during mission?
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBooster")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
return finalCredits;
};
export const addFixedLevelRewards = (
rewards: IMissionRewardExternal,
inventory: TInventoryDatabaseDocument,
MissionRewards: IMissionReward[],
rewardInfo?: IRewardInfo
): number => {
let missionBonusCredits = 0;
if (rewards.credits) {
missionBonusCredits += rewards.credits;
inventory.RegularCredits += rewards.credits;
}
if (rewards.items) {
for (const item of rewards.items) {
@ -1603,6 +1615,27 @@ function getRandomMissionDrops(
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
];
} else if (RewardInfo.T == 17) {
if (mission?.Tier == 1) {
logger.warn(`non-steel path duviri murmur tier used on steel path?!`);
}
/*if (operation eight claw is active) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
ItemCount: 10
});
}*/
rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"];
} else if (RewardInfo.T == 19) {
/*if (operation eight claw is active) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
ItemCount: 15
});
}*/
rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
];
} else if (RewardInfo.T == 70) {
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
drops.push({

View File

@ -8,7 +8,7 @@ import {
updateCurrency,
updateSlots
} from "@/src/services/inventoryService";
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
import { getRandomReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import {
@ -16,7 +16,8 @@ import {
IPurchaseResponse,
SlotPurchase,
IInventoryChanges,
PurchaseSource
PurchaseSource,
IPurchaseParams
} from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import { getWorldState } from "./worldStateService";
@ -35,6 +36,8 @@ import {
import { config } from "./configService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { fromStoreItem, toStoreItem } from "./itemDataService";
import { DailyDeal } from "../models/worldStateModel";
import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers";
export const getStoreItemCategory = (storeItem: string): string => {
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@ -51,6 +54,58 @@ export const getStoreItemTypesCategory = (typesItem: string): string => {
return typeElements[1];
};
const tallyVendorPurchase = (
inventory: TInventoryDatabaseDocument,
inventoryChanges: IInventoryChanges,
VendorType: string,
ItemId: string,
numPurchased: number,
Expiry: Date
): void => {
if (!config.noVendorPurchaseLimits) {
inventory.RecentVendorPurchases ??= [];
let vendorPurchases = inventory.RecentVendorPurchases.find(x => x.VendorType == VendorType);
if (!vendorPurchases) {
vendorPurchases =
inventory.RecentVendorPurchases[
inventory.RecentVendorPurchases.push({
VendorType: VendorType,
PurchaseHistory: []
}) - 1
];
}
let historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
if (historyEntry) {
if (Date.now() >= historyEntry.Expiry.getTime()) {
historyEntry.NumPurchased = numPurchased;
historyEntry.Expiry = Expiry;
} else {
historyEntry.NumPurchased += numPurchased;
}
} else {
historyEntry =
vendorPurchases.PurchaseHistory[
vendorPurchases.PurchaseHistory.push({
ItemId: ItemId,
NumPurchased: numPurchased,
Expiry: Expiry
}) - 1
];
}
inventoryChanges.NewVendorPurchase = {
VendorType: VendorType,
PurchaseHistory: [
{
ItemId: ItemId,
NumPurchased: historyEntry.NumPurchased,
Expiry: toMongoDate(Expiry)
}
]
};
inventoryChanges.RecentVendorPurchases = inventoryChanges.NewVendorPurchase;
}
};
export const handlePurchase = async (
purchaseRequest: IPurchaseRequest,
inventory: TInventoryDatabaseDocument
@ -97,20 +152,7 @@ export const handlePurchase = async (
if (offer.LocTagRandSeed !== undefined) {
seed = BigInt(offer.LocTagRandSeed);
}
if (!config.noVendorPurchaseLimits && ItemId) {
inventory.RecentVendorPurchases ??= [];
let vendorPurchases = inventory.RecentVendorPurchases.find(
x => x.VendorType == manifest!.VendorInfo.TypeName
);
if (!vendorPurchases) {
vendorPurchases =
inventory.RecentVendorPurchases[
inventory.RecentVendorPurchases.push({
VendorType: manifest.VendorInfo.TypeName,
PurchaseHistory: []
}) - 1
];
}
if (ItemId) {
let expiry = parseInt(offer.Expiry.$date.$numberLong);
if (purchaseRequest.PurchaseParams.IsWeekly) {
const EPOCH = 1734307200 * 1000; // Monday
@ -118,34 +160,14 @@ export const handlePurchase = async (
const weekStart = EPOCH + week * 604800000;
expiry = weekStart + 604800000;
}
const historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
let numPurchased = purchaseRequest.PurchaseParams.Quantity;
if (historyEntry) {
if (Date.now() >= historyEntry.Expiry.getTime()) {
historyEntry.NumPurchased = numPurchased;
historyEntry.Expiry = new Date(expiry);
} else {
numPurchased += historyEntry.NumPurchased;
historyEntry.NumPurchased += purchaseRequest.PurchaseParams.Quantity;
}
} else {
vendorPurchases.PurchaseHistory.push({
ItemId: ItemId,
NumPurchased: purchaseRequest.PurchaseParams.Quantity,
Expiry: new Date(expiry)
});
}
prePurchaseInventoryChanges.NewVendorPurchase = {
VendorType: manifest.VendorInfo.TypeName,
PurchaseHistory: [
{
ItemId: ItemId,
NumPurchased: numPurchased,
Expiry: { $date: { $numberLong: expiry.toString() } }
}
]
};
prePurchaseInventoryChanges.RecentVendorPurchases = prePurchaseInventoryChanges.NewVendorPurchase;
tallyVendorPurchase(
inventory,
prePurchaseInventoryChanges,
manifest.VendorInfo.TypeName,
ItemId,
purchaseRequest.PurchaseParams.Quantity,
new Date(expiry)
);
}
purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier;
} else {
@ -191,7 +213,7 @@ export const handlePurchase = async (
throw new Error(`vendor purchase should not have an expected price`);
}
if (!config.dontSubtractPurchaseItemCost) {
if (offer.PrimePrice && !config.dontSubtractPurchaseItemCost) {
const invItem: IMiscItem = {
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1
@ -200,6 +222,17 @@ export const handlePurchase = async (
purchaseResponse.InventoryChanges.MiscItems ??= [];
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
}
if (offer.Limit) {
tallyVendorPurchase(
inventory,
purchaseResponse.InventoryChanges,
"VoidTrader",
offer.ItemType,
purchaseRequest.PurchaseParams.Quantity,
fromMongoDate(worldState.VoidTraders[0].Expiry)
);
}
}
break;
}
@ -240,6 +273,12 @@ export const handlePurchase = async (
}
}
break;
case PurchaseSource.DailyDeal:
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`daily deal purchase should not have an expected price`);
}
await handleDailyDealPurchase(inventory, purchaseRequest.PurchaseParams, purchaseResponse);
break;
case PurchaseSource.Vendor:
if (purchaseRequest.PurchaseParams.SourceId! in ExportVendors) {
const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!];
@ -328,6 +367,25 @@ const handleItemPrices = (
}
};
export const handleDailyDealPurchase = async (
inventory: TInventoryDatabaseDocument,
purchaseParams: IPurchaseParams,
purchaseResponse: IPurchaseResponse
): Promise<void> => {
const dailyDeal = (await DailyDeal.findOne({ StoreItem: purchaseParams.StoreItem }))!;
dailyDeal.AmountSold += 1;
await dailyDeal.save();
if (!config.dontSubtractPurchasePlatinumCost) {
updateCurrency(inventory, dailyDeal.SalePrice, true, purchaseResponse.InventoryChanges);
}
if (!config.noVendorPurchaseLimits) {
inventory.UsedDailyDeals.push(purchaseParams.StoreItem);
purchaseResponse.DailyDealUsed = purchaseParams.StoreItem;
}
};
export const handleBundleAcqusition = async (
storeItemName: string,
inventory: TInventoryDatabaseDocument,
@ -371,18 +429,28 @@ export const handleStoreItemAcquisition = async (
} else {
const storeCategory = getStoreItemCategory(storeItemName);
const internalName = fromStoreItem(storeItemName);
logger.debug(`store category ${storeCategory}`);
if (!ignorePurchaseQuantity) {
if (internalName in ExportGear) {
quantity *= ExportGear[internalName].purchaseQuantity || 1;
logger.debug(`factored quantity is ${quantity}`);
} else if (internalName in ExportResources) {
quantity *= ExportResources[internalName].purchaseQuantity || 1;
logger.debug(`factored quantity is ${quantity}`);
}
}
logger.debug(`store category ${storeCategory}`);
switch (storeCategory) {
default: {
purchaseResponse = {
InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
InventoryChanges: await addItem(
inventory,
internalName,
quantity,
premiumPurchase,
seed,
undefined,
true
)
};
break;
}
@ -472,12 +540,57 @@ const handleBoosterPackPurchase = async (
"attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server."
);
}
const specialItemReward = pack.components.find(x => x.PityIncreaseRate);
for (let i = 0; i != quantity; ++i) {
const disallowedItems = new Set();
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
const weights = pack.rarityWeightsPerRoll[roll];
const result = getRandomWeightedRewardUc(pack.components, weights);
if (result) {
if (specialItemReward) {
{
const normalComponents = [];
for (const comp of pack.components) {
if (!comp.PityIncreaseRate) {
const { Probability, ...rest } = comp;
normalComponents.push({
...rest,
probability: Probability!
});
}
}
const result = getRandomReward(normalComponents)!;
logger.debug(`booster pack rolled`, result);
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
combineInventoryChanges(
purchaseResponse.InventoryChanges,
await addItem(inventory, result.Item, result.Amount)
);
}
if (!inventory.WeaponSkins.some(x => x.ItemType == specialItemReward.Item)) {
inventory.SpecialItemRewardAttenuation ??= [];
let atten = inventory.SpecialItemRewardAttenuation.find(x => x.Tag == specialItemReward.Item);
if (!atten) {
atten =
inventory.SpecialItemRewardAttenuation[
inventory.SpecialItemRewardAttenuation.push({
Tag: specialItemReward.Item,
Atten: specialItemReward.Probability!
}) - 1
];
}
if (Math.random() < atten.Atten) {
purchaseResponse.BoosterPackItems += toStoreItem(specialItemReward.Item) + ',{"lvl":0};';
combineInventoryChanges(
purchaseResponse.InventoryChanges,
await addItem(inventory, specialItemReward.Item)
);
// TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now?
} else {
atten.Atten += specialItemReward.PityIncreaseRate!;
}
}
} else {
const disallowedItems = new Set();
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
const weights = pack.rarityWeightsPerRoll[roll];
const result = getRandomWeightedRewardUc(pack.components, weights)!;
logger.debug(`booster pack rolled`, result);
if (disallowedItems.has(result.Item)) {
logger.debug(`oops, can't use that one; trying again`);
@ -487,9 +600,12 @@ const handleBoosterPackPurchase = async (
disallowedItems.add(result.Item);
}
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
combineInventoryChanges(purchaseResponse.InventoryChanges, await addItem(inventory, result.Item, 1));
combineInventoryChanges(
purchaseResponse.InventoryChanges,
await addItem(inventory, result.Item, result.Amount)
);
++roll;
}
++roll;
}
}
return purchaseResponse;
@ -524,7 +640,9 @@ const handleTypesPurchase = async (
logger.debug(`type category ${typeCategory}`);
switch (typeCategory) {
default:
return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
return {
InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed, undefined, true)
};
case "BoosterPacks":
return handleBoosterPackPurchase(typesName, inventory, quantity);
case "SlotItems":

View File

@ -331,7 +331,7 @@ export const giveKeyChainMissionReward = async (
const fixedLevelRewards = getLevelKeyRewards(missionName);
if (fixedLevelRewards.levelKeyRewards) {
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards);
for (const reward of missionRewards) {
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);

View File

@ -149,7 +149,8 @@ export const handleInventoryItemConfigChange = async (
} else {
const inventoryItem = inventory.WeaponSkins.id(itemId);
if (!inventoryItem) {
throw new Error(`inventory item WeaponSkins not found with id ${itemId}`);
logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
continue;
}
if ("Favorite" in itemConfigEntries) {
inventoryItem.Favorite = itemConfigEntries.Favorite;
@ -177,7 +178,8 @@ export const handleInventoryItemConfigChange = async (
const inventoryItem = inventory[equipmentName].id(itemId);
if (!inventoryItem) {
throw new Error(`inventory item ${equipmentName} not found with id ${itemId}`);
logger.warn(`inventory item ${equipmentName} not found with id ${itemId}`);
continue;
}
for (const [configId, config] of Object.entries(itemConfigEntries)) {

View File

@ -281,6 +281,10 @@ const generateVendorManifest = (
offersToAdd.push(item);
++offset;
}
if (missingItemsPerBin[item.bin]) {
missingItemsPerBin[item.bin] -= 1;
numOffersThatNeedToMatchABin -= 1;
}
} else {
numCountedOffers += 1 + item.duplicates;
}

View File

@ -59,7 +59,12 @@ export const handleSetShipDecorations = async (
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
if (!roomToPlaceIn) {
throw new Error("room not found");
throw new Error(`unknown room: ${placedDecoration.Room}`);
}
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!;
if (!itemType || meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
}
if (placedDecoration.MoveId) {
@ -83,7 +88,7 @@ export const handleSetShipDecorations = async (
OldRoom: placedDecoration.OldRoom,
NewRoom: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal
MaxCapacityIncrease: 0
};
}
@ -96,6 +101,7 @@ export const handleSetShipDecorations = async (
}
oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId });
oldRoom.MaxCapacity += meta.capacityCost;
const newDecoration = {
Type: placedDecoration.Type,
@ -108,12 +114,14 @@ export const handleSetShipDecorations = async (
//the new room is still roomToPlaceIn
roomToPlaceIn.PlacedDecos.push(newDecoration);
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
await personalRooms.save();
return {
OldRoom: placedDecoration.OldRoom,
NewRoom: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal
MaxCapacityIncrease: -meta.capacityCost
};
}
@ -121,11 +129,11 @@ export const handleSetShipDecorations = async (
const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId));
const deco = roomToPlaceIn.PlacedDecos[decoIndex];
roomToPlaceIn.PlacedDecos.splice(decoIndex, 1);
roomToPlaceIn.MaxCapacity += meta.capacityCost;
await personalRooms.save();
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
if (deco.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
} else {
@ -138,24 +146,20 @@ export const handleSetShipDecorations = async (
DecoId: placedDecoration.RemoveId,
Room: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0
MaxCapacityIncrease: 0 // Client already implies the capacity being refunded.
};
} else {
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [
{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }
]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
}
await inventory.save();
}
}
// TODO: handle capacity
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
}
await inventory.save();
}
//place decoration
const decoId = new Types.ObjectId();
@ -167,10 +171,16 @@ export const handleSetShipDecorations = async (
Sockets: placedDecoration.Sockets,
_id: decoId
});
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
await personalRooms.save();
return { DecoId: decoId.toString(), Room: placedDecoration.Room, IsApartment: placedDecoration.IsApartment };
return {
DecoId: decoId.toString(),
Room: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: -meta.capacityCost
};
};
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {

View File

@ -136,7 +136,10 @@ export const stopWebServer = async (): Promise<void> => {
await Promise.all(promises);
};
let lastWsid: number = 0;
interface IWsCustomData extends ws {
id?: number;
accountId?: string;
}
@ -150,6 +153,7 @@ interface IWsMsgFromClient {
}
interface IWsMsgToClient {
//wsid?: number;
reload?: boolean;
ports?: {
http: number | undefined;
@ -174,6 +178,10 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
ws.close();
return;
}
(ws as IWsCustomData).id = ++lastWsid;
ws.send(JSON.stringify({ wsid: lastWsid }));
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
@ -268,3 +276,21 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
}
}
};
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
};

View File

@ -4,6 +4,7 @@ import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
import { buildConfig } from "@/src/services/buildConfigService";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService";
@ -27,7 +28,7 @@ import {
} from "../types/worldStateTypes";
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers";
import { logger } from "../utils/logger";
import { Fissure } from "../models/worldStateModel";
import { DailyDeal, Fissure } from "../models/worldStateModel";
const sortieBosses = [
"SORTIE_BOSS_HYENA",
@ -372,7 +373,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
),
hasWeeklyPermanent: !!syndicate.weeklyChallenges!.find(x =>
hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
)
};
@ -1122,6 +1123,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
GlobalUpgrades: [],
VoidTraders: [],
VoidStorms: [],
DailyDeals: [],
EndlessXpChoices: [],
KnownCalendarSeasons: [],
...staticWorldState,
@ -1524,20 +1526,58 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
};
export const populateFissures = async (worldState: IWorldState): Promise<void> => {
const fissures = await Fissure.find({});
for (const fissure of fissures) {
const meta = ExportRegions[fissure.Node];
worldState.ActiveMissions.push({
_id: toOid(fissure._id),
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: toMongoDate(fissure.Activation),
Expiry: toMongoDate(fissure.Expiry),
Node: fissure.Node,
MissionType: eMissionType[meta.missionIndex].tag,
Modifier: fissure.Modifier,
Hard: fissure.Hard
});
if (config.worldState?.allTheFissures) {
let i = 0;
for (const [tier, nodes] of Object.entries(fissureMissions)) {
for (const node of nodes) {
const meta = ExportRegions[node];
worldState.ActiveMissions.push({
_id: { $oid: (i++).toString().padStart(8, "0") + "8e0c70ba050f1eb7" },
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: { $date: { $numberLong: "1000000000000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Node: node,
MissionType: eMissionType[meta.missionIndex].tag,
Modifier: tier,
Hard: config.worldState.allTheFissures == "hard"
});
}
}
} else {
const fissures = await Fissure.find({});
for (const fissure of fissures) {
const meta = ExportRegions[fissure.Node];
worldState.ActiveMissions.push({
_id: toOid(fissure._id),
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: toMongoDate(fissure.Activation),
Expiry: toMongoDate(fissure.Expiry),
Node: fissure.Node,
MissionType: eMissionType[meta.missionIndex].tag,
Modifier: fissure.Modifier,
Hard: fissure.Hard
});
}
}
};
export const populateDailyDeal = async (worldState: IWorldState): Promise<void> => {
const dailyDeals = await DailyDeal.find({});
for (const dailyDeal of dailyDeals) {
if (dailyDeal.Expiry.getTime() > Date.now()) {
worldState.DailyDeals.push({
StoreItem: dailyDeal.StoreItem,
Activation: toMongoDate(dailyDeal.Activation),
Expiry: toMongoDate(dailyDeal.Expiry),
Discount: dailyDeal.Discount,
OriginalPrice: dailyDeal.OriginalPrice,
SalePrice: dailyDeal.SalePrice,
AmountTotal: Math.round(dailyDeal.AmountTotal * (config.worldState?.darvoStockMultiplier ?? 1)),
AmountSold: dailyDeal.AmountSold
});
}
}
};
@ -1669,7 +1709,7 @@ const nightwaveTagToSeason: Record<string, number> = {
RadioLegionSyndicate: 0 // The Wolf of Saturn Six
};
export const updateWorldStateCollections = async (): Promise<void> => {
const updateFissures = async (): Promise<void> => {
const fissures = await Fissure.find();
const activeNodes = new Set<string>();
@ -1722,3 +1762,38 @@ export const updateWorldStateCollections = async (): Promise<void> => {
}
}
};
const updateDailyDeal = async (): Promise<void> => {
let darvoIndex = Math.trunc((Date.now() - 25200000) / (26 * unixTimesInMs.hour));
let darvoEnd;
do {
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
darvoEnd = darvoStart + 26 * unixTimesInMs.hour;
const darvoOid = ((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
if (!(await DailyDeal.findById(darvoOid))) {
const seed = new SRng(darvoIndex).randomInt(0, 100_000);
const rng = new SRng(seed);
let deal;
do {
deal = rng.randomReward(darvoDeals)!; // Using an actual sampling collected over roughly a year because I can't extrapolate an algorithm from it with enough certainty.
//const [storeItem, meta] = rng.randomElement(Object.entries(darvoDeals))!;
//const discount = Math.min(rng.randomInt(1, 9) * 10, (meta as { MaxDiscount?: number }).MaxDiscount ?? 1);
} while (await DailyDeal.exists({ StoreItem: deal.StoreItem }));
await DailyDeal.insertOne({
_id: darvoOid,
StoreItem: deal.StoreItem,
Activation: new Date(darvoStart),
Expiry: new Date(darvoEnd),
Discount: deal.Discount,
OriginalPrice: deal.OriginalPrice,
SalePrice: deal.SalePrice, //Math.trunc(deal.OriginalPrice * (1 - discount))
AmountTotal: deal.AmountTotal,
AmountSold: 0
});
}
} while (darvoEnd < Date.now() + 6 * unixTimesInMs.minute && ++darvoIndex);
};
export const updateWorldStateCollections = async (): Promise<void> => {
await Promise.all([updateFissures(), updateDailyDeal()]);
};

View File

@ -40,6 +40,7 @@ export interface IInventoryDatabase
| "InfestedFoundry"
| "DialogueHistory"
| "KubrowPetEggs"
| "KubrowPetPrints"
| "PendingCoupon"
| "Drones"
| "RecentVendorPurchases"
@ -79,7 +80,8 @@ export interface IInventoryDatabase
KahlLoadOuts: IOperatorConfigDatabase[];
InfestedFoundry?: IInfestedFoundryDatabase;
DialogueHistory?: IDialogueHistoryDatabase;
KubrowPetEggs?: IKubrowPetEggDatabase[];
KubrowPetEggs: IKubrowPetEggDatabase[];
KubrowPetPrints: IKubrowPetPrintDatabase[];
PendingCoupon?: IPendingCouponDatabase;
Drones: IDroneDatabase[];
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
@ -287,6 +289,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ArchwingEnabled?: boolean;
PendingSpectreLoadouts?: ISpectreLoadout[];
SpectreLoadouts?: ISpectreLoadout[];
UsedDailyDeals: string[];
EmailItems: ITypeCount[];
CompletedSyndicates: string[];
FocusXP?: IFocusXP;
@ -295,7 +298,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CompletedSorties: string[];
LastSortieReward?: ILastSortieRewardClient[];
LastLiteSortieReward?: ILastSortieRewardClient[];
SortieRewardAttenuation?: ISortieRewardAttenuation[];
SortieRewardAttenuation?: IRewardAttenuation[];
Drones: IDroneClient[];
StepSequencers: IStepSequencer[];
ActiveAvatarImageType?: string;
@ -306,7 +309,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
FocusUpgrades: IFocusUpgrade[];
HasContributedToDojo?: boolean;
HWIDProtectEnabled?: boolean;
//KubrowPetPrints: IKubrowPetPrint[];
KubrowPetPrints: IKubrowPetPrintClient[];
AlignmentReplay?: IAlignment;
PersonalGoalProgress?: IPersonalGoalProgressClient[];
ThemeStyle: string;
@ -351,7 +354,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
//LeagueTickets: any[];
//Quests: any[];
//Robotics: any[];
//UsedDailyDeals: any[];
LibraryPersonalTarget?: string;
LibraryPersonalProgress: ILibraryPersonalProgress[];
CollectibleSeries?: ICollectibleEntry[];
@ -381,6 +383,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
HubNpcCustomizations?: IHubNpcCustomization[];
Ship?: IOrbiter; // U22 and below, response only
ClaimedJunctionChallengeRewards?: string[]; // U39
SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
}
export interface IAffiliation {
@ -722,8 +725,8 @@ export interface IKubrowPetEggDatabase {
_id: Types.ObjectId;
}
export interface IKubrowPetPrint {
ItemType: KubrowPetPrintItemType;
export interface IKubrowPetPrintClient {
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint";
Name: string;
IsMale: boolean;
Size: number; // seems to be 0.7 to 1.0
@ -733,6 +736,10 @@ export interface IKubrowPetPrint {
InheritedModularParts?: any[];
}
export interface IKubrowPetPrintDatabase extends Omit<IKubrowPetPrintClient, "ItemId" | "InheritedModularParts"> {
_id: Types.ObjectId;
}
export interface ITraits {
BaseColor: string;
SecondaryColor: string;
@ -746,15 +753,11 @@ export interface ITraits {
Tail?: string;
}
export enum KubrowPetPrintItemType {
LotusTypesGameKubrowPetImprintedTraitPrint = "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
}
export interface IKubrowPetDetailsDatabase {
Name?: string;
IsPuppy?: boolean;
HasCollar: boolean;
PrintsRemaining?: number;
PrintsRemaining: number;
Status: Status;
HatchDate?: Date;
DominantTraits: ITraits;
@ -783,7 +786,7 @@ export interface ILastSortieRewardDatabase extends Omit<ILastSortieRewardClient,
SortieId: Types.ObjectId;
}
export interface ISortieRewardAttenuation {
export interface IRewardAttenuation {
Tag: string;
Atten: number;
}

View File

@ -25,6 +25,7 @@ export interface IDatabaseAccount extends IDatabaseAccountRequiredFields {
LatestEventMessageDate: Date;
LastLoginRewardDate: number;
LoginDays: number;
DailyFirstWinDate: number;
}
// Includes virtual ID

View File

@ -17,9 +17,9 @@ export interface IMissionReward {
}
export interface IMissionCredits {
MissionCredits: number[];
CreditBonus: number[];
TotalCredits: number[];
MissionCredits: [number, number];
CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus
TotalCredits: [number, number];
DailyMissionBonus?: boolean;
}

View File

@ -7,7 +7,8 @@ import {
ITypeCount,
IRecentVendorPurchaseClient,
TEquipmentKey,
ICrewMemberClient
ICrewMemberClient,
IKubrowPetPrintClient
} from "./inventoryTypes/inventoryTypes";
export enum PurchaseSource {
@ -78,6 +79,7 @@ export type IInventoryChanges = {
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[];
} & Record<
Exclude<
string,
@ -105,6 +107,7 @@ export interface IPurchaseResponse {
Standing?: IAffiliationMods[];
FreeFavorsUsed?: IAffiliationMods[];
BoosterPackItems?: string;
DailyDealUsed?: string;
}
export type IBinChanges = {

View File

@ -15,6 +15,7 @@ export interface IWorldState {
NodeOverrides: INodeOverride[];
VoidTraders: IVoidTrader[];
VoidStorms: IVoidStorm[];
DailyDeals: IDailyDeal[];
PVPChallengeInstances: IPVPChallengeInstance[];
EndlessXpChoices: IEndlessXpChoice[];
SeasonInfo?: {
@ -159,6 +160,7 @@ export interface IVoidTraderOffer {
ItemType: string;
PrimePrice: number;
RegularPrice: number;
Limit?: number;
}
export interface IVoidStorm {
@ -169,6 +171,28 @@ export interface IVoidStorm {
ActiveMissionTier: string;
}
export interface IDailyDeal {
StoreItem: string;
Activation: IMongoDate;
Expiry: IMongoDate;
Discount: number;
OriginalPrice: number;
SalePrice: number;
AmountTotal: number;
AmountSold: number;
}
export interface IDailyDealDatabase {
StoreItem: string;
Activation: Date;
Expiry: Date;
Discount: number;
OriginalPrice: number;
SalePrice: number;
AmountTotal: number;
AmountSold: number;
}
export interface IPVPChallengeInstance {
_id: IOid;
challengeTypeRefID: string;

View File

@ -20,6 +20,10 @@
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe",

View File

@ -45,5 +45,6 @@
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
]

View File

@ -1,7 +1,8 @@
{
"evergreen": [
{ "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 }
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Types/BoosterPacks/BaroTreasureBox", "PrimePrice": 0, "RegularPrice": 50000, "Limit": 1 }
],
"armorSets": [
[

View File

@ -0,0 +1,158 @@
[
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Archwing/DemolitionJetPack/DemolitionJetPack", "Discount": 60, "OriginalPrice": 275, "SalePrice": 110, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Bard/Bard", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Ember/Ember", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Ember/Ember", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 20, "OriginalPrice": 200, "SalePrice": 160, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 30, "OriginalPrice": 200, "SalePrice": 140, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 40, "OriginalPrice": 200, "SalePrice": 120, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 50, "OriginalPrice": 200, "SalePrice": 100, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Sandman/Sandman", "Discount": 20, "OriginalPrice": 225, "SalePrice": 180, "AmountTotal": 100, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Sandman/Sandman", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Trapper/Trapper", "Discount": 40, "OriginalPrice": 300, "SalePrice": 180, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Trapper/Trapper", "Discount": 50, "OriginalPrice": 300, "SalePrice": 150, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/CatbrowPet/CatbrowGeneticSignature", "Discount": 20, "OriginalPrice": 5, "SalePrice": 4, "AmountTotal": 500, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/CatbrowPet/CatbrowGeneticSignature", "Discount": 30, "OriginalPrice": 5, "SalePrice": 3, "AmountTotal": 415, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/KubrowPet/Eggs/KubrowEgg", "Discount": 50, "OriginalPrice": 10, "SalePrice": 5, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/KubrowPet/Eggs/KubrowEgg", "Discount": 60, "OriginalPrice": 10, "SalePrice": 4, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", "Discount": 30, "OriginalPrice": 20, "SalePrice": 14, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", "Discount": 45, "OriginalPrice": 20, "SalePrice": 11, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", "Discount": 40, "OriginalPrice": 20, "SalePrice": 12, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 40, "OriginalPrice": 10, "SalePrice": 6, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 40, "OriginalPrice": 10, "SalePrice": 6, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 20, "OriginalPrice": 40, "SalePrice": 32, "AmountTotal": 125, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/PowerLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 20, "OriginalPrice": 40, "SalePrice": 32, "AmountTotal": 125, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 30, "OriginalPrice": 40, "SalePrice": 28, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/WardLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 20, "OriginalPrice": 235, "SalePrice": 188, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 30, "OriginalPrice": 235, "SalePrice": 164, "AmountTotal": 250, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 50, "OriginalPrice": 235, "SalePrice": 117, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 60, "OriginalPrice": 235, "SalePrice": 94, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 30, "OriginalPrice": 125, "SalePrice": 87, "AmountTotal": 90, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 60, "OriginalPrice": 125, "SalePrice": 50, "AmountTotal": 65, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 90, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 50, "OriginalPrice": 175, "SalePrice": 87, "AmountTotal": 80, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 60, "OriginalPrice": 175, "SalePrice": 70, "AmountTotal": 70, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 70, "OriginalPrice": 175, "SalePrice": 52, "AmountTotal": 60, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 10, "OriginalPrice": 75, "SalePrice": 67, "AmountTotal": 100, "probability": 6 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 20, "OriginalPrice": 75, "SalePrice": 60, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 30, "OriginalPrice": 75, "SalePrice": 52, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 500, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 500, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 500, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 20, "OriginalPrice": 150, "SalePrice": 120, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 250, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 150, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 175, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 125, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 70, "OriginalPrice": 225, "SalePrice": 67, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 10, "OriginalPrice": 150, "SalePrice": 135, "AmountTotal": 300, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 20, "OriginalPrice": 150, "SalePrice": 120, "AmountTotal": 270, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 240, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 40, "OriginalPrice": 150, "SalePrice": 90, "AmountTotal": 205, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 60, "OriginalPrice": 150, "SalePrice": 60, "AmountTotal": 145, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 80, "OriginalPrice": 150, "SalePrice": 30, "AmountTotal": 80, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 20, "OriginalPrice": 225, "SalePrice": 180, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 165, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 150, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 30, "OriginalPrice": 75, "SalePrice": 52, "AmountTotal": 350, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 40, "OriginalPrice": 75, "SalePrice": 45, "AmountTotal": 300, "probability": 7 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 50, "OriginalPrice": 75, "SalePrice": 37, "AmountTotal": 250, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 60, "OriginalPrice": 75, "SalePrice": 30, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/DualShortSword/DualHeatSwords", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/DualShortSword/DualHeatSwords", "Discount": 70, "OriginalPrice": 175, "SalePrice": 52, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Fist/Fist", "Discount": 10, "OriginalPrice": 125, "SalePrice": 112, "AmountTotal": 500, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Fist/Fist", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 250, "probability": 6 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 30, "OriginalPrice": 125, "SalePrice": 87, "AmountTotal": 125, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 40, "OriginalPrice": 125, "SalePrice": 75, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 300, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 40, "OriginalPrice": 150, "SalePrice": 90, "AmountTotal": 250, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 200, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 20, "OriginalPrice": 165, "SalePrice": 132, "AmountTotal": 300, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 30, "OriginalPrice": 165, "SalePrice": 115, "AmountTotal": 250, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 40, "OriginalPrice": 165, "SalePrice": 99, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 50, "OriginalPrice": 165, "SalePrice": 82, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 60, "OriginalPrice": 165, "SalePrice": 66, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 300, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 60, "OriginalPrice": 150, "SalePrice": 60, "AmountTotal": 265, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 70, "OriginalPrice": 150, "SalePrice": 45, "AmountTotal": 225, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 90, "OriginalPrice": 150, "SalePrice": 15, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Scythe/EtherScytheWeapon", "Discount": 40, "OriginalPrice": 230, "SalePrice": 138, "AmountTotal": 250, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Scythe/EtherScytheWeapon", "Discount": 60, "OriginalPrice": 230, "SalePrice": 92, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 20, "OriginalPrice": 175, "SalePrice": 140, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 90, "OriginalPrice": 175, "SalePrice": 17, "AmountTotal": 100, "probability": 1 },
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield",
"Discount": 30,
"OriginalPrice": 150,
"SalePrice": 105,
"AmountTotal": 100,
"probability": 1
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield",
"Discount": 70,
"OriginalPrice": 150,
"SalePrice": 45,
"AmountTotal": 100,
"probability": 1
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield",
"Discount": 90,
"OriginalPrice": 150,
"SalePrice": 15,
"AmountTotal": 100,
"probability": 1
},
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 250, "probability": 6 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 50, "OriginalPrice": 175, "SalePrice": 87, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 60, "OriginalPrice": 175, "SalePrice": 70, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 300, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 150, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 50, "OriginalPrice": 190, "SalePrice": 95, "AmountTotal": 50, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 300, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 250, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 50, "OriginalPrice": 190, "SalePrice": 95, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 60, "OriginalPrice": 190, "SalePrice": 76, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 10, "OriginalPrice": 250, "SalePrice": 225, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 30, "OriginalPrice": 250, "SalePrice": 175, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 50, "OriginalPrice": 250, "SalePrice": 125, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Shotgun/QuadShotgun", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Shotgun/QuadShotgun", "Discount": 70, "OriginalPrice": 225, "SalePrice": 67, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 10, "OriginalPrice": 175, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 20, "OriginalPrice": 175, "SalePrice": 140, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 100, "probability": 2 }
]

View File

@ -510,18 +510,6 @@
"PrimeAccessAvailability": { "State": "PRIME1" },
"PrimeVaultAvailabilities": [false, false, false, false, false],
"PrimeTokenAvailability": true,
"DailyDeals": [
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Focus/PowerLensGreater",
"Activation": { "$date": { "$numberLong": "1715058000000" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Discount": 50,
"OriginalPrice": 40,
"SalePrice": 20,
"AmountTotal": 50,
"AmountSold": 0
}
],
"LibraryInfo": { "LastCompletedTargetType": "/Lotus/Types/Game/Library/Targets/Research7Target" },
"PVPChallengeInstances": [
{

View File

@ -98,9 +98,9 @@
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_RegularCredits"></h5>
<div class="card-body">
<p class="card-text" id="RegularCredits-owned"></p>
@ -112,7 +112,7 @@
</div>
</div>
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_PremiumCredits"></h5>
<div class="card-body">
<p class="card-text" id="PremiumCredits-owned"></p>
@ -124,7 +124,7 @@
</div>
</div>
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_FusionPoints"></h5>
<div class="card-body">
<p class="card-text" id="FusionPoints-owned"></p>
@ -136,7 +136,7 @@
</div>
</div>
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_PrimeTokens"></h5>
<div class="card-body">
<p class="card-text" id="PrimeTokens-owned"></p>
@ -148,9 +148,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_suits"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
@ -164,7 +164,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_longGuns"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
@ -183,9 +183,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_pistols"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
@ -204,7 +204,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_melee"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
@ -223,9 +223,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
@ -239,7 +239,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
@ -253,9 +253,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
@ -269,7 +269,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_mechSuits"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
@ -283,9 +283,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinels"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
@ -299,7 +299,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_moaPets"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
@ -325,9 +325,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
@ -349,7 +349,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
@ -363,9 +363,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
@ -384,7 +384,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('HoverBoards');return false;">
@ -401,9 +401,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
@ -417,7 +417,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" 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;">
@ -431,7 +431,7 @@
</div>
</div>
</div>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
@ -458,7 +458,7 @@
<div id="powersuit-route" data-route="~ /webui/powersuit/(.+)" data-title="Inventory | OpenWF WebUI">
<h3 class="mb-0"></h3>
<p class="text-body-secondary"></p>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
<div class="card-body">
<p>
@ -498,7 +498,7 @@
<a href="riven-tool/" target="_blank" data-loc="mods_fingerprintHelp"></a>
</form>
</div>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="mods_rivens"></h5>
<div class="card-body">
<table class="table table-hover w-100">
@ -521,7 +521,7 @@
</table>
</div>
</div>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_addMissingUnrankedMods"></button>
@ -535,7 +535,7 @@
<div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
<div class="row g-3">
<div class="col-md-6">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
@ -549,7 +549,7 @@
</div>
</div>
<div class="col-md-6">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
@ -565,13 +565,13 @@
<div data-route="/webui/cheats, /webui/settings" data-title="Cheats | OpenWF WebUI">
<div class="row g-3">
<div class="col-md-6">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="cheats_server"></h5>
<div class="card-body">
<div id="server-settings-no-perms" class="d-none">
<div class="d-none config-admin-hide">
<p class="card-text" data-loc="cheats_administratorRequirement"></p>
</div>
<div id="server-settings" class="d-none">
<div class="d-none config-admin-show config-form">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipTutorial" />
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
@ -768,14 +768,18 @@
<input class="form-check-input" type="checkbox" id="unlockAllSimarisResearchEntries" />
<label class="form-check-label" for="unlockAllSimarisResearchEntries" data-loc="cheats_unlockAllSimarisResearchEntries"></label>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfig('spoofMasteryRank'); return false;">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="disableDailyTribute" />
<label class="form-check-label" for="disableDailyTribute" data-loc="cheats_disableDailyTribute"></label>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfig('nightwaveStandingMultiplier'); return false;">
<form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" value="1" />
@ -807,6 +811,107 @@
</form>
</div>
</div>
<div class="card d-none config-admin-show config-form">
<h5 class="card-header" data-loc="worldState"></h5>
<div class="card-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.creditBoost" />
<label class="form-check-label" for="worldState.creditBoost" data-loc="worldState_creditBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.affinityBoost" />
<label class="form-check-label" for="worldState.affinityBoost" data-loc="worldState_affinityBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.resourceBoost" />
<label class="form-check-label" for="worldState.resourceBoost" data-loc="worldState_resourceBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.starDays" />
<label class="form-check-label" for="worldState.starDays" data-loc="worldState_starDays"></label>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_galleonOfGhouls"></label>
<select class="form-control" id="worldState.galleonOfGhouls">
<option value="0" data-loc="disabled"></option>
<option value="1" data-loc="worldState_we1"></option>
<option value="2" data-loc="worldState_we2"></option>
<option value="3" data-loc="worldState_we3"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride">
<option value="" data-loc="disabled"></option>
<option value="day" data-loc="worldState_day"></option>
<option value="night" data-loc="worldState_night"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_vallisOverride"></label>
<select class="form-control" id="worldState.vallisOverride">
<option value="" data-loc="disabled"></option>
<option value="warm" data-loc="worldState_warm"></option>
<option value="cold" data-loc="worldState_cold"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_duviriOverride"></label>
<select class="form-control" id="worldState.duviriOverride">
<option value="" data-loc="disabled"></option>
<option value="joy" data-loc="worldState_joy"></option>
<option value="anger" data-loc="worldState_anger"></option>
<option value="envy" data-loc="worldState_envy"></option>
<option value="sorrow" data-loc="worldState_sorrow"></option>
<option value="fear" data-loc="worldState_fear"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_nightwaveOverride"></label>
<select class="form-control" id="worldState.nightwaveOverride">
<option value="" data-loc="disabled"></option>
<option value="RadioLegionIntermission13Syndicate" data-loc="worldState_RadioLegionIntermission13Syndicate"></option>
<option value="RadioLegionIntermission12Syndicate" data-loc="worldState_RadioLegionIntermission12Syndicate"></option>
<option value="RadioLegionIntermission11Syndicate" data-loc="worldState_RadioLegionIntermission11Syndicate"></option>
<option value="RadioLegionIntermission10Syndicate" data-loc="worldState_RadioLegionIntermission10Syndicate"></option>
<option value="RadioLegionIntermission9Syndicate" data-loc="worldState_RadioLegionIntermission9Syndicate"></option>
<option value="RadioLegionIntermission8Syndicate" data-loc="worldState_RadioLegionIntermission8Syndicate"></option>
<option value="RadioLegionIntermission7Syndicate" data-loc="worldState_RadioLegionIntermission7Syndicate"></option>
<option value="RadioLegionIntermission6Syndicate" data-loc="worldState_RadioLegionIntermission6Syndicate"></option>
<option value="RadioLegionIntermission5Syndicate" data-loc="worldState_RadioLegionIntermission5Syndicate"></option>
<option value="RadioLegionIntermission4Syndicate" data-loc="worldState_RadioLegionIntermission4Syndicate"></option>
<option value="RadioLegionIntermission3Syndicate" data-loc="worldState_RadioLegionIntermission3Syndicate"></option>
<option value="RadioLegion3Syndicate" data-loc="worldState_RadioLegion3Syndicate"></option>
<option value="RadioLegionIntermission2Syndicate" data-loc="worldState_RadioLegionIntermission2Syndicate"></option>
<option value="RadioLegion2Syndicate" data-loc="worldState_RadioLegion2Syndicate"></option>
<option value="RadioLegionIntermissionSyndicate" data-loc="worldState_RadioLegionIntermissionSyndicate"></option>
<option value="RadioLegionSyndicate" data-loc="worldState_RadioLegionSyndicate"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_fissures"></label>
<select class="form-control" id="worldState.allTheFissures">
<option value="" data-loc="normal"></option>
<option value="normal" data-loc="worldState_allAtOnceNormal"></option>
<option value="hard" data-loc="worldState_allAtOnceSteelPath"></option>
</select>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfigStringArray('worldState.circuitGameModes'); return false;">
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label>
<div class="input-group">
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigFloat('worldState.darvoStockMultiplier'); return false;">
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_darvoStockMultiplier"></label>
<div class="input-group">
<input id="worldState.darvoStockMultiplier" class="form-control" type="number" step="0.01" placeholder="1" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@ -836,10 +941,7 @@
<datalist id="datalist-KubrowPets"></datalist>
<datalist id="datalist-QuestKeys"></datalist>
<datalist id="datalist-miscitems"></datalist>
<datalist id="datalist-mods">
<option data-key="/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser" value="Legendary Core"></option>
<option data-key="/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod" value="Traumatic Peculiar"></option>
</datalist>
<datalist id="datalist-mods"></datalist>
<datalist id="datalist-archonCrystalUpgrades"></datalist>
<datalist id="datalist-OperatorAmps"></datalist>
<datalist id="datalist-EvolutionProgress"></datalist>
@ -871,6 +973,15 @@
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<datalist id="datalist-Boosters"></datalist>
<datalist id="datalist-circuitGameModes">
<option>Survival</option>
<option>VoidFlood</option>
<option>Excavation</option>
<option>Defense</option>
<option>Exterminate</option>
<option>Assassination</option>
<option>Alchemy</option>
</datalist>
<script src="/webui/libs/jquery-3.6.0.min.js"></script>
<script src="/webui/libs/whirlpool-js.min.js"></script>
<script src="/webui/libs/single.js"></script>

View File

@ -10,7 +10,8 @@
let auth_pending = false,
did_initial_auth = false,
ws_is_open = false;
ws_is_open = false,
wsid = 0;
const sendAuth = isRegister => {
if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) {
auth_pending = true;
@ -34,6 +35,9 @@ function openWebSocket() {
};
window.ws.onmessage = e => {
const msg = JSON.parse(e.data);
if ("wsid" in msg) {
wsid = msg.wsid;
}
if ("reload" in msg) {
setTimeout(() => {
getWebSocket().then(() => {
@ -248,7 +252,8 @@ const permanentEvolutionWeapons = new Set([
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
]);
let uniqueLevelCaps = {};
@ -1846,16 +1851,28 @@ function doAcquireMod() {
}
}
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id);
for (const id of uiConfigs) {
const elm = document.getElementById(id);
if (elm.type == "checkbox") {
if (elm.tagName == "SELECT") {
elm.onchange = function () {
let value = this.value;
if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
$.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json",
data: JSON.stringify({ [id]: value })
});
};
} else if (elm.type == "checkbox") {
elm.onchange = function () {
$.post({
url: "/custom/config?" + window.authz,
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json",
data: JSON.stringify({ key: id, value: this.checked })
data: JSON.stringify({ [id]: this.checked })
}).then(() => {
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) {
updateInventory();
@ -1865,12 +1882,37 @@ for (const id of uiConfigs) {
}
}
function doSaveConfig(id) {
const elm = document.getElementById(id);
function doSaveConfigInt(id) {
$.post({
url: "/custom/config?" + window.authz,
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json",
data: JSON.stringify({ key: id, value: parseInt(elm.value) })
data: JSON.stringify({
[id]: parseInt(document.getElementById(id).value)
})
});
}
function doSaveConfigFloat(id) {
$.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json",
data: JSON.stringify({
[id]: parseFloat(document.getElementById(id).value)
})
});
}
function doSaveConfigStringArray(id) {
$.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json",
data: JSON.stringify({
[id]: document
.getElementById(id)
.getAttribute("data-tags-value")
.split(", ")
.filter(x => x)
})
});
}
@ -1881,26 +1923,32 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
interval = setInterval(() => {
if (window.authz) {
clearInterval(interval);
fetch("/custom/config?" + window.authz).then(async res => {
if (res.status == 200) {
$.post({
url: "/custom/getConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify(uiConfigs)
})
.done(json => {
//window.is_admin = true;
$("#server-settings-no-perms").addClass("d-none");
$("#server-settings").removeClass("d-none");
res.json().then(json =>
Object.entries(json).forEach(entry => {
const [key, value] = entry;
var x = document.getElementById(`${key}`);
if (x != null) {
if (x.type == "checkbox") {
x.checked = value;
} else if (x.type == "number") {
x.setAttribute("value", `${value}`);
}
$(".config-admin-hide").addClass("d-none");
$(".config-admin-show").removeClass("d-none");
Object.entries(json).forEach(entry => {
const [key, value] = entry;
var x = document.getElementById(`${key}`);
if (x != null) {
if (x.type == "checkbox") {
x.checked = value;
} else if (x.classList.contains("tags-input")) {
x.value = value.join(", ");
x.oninput();
} else {
x.value = value;
}
})
);
} else {
if ((await res.text()) == "Log-in expired") {
}
});
})
.fail(res => {
if (res.responseText == "Log-in expired") {
revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats");
@ -1908,11 +1956,10 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
});
} else {
//window.is_admin = false;
$("#server-settings-no-perms").removeClass("d-none");
$("#server-settings").addClass("d-none");
$(".config-admin-hide").removeClass("d-none");
$(".config-admin-show").addClass("d-none");
}
}
});
});
}
}, 10);
});
@ -2274,14 +2321,13 @@ function doAcquireBoosters() {
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;
const ExpiryDate = Math.trunc(new Date(ExpiryDateInput.value).getTime() / 1000);
if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus();
return false;
@ -2579,3 +2625,27 @@ const importSamples = {
function setImportSample(key) {
$("#import-inventory").val(JSON.stringify(importSamples[key], null, 2));
}
document.querySelectorAll(".tags-input").forEach(input => {
const datalist = document.getElementById(input.getAttribute("list"));
const options = [...datalist.querySelectorAll("option")].map(x => x.textContent);
input.oninput = function () {
const value = [];
for (const tag of this.value.split(",")) {
const index = options.map(x => x.toLowerCase()).indexOf(tag.trim().toLowerCase());
if (index != -1) {
value.push(options[index]);
}
}
this.setAttribute("data-tags-value", value.join(", "));
datalist.innerHTML = "";
for (const option of options) {
const elm = document.createElement("option");
elm.textContent = [...value, option, ""].join(", ");
datalist.appendChild(elm);
}
};
input.oninput();
});

View File

@ -1,6 +1,6 @@
// German translation by Animan8000
dict = {
general_inventoryUpdateNote: `Hinweis: Änderungen, die hier vorgenommen werden, werden erst im Spiel angewendet, sobald das Inventar synchronisiert wird. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Hinzufügen`,
general_bulkActions: `Massenaktionen`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
@ -175,6 +175,7 @@ dict = {
cheats_fastClanAscension: `Schneller Clan-Aufstieg`,
cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_save: `[UNTRANSLATED] Save`,
@ -186,6 +187,53 @@ dict = {
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_none: `Keines`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
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_samples: `[UNTRANSLATED] Samples:`,
@ -209,7 +257,7 @@ dict = {
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_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill 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`,

View File

@ -1,5 +1,5 @@
dict = {
general_inventoryUpdateNote: `Note: Changes made here will only be applied in-game when the game syncs the inventory. Visiting the navigation should be the easiest way to trigger that.`,
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Add`,
general_bulkActions: `Bulk Actions`,
code_loginFail: `Login failed. Double-check the email and password.`,
@ -174,6 +174,7 @@ dict = {
cheats_fastClanAscension: `Fast Clan Ascension`,
cheats_missionsCanGiveAllRelics: `Missions Can Give All Relics`,
cheats_unlockAllSimarisResearchEntries: `Unlock All Simaris Research Entries`,
cheats_disableDailyTribute: `Disable Daily Tribute`,
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`,
cheats_save: `Save`,
@ -185,6 +186,53 @@ dict = {
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_none: `None`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,
worldState_affinityBoost: `Affinity Boost`,
worldState_resourceBoost: `Resource Boost`,
worldState_starDays: `Star Days`,
worldState_galleonOfGhouls: `Galleon of Ghouls`,
disabled: `Disabled`,
worldState_we1: `Weekend 1`,
worldState_we2: `Weekend 2`,
worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `Eidolon Override`,
worldState_day: `Day`,
worldState_night: `Night`,
worldState_vallisOverride: `Orb Vallis Override`,
worldState_warm: `Warm`,
worldState_cold: `Cold`,
worldState_duviriOverride: `Duviri Override`,
worldState_joy: `Joy`,
worldState_anger: `Anger`,
worldState_envy: `Envy`,
worldState_sorrow: `Sorrow`,
worldState_fear: `Fear`,
worldState_nightwaveOverride: `Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `Intermission III`,
worldState_RadioLegion3Syndicate: `Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `Intermission II`,
worldState_RadioLegion2Syndicate: `The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `Intermission I`,
worldState_RadioLegionSyndicate: `The Wolf of Saturn Six`,
worldState_fissures: `Fissures`,
normal: `Normal`,
worldState_allAtOnceNormal: `All At Once, Normal`,
worldState_allAtOnceSteelPath: `All At Once, Steel Path`,
worldState_theCircuitOverride: `The Circuit Override`,
worldState_darvoStockMultiplier: `Darvo Stock Multiplier`,
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_samples: `Samples:`,
@ -208,7 +256,7 @@ dict = {
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_WarframeHPBoostFromImpact: `+|VAL1| Health on kill 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`,

View File

@ -1,6 +1,6 @@
// Spanish translation by hxedcl
dict = {
general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Agregar`,
general_bulkActions: `Acciones masivas`,
code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`,
@ -175,6 +175,7 @@ dict = {
cheats_fastClanAscension: `Ascenso rápido del clan`,
cheats_missionsCanGiveAllRelics: `Las misiones pueden otorgar todas las reliquias`,
cheats_unlockAllSimarisResearchEntries: `Desbloquear todas las entradas de investigación de Simaris`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`,
cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`,
cheats_save: `Guardar`,
@ -186,6 +187,53 @@ dict = {
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`,
cheats_none: `Ninguno`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
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_samples: `Muestras:`,
@ -209,7 +257,7 @@ dict = {
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_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
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`,

View File

@ -1,6 +1,6 @@
// French translation by Vitruvio
dict = {
general_inventoryUpdateNote: `Note : Les changements effectués ici seront appliqués lors de la syncrhonisation. Visiter la navigation appliquera les changements apportés à l'inventaire.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Ajouter`,
general_bulkActions: `Action groupée`,
code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
@ -175,6 +175,7 @@ dict = {
cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
cheats_save: `Sauvegarder`,
@ -186,6 +187,53 @@ dict = {
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_none: `Aucun`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
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_samples: `Echantillons :`,
@ -209,7 +257,7 @@ dict = {
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`,
upgrade_WarframeHealthMax: `+|VAL| de santé`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| de santé par ennemi tué avec des dégâts explosifs (Max |VAL2| de santé)`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`,
upgrade_WarframeRegen: `+|VAL| régénération de santé/s`,

View File

@ -1,6 +1,6 @@
// Russian translation by AMelonInsideLemon
dict = {
general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Добавить`,
general_bulkActions: `Массовые действия`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
@ -175,6 +175,7 @@ dict = {
cheats_fastClanAscension: `Мгновенное Вознесение Клана`,
cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_save: `[UNTRANSLATED] Save`,
@ -186,6 +187,53 @@ dict = {
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_none: `Отсутствует`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
import_submit: `Отправить`,
import_samples: `[UNTRANSLATED] Samples:`,
@ -209,7 +257,7 @@ dict = {
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_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill 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`,

View File

@ -1,6 +1,6 @@
// Chinese translation by meb154 & bishan178
// Chinese translation by meb154, bishan178 & Corvus
dict = {
general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `添加`,
general_bulkActions: `批量操作`,
code_loginFail: `登录失败。请检查邮箱和密码。`,
@ -74,9 +74,9 @@ dict = {
inventory_longGuns: `主要武器`,
inventory_pistols: `次要武器`,
inventory_melee: `近战武器`,
inventory_spaceSuits: `Archwings`,
inventory_spaceGuns: `Archwing主武器`,
inventory_spaceMelee: `Archwing近战武器`,
inventory_spaceSuits: `载具`,
inventory_spaceGuns: `载具主武器`,
inventory_spaceMelee: `载具近战武器`,
inventory_mechSuits: `殁世机甲`,
inventory_sentinels: `守护`,
inventory_sentinelWeapons: `守护武器`,
@ -88,15 +88,15 @@ dict = {
inventory_Boosters: `加成器`,
inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失Archwing`,
inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`,
inventory_bulkAddSpaceSuits: `添加缺失载具`,
inventory_bulkAddSpaceWeapons: `添加缺失载具武器`,
inventory_bulkAddSentinels: `添加缺失守护`,
inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`,
inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`,
inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
inventory_bulkRankUpSpaceWeapons: `所有载具武器升满级`,
inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`,
@ -175,17 +175,65 @@ dict = {
cheats_fastClanAscension: `快速升级氏族`,
cheats_missionsCanGiveAllRelics: `任务可获取所有遗物`,
cheats_unlockAllSimarisResearchEntries: `解锁所有Simaris研究条目`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`,
cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`,
cheats_save: `保存`,
cheats_account: `账户`,
cheats_unlockAllFocusSchools: `解锁所有专精学派`,
cheats_helminthUnlockAll: `完全升级Helminth`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_addMissingSubsumedAbilities: `添加Helminth未汲取的战甲技能`,
cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`,
cheats_none: ``,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,
worldState_affinityBoost: `经验加成`,
worldState_resourceBoost: `资源加成`,
worldState_starDays: `活动:星日`,
worldState_galleonOfGhouls: `战术警报:尸鬼的帆船战舰`,
disabled: `关闭/取消配置`,
worldState_we1: `活动阶段:第一周`,
worldState_we2: `活动阶段:第二周`,
worldState_we3: `活动阶段:第三周`,
worldState_eidolonOverride: `夜灵平原/魔胎之境状态`,
worldState_day: `白昼/FASS`,
worldState_night: `黑夜/VOME`,
worldState_vallisOverride: `奥布山谷状态`,
worldState_warm: `温暖`,
worldState_cold: `寒冷`,
worldState_duviriOverride: `双衍王镜状态`,
worldState_joy: `喜悦`,
worldState_anger: `愤怒`,
worldState_envy: `嫉妒`,
worldState_sorrow: `悲伤`,
worldState_fear: `恐惧`,
worldState_nightwaveOverride: `午夜电波系列`,
worldState_RadioLegionIntermission13Syndicate: `诺拉的混选VOL.9`,
worldState_RadioLegionIntermission12Syndicate: `诺拉的混选VOL.8`,
worldState_RadioLegionIntermission11Syndicate: `诺拉的混选VOL.7`,
worldState_RadioLegionIntermission10Syndicate: `诺拉的混选VOL.6`,
worldState_RadioLegionIntermission9Syndicate: `诺拉的混选VOL.5`,
worldState_RadioLegionIntermission8Syndicate: `诺拉的混选VOL.4`,
worldState_RadioLegionIntermission7Syndicate: `诺拉的混选VOL.3`,
worldState_RadioLegionIntermission6Syndicate: `诺拉的混选VOL.2`,
worldState_RadioLegionIntermission5Syndicate: `诺拉的混选VOL.1`,
worldState_RadioLegionIntermission4Syndicate: `诺拉的精选`,
worldState_RadioLegionIntermission3Syndicate: `间歇III`,
worldState_RadioLegion3Syndicate: `系列3 — 玻璃匠`,
worldState_RadioLegionIntermission2Syndicate: `间歇II`,
worldState_RadioLegion2Syndicate: `系列2 — 使徒`,
worldState_RadioLegionIntermissionSyndicate: `间歇I`,
worldState_RadioLegionSyndicate: `系列1 — 土星六号之狼`,
worldState_fissures: `虚空裂缝难度设定`,
normal: `正常`,
worldState_allAtOnceNormal: `全部开启(普通)`,
worldState_allAtOnceSteelPath: `全部开启(钢铁之路)`,
worldState_theCircuitOverride: `无尽回廊任务循环配置:`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`,
import_submit: `提交`,
import_samples: `示例:`,
@ -209,7 +257,7 @@ dict = {
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% 能量球效果`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% 生命球效果`,
upgrade_WarframeHealthMax: `+|VAL| 生命`,
upgrade_WarframeHPBoostFromImpact: `每个被爆炸伤害击杀的敌人,补充 |VAL1|生命 (最大 |VAL2| 生命)`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% 跑酷速度`,
upgrade_WarframeRadiationDamageBoost: `对受辐射状态影响的敌人 +|VAL|% 技能伤害`,
upgrade_WarframeRegen: `+|VAL| 生命再生/s`,