Compare commits

...

17 Commits
main ... main

Author SHA1 Message Date
9468768947 fix: weapon seed's low dword being sign extended (#1914)
JavaScript's semantics here are incredibly stupid, but basically if the initial DWORD's high WORD's MSB is true, the number would become negative after the shift left by 16. Then when ORing it with the highDword, the initial DWORD would be sign-extended to a QWORD, meaning the high DWORD would become all 1s, basically cancelling out the entire OR operation.

Reviewed-on: OpenWF/SpaceNinjaServer#1914
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:28:01 -07:00
0af7f41201 fix: unset LibraryPersonalTarget after completing it (#1913)
Reviewed-on: OpenWF/SpaceNinjaServer#1913
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:27:47 -07:00
de1e2a25f2 fix(webui): ensure that all requests using authz revalidate it (#1911)
Closes #1907

Reviewed-on: OpenWF/SpaceNinjaServer#1911
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:27:38 -07:00
1cf7b41d3f chore: note that random element functions could return undefined (#1910)
We should be explicit about the fact that we expect the arrays to not be empty.

Reviewed-on: OpenWF/SpaceNinjaServer#1910
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 12:27:25 -07:00
ab9cc685eb fix: exclude capture as a mission type for sorties (#1909)
Closes #1865

Reviewed-on: OpenWF/SpaceNinjaServer#1909
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-29 02:05:18 -07:00
743b784754 chore(webui): use plural form of "Moa", just to stay consistent with the other categories (#1905)
Reviewed-on: OpenWF/SpaceNinjaServer#1905
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-28 14:01:35 -07:00
5df533a7fb chore: auto-generate "daily special" for fish vendors (#1902)
Trying to go a bit more towards an "auto-generate by default" approach, with manual overrides where needed.

Reviewed-on: OpenWF/SpaceNinjaServer#1902
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:01:17 -07:00
9417aa3c84 fix: only consider market-listed blueprints for login reward (#1900)
Closes #1882

Reviewed-on: OpenWF/SpaceNinjaServer#1900
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:01:02 -07:00
a1872e2b07 chore: simplify getInnateDamageTag (#1899)
Reviewed-on: OpenWF/SpaceNinjaServer#1899
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:51 -07:00
9042e85355 feat: infested lich rewards (#1898)
Closes #1884

Reviewed-on: OpenWF/SpaceNinjaServer#1898
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:38 -07:00
66ee550ccd feat: refresh duviri seed when mood changes (#1895)
Closes #1887

Reviewed-on: OpenWF/SpaceNinjaServer#1895
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:22 -07:00
7a295a86ec fix: handle boosters in store item utilities (#1894)
e.g. `/Lotus/Types/StoreItems/Boosters/AffinityBoosterStoreItem`

Reviewed-on: OpenWF/SpaceNinjaServer#1894
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 14:00:06 -07:00
88d00eaaa1 feat: weaken nemesis (#1893)
Closes #1885

Reviewed-on: OpenWF/SpaceNinjaServer#1893
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 13:59:54 -07:00
1e8f2fc766 chore: comment out mixed fields in inventory (#1892)
If they are needed in the future, they schould be properly schema'd.

Reviewed-on: OpenWF/SpaceNinjaServer#1892
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-28 13:58:39 -07:00
0d842ade90 chore(webui): update German translation (#1904)
Reviewed-on: OpenWF/SpaceNinjaServer#1904
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-28 05:14:25 -07:00
4e3a2e17ee chore: removing unnecessary entries in allScans.json (#1903)
Reviewed-on: OpenWF/SpaceNinjaServer#1903
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-28 03:36:39 -07:00
61864b2be1 chore(webui): update to Spanish translation (#1901)
Reviewed-on: OpenWF/SpaceNinjaServer#1901
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-27 19:39:49 -07:00
29 changed files with 601 additions and 697 deletions

8
package-lock.json generated
View File

@ -18,7 +18,7 @@
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"typescript": "^5.5",
"warframe-public-export-plus": "^0.5.58",
"warframe-public-export-plus": "^0.5.59",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
@ -3789,9 +3789,9 @@
}
},
"node_modules/warframe-public-export-plus": {
"version": "0.5.58",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.58.tgz",
"integrity": "sha512-2G3tKcoblUl7S3Rkk5k/qH+VGZBUmU2QjtIrEO/Bt6UlgO83s648elkNdDKOLBKXnxIsa194nVwz+ci1K86sXg=="
"version": "0.5.59",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.59.tgz",
"integrity": "sha512-/SUCVjngVDBz6gahz7CdVLywtHLODL6O5nmNtQcxFDUwrUGnF1lETcG8/UO+WLeGxBVAy4BDPbq+9ZWlYZM4uQ=="
},
"node_modules/warframe-riven-info": {
"version": "0.1.2",

View File

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

View File

@ -17,7 +17,7 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
ItemCount: -1
}
]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex =
inventory.Upgrades.push({

View File

@ -28,7 +28,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
});
const rawRivenType = getRandomRawRivenType();
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
const upgradeIndex =

View File

@ -13,6 +13,7 @@ import { addItems, combineInventoryChanges, getInventory } from "@/src/services/
import { logger } from "@/src/utils/logger";
import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
@ -48,7 +49,7 @@ export const inboxController: RequestHandler = async (req, res) => {
await addItems(
inventory,
attachmentItems.map(attItem => ({
ItemType: attItem,
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
})),
inventoryChanges

View File

@ -18,10 +18,12 @@ import {
addMiscItems,
allDailyAffiliationKeys,
cleanupInventory,
createLibraryDailyTask
createLibraryDailyTask,
generateRewardSeed
} from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose";
export const inventoryController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request);
@ -87,7 +89,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
cleanupInventory(inventory);
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
await inventory.save();
//await inventory.save();
}
if (
@ -96,9 +98,20 @@ export const inventoryController: RequestHandler = async (request, response) =>
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
) {
handleSubsumeCompletion(inventory);
await inventory.save();
//await inventory.save();
}
if (inventory.LastInventorySync) {
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
if (lastSyncDuviriMood != currentDuviriMood) {
logger.debug(`refreshing duviri seed`);
inventory.DuviriInfo.Seed = generateRewardSeed();
}
}
inventory.LastInventorySync = new Types.ObjectId();
await inventory.save();
response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query));
};
@ -274,7 +287,7 @@ export const getInventoryResponse = async (
}
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
inventoryResponse.LastInventorySync = undefined;
// Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true;

View File

@ -141,7 +141,7 @@ const getModularWeaponSale = (
getItemType: (parts: string[]) => string
): IModularWeaponSaleInfo => {
const rng = new CRng(day);
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
let partsCost = 0;
for (const part of parts) {
partsCost += ExportWeapons[part].premiumPrice!;

View File

@ -2,9 +2,12 @@ import {
consumeModCharge,
encodeNemesisGuess,
getInfNodes,
getKnifeUpgrade,
getNemesisPasscode,
getNemesisPasscodeModTypes,
getWeaponsForManifest,
IKnifeResponse
IKnifeResponse,
showdownNodes
} from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
@ -15,6 +18,8 @@ import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import {
IInnateDamageFingerprint,
IInventoryClient,
INemesisClient,
InventorySlot,
IUpgradeClient,
IWeaponSkinClient,
@ -100,50 +105,45 @@ export const nemesisController: RequestHandler = async (req, res) => {
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
);
// Increase antivirus
let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
// Increase antivirus if correct antivirus mod is installed
const response: IKnifeResponse = {};
for (const upgrade of body.knife!.AttachedUpgrades) {
switch (upgrade.ItemType) {
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
if (result1 == 0 || result2 == 0 || result3 == 0) {
let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
for (const upgrade of body.knife!.AttachedUpgrades) {
switch (upgrade.ItemType) {
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
antivirusGain += 15;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
antivirusGain += 10;
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
break;
}
}
inventory.Nemesis!.HenchmenKilled += antivirusGain;
}
inventory.Nemesis!.HenchmenKilled += antivirusGain;
if (inventory.Nemesis!.HenchmenKilled >= 100) {
inventory.Nemesis!.HenchmenKilled = 100;
inventory.Nemesis!.InfNodes = [
{
Node: "CrewBattleNode559",
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
} else {
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
}
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
await inventory.save();
res.json(response);
@ -213,6 +213,38 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({
target: inventory.toJSON().Nemesis
});
} else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(
accountId,
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
);
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
inventory.Nemesis!.InfNodes = [
{
Node: showdownNodes[inventory.Nemesis!.Faction],
Influence: 1
}
];
inventory.Nemesis!.Weakened = true;
const response: IKnifeResponse & { target: INemesisClient } = {
target: inventory.toJSON<IInventoryClient>().Nemesis!
};
// Consume charge of the correct requiem mod(s)
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
for (const modType of modTypes) {
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
}
await inventory.save();
res.json(response);
} else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
@ -264,12 +296,19 @@ interface INemesisRequiemRequest {
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
position: number; // grn/crp: 0-2 | coda: 0
// knife field provided for coda only
knife?: {
Item: IEquipmentClient;
Skins: IWeaponSkinClient[];
ModSlot: number;
CustSlot: number;
AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean;
};
knife?: IKnife;
}
// interface INemesisWeakenRequest {
// target: INemesisClient;
// knife: IKnife;
// }
interface IKnife {
Item: IEquipmentClient;
Skins: IWeaponSkinClient[];
ModSlot: number;
CustSlot: number;
AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean;
}

View File

@ -1,12 +1,14 @@
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
import { SRng } from "@/src/services/rngService";
import { IInfNode, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { getRewardAtPercentage, SRng } from "@/src/services/rngService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { logger } from "../utils/logger";
import { IOid } from "../types/commonTypes";
import { Types } from "mongoose";
import { addMods } from "../services/inventoryService";
import { addMods, generateRewardSeed } from "../services/inventoryService";
import { isArchwingMission } from "../services/worldStateService";
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
import { createMessage } from "../services/inboxService";
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
const infNodes = [];
@ -38,17 +40,59 @@ const systemIndexes: Record<string, number[]> = {
FC_INFESTATION: [23]
};
export const showdownNodes: Record<string, string> = {
FC_GRINEER: "CrewBattleNode557",
FC_CORPUS: "CrewBattleNode558",
FC_INFESTATION: "CrewBattleNode559"
};
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
const rng = new SRng(nemesis.fp);
const passcode = [rng.randomInt(0, 7)];
const choices = [0, 1, 2, 3, 5, 6, 7];
let choiceIndex = rng.randomInt(0, choices.length - 1);
const passcode = [choices[choiceIndex]];
if (nemesis.Faction != "FC_INFESTATION") {
passcode.push(rng.randomInt(0, 7));
passcode.push(rng.randomInt(0, 7));
choices.splice(choiceIndex, 1);
choiceIndex = rng.randomInt(0, choices.length - 1);
passcode.push(choices[choiceIndex]);
choices.splice(choiceIndex, 1);
choiceIndex = rng.randomInt(0, choices.length - 1);
passcode.push(choices[choiceIndex]);
}
return passcode;
};
const reqiuemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
];
const antivirusMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
];
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
const passcode = getNemesisPasscode(nemesis);
return nemesis.Faction == "FC_INFESTATION"
? passcode.map(i => antivirusMods[i])
: passcode.map(i => reqiuemMods[i]);
};
export const encodeNemesisGuess = (
symbol1: number,
result1: number,
@ -79,6 +123,31 @@ export interface IKnifeResponse {
HasKnife?: boolean;
}
export const getKnifeUpgrade = (
inventory: TInventoryDatabaseDocument,
dataknifeUpgrades: string[],
type: string
): { ItemId: IOid; ItemType: string } => {
if (dataknifeUpgrades.indexOf(type) != -1) {
return {
ItemId: { $oid: "000000000000000000000000" },
ItemType: type
};
}
for (const upgradeId of dataknifeUpgrades) {
if (upgradeId.length == 24) {
const upgrade = inventory.Upgrades.id(upgradeId);
if (upgrade && upgrade.ItemType == type) {
return {
ItemId: { $oid: upgradeId },
ItemType: type
};
}
}
}
throw new Error(`${type} does not seem to be installed on parazon?!`);
};
export const consumeModCharge = (
response: IKnifeResponse,
inventory: TInventoryDatabaseDocument,
@ -177,7 +246,6 @@ export const getWeaponsForManifest = (manifest: string): readonly string[] => {
throw new Error(`unknown nemesis manifest: ${manifest}`);
};
// TODO: This sucks.
export const getInnateDamageTag = (
KillingSuit: string
):
@ -188,78 +256,7 @@ export const getInnateDamageTag = (
| "InnateMagDamage"
| "InnateRadDamage"
| "InnateToxinDamage" => {
const baseSuitType = ExportWarframes[KillingSuit].parentName;
switch (baseSuitType) {
case "/Lotus/Powersuits/Volt/VoltBaseSuit":
case "/Lotus/Powersuits/Excalibur/ExcaliburBaseSuit":
case "/Lotus/Powersuits/AntiMatter/NovaBaseSuit":
case "/Lotus/Powersuits/Banshee/BansheeBaseSuit":
case "/Lotus/Powersuits/Berserker/BerserkerBaseSuit":
case "/Lotus/Powersuits/Magician/MagicianBaseSuit":
case "/Lotus/Powersuits/Sentient/SentientBaseSuit":
case "/Lotus/Powersuits/Gyre/GyreBaseSuit":
return "InnateElectricityDamage";
case "/Lotus/Powersuits/Ember/EmberBaseSuit":
case "/Lotus/Powersuits/Dragon/DragonBaseSuit":
case "/Lotus/Powersuits/Nezha/NezhaBaseSuit":
case "/Lotus/Powersuits/Sandman/SandmanBaseSuit":
case "/Lotus/Powersuits/Trapper/TrapperBaseSuit":
case "/Lotus/Powersuits/Wisp/WispBaseSuit":
case "/Lotus/Powersuits/Odalisk/OdaliskBaseSuit":
case "/Lotus/Powersuits/PaxDuviricus/PaxDuviricusBaseSuit":
case "/Lotus/Powersuits/Choir/ChoirBaseSuit":
case "/Lotus/Powersuits/Temple/TempleBaseSuit":
return "InnateHeatDamage";
case "/Lotus/Powersuits/Frost/FrostBaseSuit":
case "/Lotus/Powersuits/Glass/GlassBaseSuit":
case "/Lotus/Powersuits/Fairy/FairyBaseSuit":
case "/Lotus/Powersuits/IronFrame/IronFrameBaseSuit":
case "/Lotus/Powersuits/Revenant/RevenantBaseSuit":
case "/Lotus/Powersuits/Trinity/TrinityBaseSuit":
case "/Lotus/Powersuits/Hoplite/HopliteBaseSuit":
case "/Lotus/Powersuits/Koumei/KoumeiBaseSuit":
return "InnateFreezeDamage";
case "/Lotus/Powersuits/Saryn/SarynBaseSuit":
case "/Lotus/Powersuits/Paladin/PaladinBaseSuit":
case "/Lotus/Powersuits/Brawler/BrawlerBaseSuit":
case "/Lotus/Powersuits/Infestation/InfestationBaseSuit":
case "/Lotus/Powersuits/Necro/NecroBaseSuit":
case "/Lotus/Powersuits/Khora/KhoraBaseSuit":
case "/Lotus/Powersuits/Ranger/RangerBaseSuit":
case "/Lotus/Powersuits/Dagath/DagathBaseSuit":
return "InnateToxinDamage";
case "/Lotus/Powersuits/Mag/MagBaseSuit":
case "/Lotus/Powersuits/Pirate/PirateBaseSuit":
case "/Lotus/Powersuits/Cowgirl/CowgirlBaseSuit":
case "/Lotus/Powersuits/Priest/PriestBaseSuit":
case "/Lotus/Powersuits/BrokenFrame/BrokenFrameBaseSuit":
case "/Lotus/Powersuits/Alchemist/AlchemistBaseSuit":
case "/Lotus/Powersuits/Yareli/YareliBaseSuit":
case "/Lotus/Powersuits/Geode/GeodeBaseSuit":
case "/Lotus/Powersuits/Frumentarius/FrumentariusBaseSuit":
return "InnateMagDamage";
case "/Lotus/Powersuits/Loki/LokiBaseSuit":
case "/Lotus/Powersuits/Ninja/NinjaBaseSuit":
case "/Lotus/Powersuits/Jade/JadeBaseSuit":
case "/Lotus/Powersuits/Bard/BardBaseSuit":
case "/Lotus/Powersuits/Harlequin/HarlequinBaseSuit":
case "/Lotus/Powersuits/Garuda/GarudaBaseSuit":
case "/Lotus/Powersuits/YinYang/YinYangBaseSuit":
case "/Lotus/Powersuits/Werewolf/WerewolfBaseSuit":
case "/Lotus/Powersuits/ConcreteFrame/ConcreteFrameBaseSuit":
return "InnateRadDamage";
case "/Lotus/Powersuits/Rhino/RhinoBaseSuit":
case "/Lotus/Powersuits/Tengu/TenguBaseSuit":
case "/Lotus/Powersuits/MonkeyKing/MonkeyKingBaseSuit":
case "/Lotus/Powersuits/Runner/RunnerBaseSuit":
case "/Lotus/Powersuits/Pacifist/PacifistBaseSuit":
case "/Lotus/Powersuits/Devourer/DevourerBaseSuit":
case "/Lotus/Powersuits/Wraith/WraithBaseSuit":
case "/Lotus/Powersuits/Pagemaster/PagemasterBaseSuit":
return "InnateImpactDamage";
}
logger.warn(`unknown innate damage type for ${KillingSuit}, using heat as a fallback`);
return "InnateHeatDamage";
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
};
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
@ -273,3 +270,109 @@ export const getInnateDamageValue = (fp: bigint): number => {
}
return Math.trunc(value * 0x40000000);
};
export const getKillTokenRewardCount = (fp: bigint): number => {
const rng = new SRng(fp);
return rng.randomInt(10, 15);
};
// /Lotus/Types/Enemies/InfestedLich/InfestedLichRewardManifest
const infestedLichRotA = [
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeHuman", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeInfested", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterA", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterB", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandDespairPoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandGridPoster", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandHuddlePoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandJumpPoster", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLimoPoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterDay", probability: 0.046 },
{
type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterNight",
probability: 0.045
},
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandSillyPoster", probability: 0.046 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhiteBluePoster", probability: 0.045 },
{ type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhitePinkPoster", probability: 0.045 }
];
const infestedLichRotB = [
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraA", probability: 0.072 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraB", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraC", probability: 0.072 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraD", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraE", probability: 0.072 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraF", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraG", probability: 0.071 },
{ type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraH", probability: 0.072 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDJRomHype", probability: 0.071 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DancePacketWindmillShuffle", probability: 0.072 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceHarddrivePony", probability: 0.071 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDrillbitCrisscross", probability: 0.072 },
{ type: "/Lotus/StoreItems/Types/Items/Emotes/DanceZekeCanthavethis", probability: 0.071 },
{ type: "/Lotus/StoreItems/Types/Items/PhotoBooth/PhotoboothTileRJLasXStadiumBossArena", probability: 0.071 }
];
export const getInfestedLichItemRewards = (fp: bigint): string[] => {
const rng = new SRng(fp);
const rotAReward = getRewardAtPercentage(infestedLichRotA, rng.randomFloat())!.type;
rng.randomFloat(); // unused afaict
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
return [rotAReward, rotBReward];
};
export const sendCodaFinishedMessage = async (
inventory: TInventoryDatabaseDocument,
fp: bigint = generateRewardSeed(),
name: string = "ZEKE_BEATWOMAN_TM.1999",
killed: boolean = true
): Promise<void> => {
const att: string[] = [];
// First vanquish/convert gives a sigil
const sigil = killed
? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil"
: "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil";
if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) {
att.push(toStoreItem(sigil));
}
const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp);
att.push(fromStoreItem(rotAReward));
att.push(fromStoreItem(rotBReward));
let countedAtt: ITypeCount[] | undefined;
if (killed) {
countedAtt = [
{
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
ItemCount: getKillTokenRewardCount(fp)
}
];
}
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/VanquishBandMsgBody",
arg: [
{
Key: "LICH_NAME",
Tag: name
}
],
att: att,
countedAtt: countedAtt,
sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
highPriority: true
}
]);
};

View File

@ -31,7 +31,7 @@ export interface IFingerprintStat {
}
export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFingerprint => {
const challenge = getRandomElement(meta.availableChallenges!);
const challenge = getRandomElement(meta.availableChallenges!)!;
const fingerprintChallenge: IRivenChallenge = {
Type: challenge.fullName,
Progress: 0,
@ -54,11 +54,11 @@ export const createVeiledRivenFingerprint = (meta: IUpgrade): IVeiledRivenFinger
export const createUnveiledRivenFingerprint = (meta: IUpgrade): IUnveiledRivenFingerprint => {
const fingerprint: IUnveiledRivenFingerprint = {
compat: getRandomElement(meta.compatibleItems!),
compat: getRandomElement(meta.compatibleItems!)!,
lim: 0,
lvl: 0,
lvlReq: getRandomInt(8, 16),
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"]),
pol: getRandomElement(["AP_ATTACK", "AP_DEFENSE", "AP_TACTIC"])!,
buffs: [],
curses: []
};
@ -81,7 +81,7 @@ export const randomiseRivenStats = (meta: IUpgrade, fingerprint: IUnveiledRivenF
if (Math.random() < 0.5) {
const entry = getRandomElement(
meta.upgradeEntries!.filter(x => x.canBeCurse && !fingerprint.buffs.find(y => y.Tag == x.tag))
);
)!;
fingerprint.curses.push({ Tag: entry.tag, Value: Math.trunc(Math.random() * 0x40000000) });
}
};

View File

@ -1399,7 +1399,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//How many Gift do you have left*(gift spends the trade)
GiftsRemaining: { type: Number, default: 8 },
//Curent trade info Giving or Getting items
PendingTrades: [Schema.Types.Mixed],
//PendingTrades: [Schema.Types.Mixed],
//Syndicate currently being pledged to.
SupportedSyndicate: String,
@ -1449,7 +1449,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
KubrowPetEggs: [kubrowPetEggSchema],
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
KubrowPetPrints: [Schema.Types.Mixed],
//KubrowPetPrints: [Schema.Types.Mixed],
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc
Consumables: [typeCountSchema],
@ -1495,7 +1495,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//item like DojoKey or Boss missions key
LevelKeys: [typeCountSchema],
//Active quests
Quests: [Schema.Types.Mixed],
//Quests: [Schema.Types.Mixed],
//Cosmetics like profile glyphs\Kavasa Prime Kubrow Collar\Game Theme etc
FlavourItems: [FlavourItemSchema],
@ -1534,7 +1534,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
TauntHistory: { type: [tauntSchema], default: undefined },
//noShow2FA,VisitPrimeVault etc
WebFlags: Schema.Types.Mixed,
//WebFlags: Schema.Types.Mixed,
//Id CompletedAlerts
CompletedAlerts: [String],
@ -1554,7 +1554,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//the color your clan requests like Items/Research/DojoColors/DojoColorPlainsB
ActiveDojoColorResearch: String,
SentientSpawnChanceBoosters: Schema.Types.Mixed,
//SentientSpawnChanceBoosters: Schema.Types.Mixed,
QualifyingInvasions: [invasionProgressSchema],
FactionScores: [Number],
@ -1589,10 +1589,10 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
DiscoveredMarkers: [discoveredMarkerSchema],
//Open location mission like "JobId" + "StageCompletions"
CompletedJobs: [Schema.Types.Mixed],
//CompletedJobs: [Schema.Types.Mixed],
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
PersonalGoalProgress: [Schema.Types.Mixed],
//PersonalGoalProgress: [Schema.Types.Mixed],
//Setting interface Style
ThemeStyle: String,
@ -1622,13 +1622,13 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
LibraryActiveDailyTaskInfo: libraryDailyTaskInfoSchema,
//https://warframe.fandom.com/wiki/Invasion
InvasionChainProgress: [Schema.Types.Mixed],
//InvasionChainProgress: [Schema.Types.Mixed],
//CorpusLich or GrineerLich
NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema,
NemesisHistory: { type: [nemesisSchema], default: undefined },
LastNemesisAllySpawnTime: Schema.Types.Mixed,
//LastNemesisAllySpawnTime: Schema.Types.Mixed,
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: settingsSchema,
@ -1642,7 +1642,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PlayerSkills: { type: playerSkillsSchema, default: {} },
//TradeBannedUntil data
TradeBannedUntil: Schema.Types.Mixed,
//TradeBannedUntil: Schema.Types.Mixed,
//https://warframe.fandom.com/wiki/Helminth
InfestedFoundry: infestedFoundrySchema,
@ -1662,23 +1662,24 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Unknown and system
DuviriInfo: DuviriInfoSchema,
LastInventorySync: Schema.Types.ObjectId,
Mailbox: MailboxSchema,
HandlerPoints: Number,
ChallengesFixVersion: { type: Number, default: 6 },
PlayedParkourTutorial: Boolean,
ActiveLandscapeTraps: [Schema.Types.Mixed],
RepVotes: [Schema.Types.Mixed],
LeagueTickets: [Schema.Types.Mixed],
//ActiveLandscapeTraps: [Schema.Types.Mixed],
//RepVotes: [Schema.Types.Mixed],
//LeagueTickets: [Schema.Types.Mixed],
HasContributedToDojo: Boolean,
HWIDProtectEnabled: Boolean,
LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" },
CurrentLoadOutIds: [oidSchema],
RandomUpgradesIdentified: Number,
BountyScore: Number,
ChallengeInstanceStates: [Schema.Types.Mixed],
//ChallengeInstanceStates: [Schema.Types.Mixed],
RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
Robotics: [Schema.Types.Mixed],
UsedDailyDeals: [Schema.Types.Mixed],
//Robotics: [Schema.Types.Mixed],
//UsedDailyDeals: [Schema.Types.Mixed],
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },
HasResetAccount: { type: Boolean, default: false },
@ -1759,6 +1760,9 @@ inventorySchema.set("toJSON", {
sn: inventoryDatabase.LockedWeaponGroup.sn ? toOid(inventoryDatabase.LockedWeaponGroup.sn) : undefined
};
}
if (inventoryDatabase.LastInventorySync) {
inventoryResponse.LastInventorySync = toOid(inventoryDatabase.LastInventorySync);
}
}
});

View File

@ -1798,7 +1798,7 @@ export const addKeyChainItems = async (
};
export const createLibraryDailyTask = (): ILibraryDailyTaskInfo => {
const enemyTypes = getRandomElement(libraryDailyTasks);
const enemyTypes = getRandomElement(libraryDailyTasks)!;
const enemyAvatar = ExportEnemies.avatars[enemyTypes[0]];
const scansRequired = getRandomInt(2, 4);
return {
@ -1944,22 +1944,22 @@ export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, neme
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
]);
])!;
const body = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
]);
])!;
const legs = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
]);
])!;
const tail = getRandomElement([
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
]);
])!;
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
addRecipes(inventory, [
{

View File

@ -17,6 +17,7 @@ import {
dict_uk,
dict_zh,
ExportArcanes,
ExportBoosters,
ExportCustoms,
ExportDrones,
ExportGear,
@ -217,15 +218,30 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => {
};
export const isStoreItem = (type: string): boolean => {
return type.startsWith("/Lotus/StoreItems/");
return type.startsWith("/Lotus/StoreItems/") || type in ExportBoosters;
};
export const toStoreItem = (type: string): string => {
if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) {
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type);
if (boosterEntry) {
return boosterEntry[0];
}
throw new Error(`could not convert ${type} to a store item`);
}
return "/Lotus/StoreItems/" + type.substring("/Lotus/".length);
};
export const fromStoreItem = (type: string): string => {
return "/Lotus/" + type.substring("/Lotus/StoreItems/".length);
if (type.startsWith("/Lotus/StoreItems/")) {
return "/Lotus/" + type.substring("/Lotus/StoreItems/".length);
}
if (type in ExportBoosters) {
return ExportBoosters[type].typeName;
}
throw new Error(`${type} is not a store item`);
};
export const getDefaultUpgrades = (parts: string[]): IDefaultUpgrade[] | undefined => {

View File

@ -77,7 +77,6 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab
const reward = rng.randomReward(randomRewards)!;
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
if (reward.RewardType == "RT_RANDOM_RECIPE") {
// Not very faithful implementation but roughly the same idea
const masteredItems = new Set();
for (const entry of inventory.XPInfo) {
masteredItems.add(entry.ItemType);
@ -95,15 +94,15 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab
}
const eligibleRecipes: string[] = [];
for (const [uniqueName, recipe] of Object.entries(ExportRecipes)) {
if (unmasteredItems.has(recipe.resultType)) {
if (!recipe.excludeFromMarket && unmasteredItems.has(recipe.resultType)) {
eligibleRecipes.push(uniqueName);
}
}
if (eligibleRecipes.length == 0) {
// This account has all warframes and weapons already mastered (filthy cheater), need a different reward.
// This account has all applicable warframes and weapons already mastered (filthy cheater), need a different reward.
return getRandomLoginReward(rng, day, inventory);
}
reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes));
reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes)!);
}
return {
//_id: toOid(new Types.ObjectId()),

View File

@ -55,7 +55,7 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
import { getInfNodes, getWeaponsForManifest } from "@/src/helpers/nemesisHelpers";
import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
@ -380,6 +380,7 @@ export const addMissionInventoryUpdates = async (
: 10)
) {
progress.Completed = true;
inventory.LibraryPersonalTarget = undefined;
}
logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`);
synthesisIgnored = false;
@ -639,7 +640,10 @@ export const addMissionInventoryUpdates = async (
});
if (value.killed) {
if (value.weaponLoc) {
if (
value.weaponLoc &&
inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason
) {
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
inventory.Nemesis.WeaponIdx
];
@ -657,6 +661,11 @@ export const addMissionInventoryUpdates = async (
}
}
// TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given?
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);
}
inventory.Nemesis = undefined;
}
break;
@ -922,7 +931,7 @@ export const addMissionRewards = async (
if (rewardInfo.useVaultManifest) {
MissionRewards.push({
StoreItem: getRandomElement(corruptedMods),
StoreItem: getRandomElement(corruptedMods)!,
ItemCount: 1
});
}

View File

@ -47,12 +47,12 @@ export const createGarden = (): IGardeningDatabase => {
Name: "Garden0",
Plants: [
{
PlantType: getRandomElement(plantTypes),
PlantType: getRandomElement(plantTypes)!,
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
PlantType: getRandomElement(plantTypes)!,
EndTime: endTime,
PlotIndex: 1
}
@ -62,12 +62,12 @@ export const createGarden = (): IGardeningDatabase => {
Name: "Garden1",
Plants: [
{
PlantType: getRandomElement(plantTypes),
PlantType: getRandomElement(plantTypes)!,
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
PlantType: getRandomElement(plantTypes)!,
EndTime: endTime,
PlotIndex: 1
}
@ -77,12 +77,12 @@ export const createGarden = (): IGardeningDatabase => {
Name: "Garden2",
Plants: [
{
PlantType: getRandomElement(plantTypes),
PlantType: getRandomElement(plantTypes)!,
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
PlantType: getRandomElement(plantTypes)!,
EndTime: endTime,
PlotIndex: 1
}

View File

@ -6,7 +6,7 @@ export interface IRngResult {
probability: number;
}
export const getRandomElement = <T>(arr: T[]): T => {
export const getRandomElement = <T>(arr: T[]): T | undefined => {
return arr[Math.floor(Math.random() * arr.length)];
};
@ -18,7 +18,10 @@ export const getRandomInt = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const getRewardAtPercentage = <T extends { probability: number }>(pool: T[], percentage: number): T | undefined => {
export const getRewardAtPercentage = <T extends { probability: number }>(
pool: T[],
percentage: number
): T | undefined => {
if (pool.length == 0) return;
const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
@ -110,7 +113,7 @@ export class CRng {
return min;
}
randomElement<T>(arr: T[]): T {
randomElement<T>(arr: T[]): T | undefined {
return arr[Math.floor(this.random() * arr.length)];
}
@ -142,7 +145,7 @@ export class SRng {
return min;
}
randomElement<T>(arr: T[]): T {
randomElement<T>(arr: T[]): T | undefined {
return arr[this.randomInt(0, arr.length - 1)];
}

View File

@ -1,4 +1,5 @@
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { catBreadHash } from "@/src/helpers/stringHelpers";
import { CRng, mixSeeds } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
@ -6,7 +7,6 @@ import { ExportVendors, IRange } from "warframe-public-export-plus";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
import DeimosFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json";
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
@ -22,12 +22,10 @@ import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorI
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json";
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
@ -36,7 +34,6 @@ import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVe
const rawVendorManifests: IVendorManifest[] = [
ArchimedeanVendorManifest,
DeimosEntratiFragmentVendorProductsManifest,
DeimosFishmongerVendorManifest,
DeimosHivemindCommisionsManifestFishmonger,
DeimosHivemindCommisionsManifestPetVendor,
DeimosHivemindCommisionsManifestProspector,
@ -52,12 +49,10 @@ const rawVendorManifests: IVendorManifest[] = [
HubsRailjackCrewMemberVendorManifest,
MaskSalesmanManifest,
Nova1999ConquestShopManifest,
OstronFishmongerVendorManifest,
OstronPetVendorManifest,
OstronProspectorVendorManifest,
RadioLegionIntermission12VendorManifest,
SolarisDebtTokenVendorRepossessionsManifest,
SolarisFishmongerVendorManifest,
SolarisProspectorVendorManifest,
Temple1999VendorManifest,
TeshinHardModeVendorManifest, // uses preprocessing
@ -87,17 +82,11 @@ const generatableVendors: IGeneratableVendorInfo[] = [
cycleOffset: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day
},
{
_id: { $oid: "5be4a159b144f3cdf1c22efa" },
TypeName: "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.hour
},
{
_id: { $oid: "61ba123467e5d37975aeeb03" },
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.week
cycleDuration: unixTimesInMs.week // TODO: Auto-detect this based on the items, so we don't need to specify it explicitly.
}
// {
// _id: { $oid: "5dbb4c41e966f7886c3ce939" },
@ -105,6 +94,10 @@ const generatableVendors: IGeneratableVendorInfo[] = [
// }
];
const getVendorOid = (typeName: string): string => {
return "5be4a159b144f3cd" + catBreadHash(typeName).toString(16).padStart(8, "0");
};
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo.TypeName == typeName) {
@ -116,6 +109,14 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
return generateVendorManifest(vendorInfo);
}
}
if (typeName in ExportVendors) {
return generateVendorManifest({
_id: { $oid: getVendorOid(typeName) },
TypeName: typeName,
RandomSeedType: ExportVendors[typeName].randomSeedType,
cycleDuration: unixTimesInMs.hour
});
}
return undefined;
};
@ -130,6 +131,17 @@ export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined
return generateVendorManifest(vendorInfo);
}
}
for (const [typeName, manifest] of Object.entries(ExportVendors)) {
const typeNameOid = getVendorOid(typeName);
if (typeNameOid == oid) {
return generateVendorManifest({
_id: { $oid: typeNameOid },
TypeName: typeName,
RandomSeedType: manifest.randomSeedType,
cycleDuration: unixTimesInMs.hour
});
}
}
return undefined;
};
@ -195,12 +207,12 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
const manifest = ExportVendors[vendorInfo.TypeName];
const offersToAdd = [];
if (manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue) {
if (manifest.numItems && !manifest.isOneBinPerCycle) {
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider per-bin item limits
// TODO: Consider item probability weightings
offersToAdd.push(rng.randomElement(manifest.items));
offersToAdd.push(rng.randomElement(manifest.items)!);
}
} else {
let binThisCycle;
@ -244,7 +256,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
let itemPrice: { type: string; count: IRange };
do {
itemPrice = rng.randomElement(manifest.randomItemPricesPerBin![rawItem.bin]);
itemPrice = rng.randomElement(manifest.randomItemPricesPerBin![rawItem.bin])!;
} while (item.ItemPrices.find(x => x.ItemType == itemPrice.type));
item.ItemPrices.push({
ItemType: itemPrice.type,
@ -263,11 +275,18 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
) * rawItem.credits.step;
item.RegularPrice = [value, value];
}
if (rawItem.platinum) {
const value =
typeof rawItem.platinum == "number"
? rawItem.platinum
: rng.randomInt(rawItem.platinum.minValue, rawItem.platinum.maxValue);
item.PremiumPrice = [value, value];
}
if (vendorInfo.RandomSeedType) {
item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
item.LocTagRandSeed = (BigInt(highDword) << 32n) | BigInt(item.LocTagRandSeed);
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
}
}
processed.ItemManifest.push(item);

View File

@ -225,7 +225,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
const seed = new CRng(day).randomInt(0, 0xffff);
const rng = new CRng(seed);
const boss = rng.randomElement(sortieBosses);
const boss = rng.randomElement(sortieBosses)!;
const modifiers = [
"SORTIE_MODIFIER_LOW_ENERGY",
@ -269,7 +269,10 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
key in sortieTilesets
) {
if (!availableMissionIndexes.includes(value.missionIndex)) {
if (
value.missionIndex != 5 && // Sorties do not have capture missions
!availableMissionIndexes.includes(value.missionIndex)
) {
availableMissionIndexes.push(value.missionIndex);
}
nodes.push(key);
@ -299,7 +302,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
missionIndex = ExportRegions[node].missionIndex;
if (missionIndex != 28) {
missionIndex = rng.randomElement(availableMissionIndexes);
missionIndex = rng.randomElement(availableMissionIndexes)!;
}
} while (
specialMissionTypes.indexOf(missionIndex) != -1 &&
@ -309,7 +312,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
if (i == 2 && rng.randomInt(0, 2) == 2) {
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
const modifierType = rng.randomElement(filteredModifiers);
const modifierType = rng.randomElement(filteredModifiers)!;
if (boss == "SORTIE_BOSS_PHORID") {
selectedNodes.push({
@ -343,7 +346,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
: modifiers;
const modifierType = rng.randomElement(filteredModifiers);
const modifierType = rng.randomElement(filteredModifiers)!;
selectedNodes.push({
missionType,
@ -386,7 +389,7 @@ const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
Daily: true,
Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } },
Challenge: rng.randomElement(dailyChallenges)
Challenge: rng.randomElement(dailyChallenges)!
};
};
@ -405,7 +408,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: rng.randomElement(weeklyChallenges)
Challenge: rng.randomElement(weeklyChallenges)!
};
};
@ -422,7 +425,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } },
Challenge: rng.randomElement(weeklyHardChallenges)
Challenge: rng.randomElement(weeklyHardChallenges)!
};
};
@ -1193,7 +1196,7 @@ export const getLiteSortie = (week: number): ILiteSortie => {
"MT_EXTERMINATION",
"MT_SABOTAGE",
"MT_RESCUE"
]),
])!,
node: firstNode
},
{
@ -1203,8 +1206,8 @@ export const getLiteSortie = (week: number): ILiteSortie => {
"MT_ARTIFACT",
"MT_EXCAVATE",
"MT_SURVIVAL"
]),
node: rng.randomElement(nodes)
])!,
node: rng.randomElement(nodes)!
},
{
missionType: "MT_ASSASSINATION",

View File

@ -52,6 +52,7 @@ export interface IInventoryDatabase
| "LastLiteSortieReward"
| "CrewMembers"
| "QualifyingInvasions"
| "LastInventorySync"
| TEquipmentKey
>,
InventoryDatabaseEquipment {
@ -89,6 +90,7 @@ export interface IInventoryDatabase
LastLiteSortieReward?: ILastSortieRewardDatabase[];
CrewMembers: ICrewMemberDatabase[];
QualifyingInvasions: IInvasionProgressDatabase[];
LastInventorySync?: Types.ObjectId;
}
export interface IQuestKeyDatabase {
@ -258,7 +260,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EquippedGear: string[];
DeathMarks: string[];
FusionTreasures: IFusionTreasure[];
WebFlags: IWebFlags;
//WebFlags: IWebFlags;
CompletedAlerts: string[];
Consumables: ITypeCount[];
LevelKeys: ITypeCount[];
@ -268,10 +270,10 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
KubrowPetEggs?: IKubrowPetEggClient[];
LoreFragmentScans: ILoreFragmentScan[];
EquippedEmotes: string[];
PendingTrades: IPendingTrade[];
//PendingTrades: IPendingTrade[];
Boosters: IBooster[];
ActiveDojoColorResearch: string;
SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
//SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters;
SupportedSyndicate?: string;
Affiliations: IAffiliation[];
QualifyingInvasions: IInvasionProgressClient[];
@ -293,19 +295,19 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ActiveAvatarImageType: string;
ShipDecorations: ITypeCount[];
DiscoveredMarkers: IDiscoveredMarker[];
CompletedJobs: ICompletedJob[];
//CompletedJobs: ICompletedJob[];
FocusAbility?: string;
FocusUpgrades: IFocusUpgrade[];
HasContributedToDojo?: boolean;
HWIDProtectEnabled?: boolean;
KubrowPetPrints: IKubrowPetPrint[];
//KubrowPetPrints: IKubrowPetPrint[];
AlignmentReplay?: IAlignment;
PersonalGoalProgress: IPersonalGoalProgress[];
//PersonalGoalProgress: IPersonalGoalProgress[];
ThemeStyle: string;
ThemeBackground: string;
ThemeSounds: string;
BountyScore: number;
ChallengeInstanceStates: IChallengeInstanceState[];
//ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[];
RecentVendorPurchases?: IRecentVendorPurchaseClient[];
NodeIntrosCompleted: string[];
@ -313,17 +315,17 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CompletedJobChains?: ICompletedJobChain[];
SeasonChallengeHistory: ISeasonChallenge[];
EquippedInstrument?: string;
InvasionChainProgress: IInvasionChainProgress[];
//InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient;
NemesisHistory?: INemesisBaseClient[];
LastNemesisAllySpawnTime?: IMongoDate;
//LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[];
PlayerSkills: IPlayerSkills;
CrewShipAmmo: ITypeCount[];
CrewShipWeaponSkins: IUpgradeClient[];
CrewShipSalvagedWeaponSkins: IUpgradeClient[];
TradeBannedUntil?: IMongoDate;
//TradeBannedUntil?: IMongoDate;
PlayedParkourTutorial: boolean;
SubscribedToEmailsPersonalized: number;
InfestedFoundry?: IInfestedFoundryClient;
@ -333,17 +335,17 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
LotusCustomization?: ILotusCustomization;
UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[];
LastInventorySync: IOid;
LastInventorySync?: IOid;
NextRefill?: IMongoDate;
FoundToday?: IMiscItem[]; // for Argon Crystals
CustomMarkers?: ICustomMarkers[];
ActiveLandscapeTraps: any[];
//ActiveLandscapeTraps: any[];
EvolutionProgress?: IEvolutionProgress[];
RepVotes: any[];
LeagueTickets: any[];
Quests: any[];
Robotics: any[];
UsedDailyDeals: any[];
//RepVotes: any[];
//LeagueTickets: any[];
//Quests: any[];
//Robotics: any[];
//UsedDailyDeals: any[];
LibraryPersonalTarget?: string;
LibraryPersonalProgress: ILibraryPersonalProgress[];
CollectibleSeries?: ICollectibleEntry[];

View File

@ -1097,7 +1097,5 @@
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLamp",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall",
"/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem",
"/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar",
"/Lotus/Types/Enemies/Grineer/AIWeek/Avatars/BeastMasterAvatar"
"/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem"
]

View File

@ -1,106 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e01c96976e97d6b8016"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/FishmongerVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosOrokinFishAPartItem",
"PremiumPrice": [9, 9],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91b9"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishDPartItem",
"PremiumPrice": [17, 17],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91ba"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"PremiumPrice": [10, 10],
"Bin": "BIN_1",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91bb"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"PremiumPrice": [6, 6],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91bc"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91bd"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem",
"PremiumPrice": [7, 7],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e91be"
}
}
],
"PropertyTextHash": "6DF13A7FB573C25B4B4F989CBEFFC615",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,106 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "59d6e27ebcc718474eb17115"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/FishmongerVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayUncommonFishAPartItem",
"PremiumPrice": [14, 14],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9808"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem",
"PremiumPrice": [12, 12],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9809"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem",
"PremiumPrice": [8, 8],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem",
"PremiumPrice": [7, 7],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980b"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishAPartItem",
"PremiumPrice": [10, 10],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/BothCommonFishBPartItem",
"PremiumPrice": [8, 8],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e980d"
}
}
],
"PropertyTextHash": "CC3B9DAFB38F412998E90A41421A8986",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,106 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5b0de8556df82a56ea9bae82"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/FishmongerVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishThermalLaserItem",
"PremiumPrice": [15, 15],
"Bin": "BIN_1",
"QuantityMultiplier": 10,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9515"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishVenedoCaseItem",
"PremiumPrice": [8, 8],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9516"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/SolarisFishDissipatorCoilItem",
"PremiumPrice": [18, 18],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9517"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishExaBrainItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9518"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishAnoscopicSensorItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9519"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/GenericFishScrapItem",
"PremiumPrice": [5, 5],
"Bin": "BIN_0",
"QuantityMultiplier": 20,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e951a"
}
}
],
"PropertyTextHash": "946131D0CF5CDF7C2C03BB967DE0DF49",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -375,6 +375,7 @@ function fetchItemList() {
}
fetchItemList();
// Assumes that caller revalidates authz
function updateInventory() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
@ -487,25 +488,27 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
if (item.XP < maxXP) {
addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
);
revalidateAuthz(() => {
if (item.XP < maxXP) {
addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
);
}
}
}
}
}
});
};
a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
@ -1229,76 +1232,22 @@ function addMissingEvolutionProgress() {
}
function maxRankAllEvolutions() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
revalidateAuthz(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
const requests = [];
req.done(data => {
const requests = [];
data.EvolutionProgress.forEach(item => {
if (item.Rank < 5) {
requests.push({
ItemType: item.ItemType,
Rank: 5
});
}
});
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests);
}
toast(loc("code_noEquipmentToRankUp"));
});
}
function maxRankAllEquipment(categories) {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
categories.forEach(category => {
data[category].forEach(item => {
const maxXP =
category === "Suits" ||
category === "SpaceSuits" ||
category === "Sentinels" ||
category === "Hoverboards"
? 1_600_000
: 800_000;
if (item.XP < maxXP) {
if (!batchData[category]) {
batchData[category] = [];
}
batchData[category].push({
ItemId: { $oid: item.ItemId.$oid },
XP: maxXP
});
}
if (category === "Suits") {
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
batchData["SpecialItems"] ??= [];
batchData["SpecialItems"].push({
ItemId: { $oid: exaltedItem.ItemId.$oid },
XP: exaltedCap
});
}
}
}
}
}
});
data.EvolutionProgress.forEach(item => {
if (item.Rank < 5) {
requests.push({
ItemType: item.ItemType,
Rank: 5
});
}
});
if (Object.keys(batchData).length > 0) {
return sendBatchGearExp(batchData);
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests);
}
toast(loc("code_noEquipmentToRankUp"));
@ -1306,6 +1255,64 @@ function maxRankAllEquipment(categories) {
});
}
function maxRankAllEquipment(categories) {
revalidateAuthz(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
window.itemListPromise.then(itemMap => {
const batchData = {};
categories.forEach(category => {
data[category].forEach(item => {
const maxXP =
category === "Suits" ||
category === "SpaceSuits" ||
category === "Sentinels" ||
category === "Hoverboards"
? 1_600_000
: 800_000;
if (item.XP < maxXP) {
if (!batchData[category]) {
batchData[category] = [];
}
batchData[category].push({
ItemId: { $oid: item.ItemId.$oid },
XP: maxXP
});
}
if (category === "Suits") {
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
batchData["SpecialItems"] ??= [];
batchData["SpecialItems"].push({
ItemId: { $oid: exaltedItem.ItemId.$oid },
XP: exaltedCap
});
}
}
}
}
}
});
});
if (Object.keys(batchData).length > 0) {
return sendBatchGearExp(batchData);
}
toast(loc("code_noEquipmentToRankUp"));
});
});
});
}
// Assumes that caller revalidates authz
function addGearExp(category, oid, xp) {
const data = {};
data[category] = [
@ -1314,16 +1321,14 @@ function addGearExp(category, oid, xp) {
XP: xp
}
];
revalidateAuthz(() => {
$.post({
url: "/custom/addXp?" + window.authz,
contentType: "application/json",
data: JSON.stringify(data)
}).done(function () {
if (category != "SpecialItems") {
updateInventory();
}
});
$.post({
url: "/custom/addXp?" + window.authz,
contentType: "application/json",
data: JSON.stringify(data)
}).done(function () {
if (category != "SpecialItems") {
updateInventory();
}
});
}
@ -1598,32 +1603,34 @@ function doAcquireMod() {
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
function doChangeSettings() {
fetch("/custom/config?" + window.authz)
.then(response => response.json())
.then(json => {
for (const i of uiConfigs) {
var x = document.getElementById(i);
if (x != null) {
if (x.type == "checkbox") {
if (x.checked === true) {
json[i] = true;
} else {
json[i] = false;
revalidateAuthz(() => {
fetch("/custom/config?" + window.authz)
.then(response => response.json())
.then(json => {
for (const i of uiConfigs) {
var x = document.getElementById(i);
if (x != null) {
if (x.type == "checkbox") {
if (x.checked === true) {
json[i] = true;
} else {
json[i] = false;
}
} else if (x.type == "number") {
json[i] = parseInt(x.value);
}
} else if (x.type == "number") {
json[i] = parseInt(x.value);
}
}
}
$.post({
url: "/custom/config?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(json, null, 2)
}).then(() => {
// A few cheats affect the inventory response which in turn may change what values we need to show
updateInventory();
$.post({
url: "/custom/config?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(json, null, 2)
}).then(() => {
// A few cheats affect the inventory response which in turn may change what values we need to show
updateInventory();
});
});
});
});
}
// Cheats route
@ -1876,33 +1883,39 @@ function doChangeSupportedSyndicate() {
}
function doAddCurrency(currency) {
$.post({
url: "/custom/addCurrency?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
currency,
delta: document.getElementById(currency + "-delta").valueAsNumber
})
}).then(function () {
updateInventory();
revalidateAuthz(() => {
$.post({
url: "/custom/addCurrency?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
currency,
delta: document.getElementById(currency + "-delta").valueAsNumber
})
}).then(function () {
updateInventory();
});
});
}
function doQuestUpdate(operation, itemType) {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
contentType: "application/json"
}).then(function () {
updateInventory();
revalidateAuthz(() => {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
contentType: "application/json"
}).then(function () {
updateInventory();
});
});
}
function doBulkQuestUpdate(operation) {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
contentType: "application/json"
}).then(function () {
updateInventory();
revalidateAuthz(() => {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
contentType: "application/json"
}).then(function () {
updateInventory();
});
});
}

View File

@ -34,8 +34,8 @@ dict = {
code_rerollsNumber: `Anzahl der Umrollversuche`,
code_viewStats: `Statistiken anzeigen`,
code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_rankUp: `Rang erhöhen`,
code_rankDown: `Rang verringern`,
code_count: `Anzahl`,
code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
@ -84,23 +84,23 @@ dict = {
inventory_sentinelWeapons: `Wächter-Waffen`,
inventory_operatorAmps: `Verstärker`,
inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`,
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`,
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`,
@ -120,9 +120,9 @@ dict = {
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
mods_rivens: `Rivens`,
mods_mods: `Mods`,
mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
mods_removeUnranked: `Mods ohne Rang entfernen`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
cheats_server: `Server`,
cheats_skipTutorial: `Tutorial überspringen`,
@ -134,7 +134,7 @@ dict = {
cheats_infiniteEndo: `Unendlich Endo`,
cheats_infiniteRegalAya: `Unendlich Reines Aya`,
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`,
@ -154,7 +154,7 @@ dict = {
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`,
cheats_skipClanKeyCrafting: `Clan-Schlüsselherstellung überspringen`,
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,

View File

@ -83,7 +83,7 @@ dict = {
inventory_sentinelWeapons: `Sentinel Weapons`,
inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`,
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Beasts`,
inventory_evolutionProgress: `Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Add Missing Warframes`,

View File

@ -84,7 +84,7 @@ dict = {
inventory_sentinelWeapons: `Armas de centinela`,
inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`,
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestias`,
inventory_evolutionProgress: `Progreso de evolución Incarnon`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`,
@ -154,7 +154,7 @@ dict = {
cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`,
cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`,
cheats_skipClanKeyCrafting: `Saltar la fabricación de la llave de clan`,
cheats_noDojoRoomBuildStage: `Sin etapa de construcción de sala del dojo`,
cheats_noDojoDecoBuildStage: `Sin etapa de construcción de decoraciones del dojo`,
cheats_fastDojoRoomDestruction: `Destrucción rápida de salas del dojo`,

View File

@ -84,7 +84,7 @@ dict = {
inventory_sentinelWeapons: `Armes de sentinelles`,
inventory_operatorAmps: `Amplificateurs`,
inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`,
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,