Compare commits
1 Commits
main
...
unique-mod
Author | SHA1 | Date | |
---|---|---|---|
2e5d252aee |
@ -14,8 +14,6 @@ ENV APP_INFINITE_PLATINUM=false
|
||||
ENV APP_INFINITE_ENDO=false
|
||||
ENV APP_INFINITE_REGAL_AYA=false
|
||||
ENV APP_INFINITE_HELMINTH_MATERIALS=false
|
||||
ENV APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS=false
|
||||
ENV APP_DONT_SUBTRACT_VOIDTRACES=false
|
||||
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
||||
|
@ -19,8 +19,6 @@
|
||||
"infiniteEndo": false,
|
||||
"infiniteRegalAya": false,
|
||||
"infiniteHelminthMaterials": false,
|
||||
"claimingBlueprintRefundsIngredients": false,
|
||||
"dontSubtractVoidTraces": false,
|
||||
"dontSubtractConsumables": false,
|
||||
"unlockAllShipFeatures": false,
|
||||
"unlockAllShipDecorations": false,
|
||||
|
@ -21,8 +21,6 @@ services:
|
||||
# APP_INFINITE_ENDO: false
|
||||
# APP_INFINITE_REGAL_AYA: false
|
||||
# APP_INFINITE_HELMINTH_MATERIALS: false
|
||||
# APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false
|
||||
# APP_DONT_SUBTRACT_VOIDTRACES: false
|
||||
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
||||
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -13,7 +13,7 @@
|
||||
"@types/morgan": "^1.9.9",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.4.4",
|
||||
"json-with-bigint": "^3.2.2",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
|
@ -20,7 +20,7 @@
|
||||
"@types/morgan": "^1.9.9",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.4.4",
|
||||
"json-with-bigint": "^3.2.2",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||
import { IUpgradeFromClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||
|
||||
@ -24,7 +24,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
]);
|
||||
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||
});
|
||||
|
||||
const rawRivenType = getRandomRawRivenType();
|
||||
@ -57,8 +57,8 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
const meta = ExportUpgrades[upgrade.ItemType];
|
||||
counts[meta.rarity] += upgrade.ItemCount;
|
||||
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||
} else {
|
||||
addMods(inventory, [
|
||||
{
|
||||
@ -128,14 +128,24 @@ const getRandomRawRivenType = (): string => {
|
||||
};
|
||||
|
||||
interface IArtifactTransmutationRequest {
|
||||
Upgrade: IUpgradeFromClient;
|
||||
Upgrade: IAgnosticUpgradeClient;
|
||||
LevelDiff: number;
|
||||
Consumed: IUpgradeFromClient[];
|
||||
Consumed: IAgnosticUpgradeClient[];
|
||||
Cost: number;
|
||||
FusionPointCost: number;
|
||||
RivenTransmute?: boolean;
|
||||
}
|
||||
|
||||
interface IAgnosticUpgradeClient {
|
||||
ItemType: string;
|
||||
ItemId: IOid;
|
||||
FromSKU: boolean;
|
||||
UpgradeFingerprint: string;
|
||||
PendingRerollFingerprint: string;
|
||||
ItemCount: number;
|
||||
LastAdded: IOid;
|
||||
}
|
||||
|
||||
const specialModSets: string[][] = [
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||
|
@ -4,9 +4,9 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { getRecipe } from "@/src/services/itemDataService";
|
||||
import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
getInventory,
|
||||
updateCurrency,
|
||||
@ -17,11 +17,8 @@ import {
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { IRecipe } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
interface IClaimCompletedRecipeRequest {
|
||||
RecipeIds: IOid[];
|
||||
@ -29,8 +26,10 @@ interface IClaimCompletedRecipeRequest {
|
||||
|
||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
if (!accountId) throw new Error("no account id");
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||
if (!pendingRecipe) {
|
||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||
@ -49,14 +48,40 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
}
|
||||
|
||||
if (req.query.cancel) {
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
||||
const inventoryChanges: IInventoryChanges = {
|
||||
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||
};
|
||||
|
||||
const equipmentIngredients = new Set();
|
||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||
if (pendingRecipe[category]) {
|
||||
pendingRecipe[category].forEach(item => {
|
||||
const index = inventory[category].push(item) - 1;
|
||||
inventoryChanges[category] ??= [];
|
||||
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||
equipmentIngredients.add(item.ItemType);
|
||||
|
||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||
inventoryChanges.WeaponBin.Slots -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||
} else {
|
||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||
|
||||
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
||||
let BrandedSuits: undefined | IOid[];
|
||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||
inventory.PendingSpectreLoadouts ??= [];
|
||||
inventory.SpectreLoadouts ??= [];
|
||||
@ -81,7 +106,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||
1
|
||||
);
|
||||
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
||||
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
|
||||
}
|
||||
|
||||
let InventoryChanges: IInventoryChanges = {};
|
||||
@ -118,43 +143,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
))
|
||||
};
|
||||
}
|
||||
if (config.claimingBlueprintRefundsIngredients) {
|
||||
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({ InventoryChanges, BrandedSuits });
|
||||
}
|
||||
};
|
||||
|
||||
const refundRecipeIngredients = async (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
inventoryChanges: IInventoryChanges,
|
||||
recipe: IRecipe,
|
||||
pendingRecipe: IPendingRecipeDatabase
|
||||
): Promise<void> => {
|
||||
updateCurrency(inventory, recipe.buildPrice * -1, false, inventoryChanges);
|
||||
|
||||
const equipmentIngredients = new Set();
|
||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||
if (pendingRecipe[category]) {
|
||||
pendingRecipe[category].forEach(item => {
|
||||
const index = inventory[category].push(item) - 1;
|
||||
inventoryChanges[category] ??= [];
|
||||
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||
equipmentIngredients.add(item.ItemType);
|
||||
|
||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||
inventoryChanges.WeaponBin.Slots -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -15,14 +15,6 @@ export const crewMembersController: RequestHandler = async (req, res) => {
|
||||
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||
dbCrewMember.Configs = data.crewMember.Configs;
|
||||
if (data.crewMember.SecondInCommand) {
|
||||
for (const cm of inventory.CrewMembers) {
|
||||
if (cm.SecondInCommand) {
|
||||
cm.SecondInCommand = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
|
@ -1,529 +1,60 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { IEndlessXpReward, IInventoryClient, TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ExportRewards, ICountedStoreItem } from "warframe-public-export-plus";
|
||||
import { getRandomElement } from "@/src/services/rngService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
|
||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||
if (payload.Mode == "r") {
|
||||
const inventory = await getInventory(accountId, "EndlessXP");
|
||||
inventory.EndlessXP ??= [];
|
||||
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||
if (!entry) {
|
||||
entry = {
|
||||
Category: payload.Category,
|
||||
Earn: 0,
|
||||
Claim: 0,
|
||||
Choices: payload.Choices,
|
||||
PendingRewards: []
|
||||
};
|
||||
inventory.EndlessXP.push(entry);
|
||||
}
|
||||
|
||||
const weekStart = 1734307200_000 + Math.trunc((Date.now() - 1734307200_000) / 604800000) * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
|
||||
entry.Earn = 0;
|
||||
entry.Claim = 0;
|
||||
entry.BonusAvailable = new Date(weekStart);
|
||||
entry.Expiry = new Date(weekEnd);
|
||||
inventory.EndlessXP ??= [];
|
||||
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||
if (entry) {
|
||||
entry.Choices = payload.Choices;
|
||||
entry.PendingRewards =
|
||||
payload.Category == "EXC_HARD"
|
||||
? generateHardModeRewards(payload.Choices)
|
||||
: generateNormalModeRewards(payload.Choices);
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
|
||||
});
|
||||
} else if (payload.Mode == "c") {
|
||||
const inventory = await getInventory(accountId);
|
||||
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
for (const reward of entry.PendingRewards) {
|
||||
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
(
|
||||
await handleStoreItemAcquisition(
|
||||
reward.Rewards[0].StoreItem,
|
||||
inventory,
|
||||
reward.Rewards[0].ItemCount
|
||||
)
|
||||
).InventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
entry.Claim = entry.Earn;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
ClaimedXp: entry.Claim
|
||||
});
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`);
|
||||
inventory.EndlessXP.push({
|
||||
Category: payload.Category,
|
||||
Choices: payload.Choices
|
||||
});
|
||||
}
|
||||
};
|
||||
await inventory.save();
|
||||
|
||||
type IEndlessXpRequest =
|
||||
| {
|
||||
Mode: "r";
|
||||
Category: TEndlessXpCategory;
|
||||
Choices: string[];
|
||||
}
|
||||
| {
|
||||
Mode: "c" | "something else";
|
||||
Category: TEndlessXpCategory;
|
||||
};
|
||||
|
||||
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
|
||||
const reward = getRandomElement(ExportRewards[deckName][0])!;
|
||||
return [
|
||||
{
|
||||
StoreItem: reward.type,
|
||||
ItemCount: reward.itemCount
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const normalModeChosenRewards: Record<string, string[]> = {
|
||||
Excalibur: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
|
||||
],
|
||||
Trinity: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
|
||||
],
|
||||
Ember: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
|
||||
],
|
||||
Loki: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
|
||||
],
|
||||
Mag: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
|
||||
],
|
||||
Rhino: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
|
||||
],
|
||||
Ash: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
|
||||
],
|
||||
Frost: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
|
||||
],
|
||||
Nyx: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
|
||||
],
|
||||
Saryn: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
|
||||
],
|
||||
Vauban: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
|
||||
],
|
||||
Nova: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
|
||||
],
|
||||
Nekros: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
|
||||
],
|
||||
Valkyr: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
|
||||
],
|
||||
Oberon: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
|
||||
],
|
||||
Hydroid: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
|
||||
],
|
||||
Mirage: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
|
||||
],
|
||||
Limbo: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
|
||||
],
|
||||
Mesa: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
|
||||
],
|
||||
Chroma: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
|
||||
],
|
||||
Atlas: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
|
||||
],
|
||||
Ivara: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
|
||||
],
|
||||
Inaros: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
|
||||
],
|
||||
Titania: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
|
||||
],
|
||||
Nidus: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
|
||||
],
|
||||
Octavia: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
|
||||
],
|
||||
Harrow: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
|
||||
],
|
||||
Gara: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
|
||||
],
|
||||
Khora: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
|
||||
],
|
||||
Revenant: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
|
||||
],
|
||||
Garuda: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
|
||||
],
|
||||
Baruuk: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
|
||||
],
|
||||
Hildryn: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
|
||||
]
|
||||
};
|
||||
|
||||
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||
const choiceRewards = normalModeChosenRewards[choices[0]];
|
||||
return [
|
||||
{
|
||||
RequiredTotalXp: 190,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 400,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[0],
|
||||
ItemCount: 1
|
||||
res.json({
|
||||
NewProgress: {
|
||||
Category: payload.Category,
|
||||
Earn: 0,
|
||||
Claim: 0,
|
||||
BonusAvailable: {
|
||||
$date: {
|
||||
$numberLong: "9999999999999"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 630,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 890,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1190,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[1],
|
||||
ItemCount: 1
|
||||
},
|
||||
Expiry: {
|
||||
$date: {
|
||||
$numberLong: "9999999999999"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1540,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1950,
|
||||
Rewards: [
|
||||
},
|
||||
Choices: payload.Choices,
|
||||
PendingRewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[2],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2430,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[3],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2990,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 3640,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[4],
|
||||
ItemCount: 1
|
||||
RequiredTotalXp: 190,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
// ...
|
||||
]
|
||||
}
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
const hardModeChosenRewards: Record<string, string> = {
|
||||
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
||||
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
||||
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
||||
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker",
|
||||
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
|
||||
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
|
||||
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
|
||||
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
|
||||
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
|
||||
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
|
||||
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
|
||||
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
|
||||
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
|
||||
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
|
||||
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
|
||||
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
|
||||
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
|
||||
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
|
||||
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
|
||||
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
|
||||
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
|
||||
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
|
||||
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
|
||||
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
|
||||
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
|
||||
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
|
||||
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
|
||||
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
|
||||
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
|
||||
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
|
||||
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
|
||||
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
|
||||
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
|
||||
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
|
||||
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
|
||||
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
|
||||
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
|
||||
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
|
||||
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
|
||||
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
|
||||
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
|
||||
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
|
||||
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
|
||||
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
|
||||
};
|
||||
|
||||
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||
return [
|
||||
{
|
||||
RequiredTotalXp: 285,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 600,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 945,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1335,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1785,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: hardModeChosenRewards[choices[0]],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2310,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2925,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 3645,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 4485,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 5460,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: hardModeChosenRewards[choices[1]],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
interface IEndlessXpRequest {
|
||||
Mode: string; // "r"
|
||||
Category: TEndlessXpCategory;
|
||||
Choices: string[];
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { config } from "@/src/services/configService";
|
||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { getShip } from "@/src/services/shipService";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
@ -20,6 +21,7 @@ export const getShipController: RequestHandler = async (req, res) => {
|
||||
|
||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||
const loadout = await getLoadout(accountId);
|
||||
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||
|
||||
const getShipResponse: IGetShipResponse = {
|
||||
ShipOwnerId: accountId,
|
||||
@ -29,8 +31,8 @@ export const getShipController: RequestHandler = async (req, res) => {
|
||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||
ShipInterior: {
|
||||
Colors: personalRooms.ShipInteriorColors,
|
||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
||||
SkinFlavourItem: ""
|
||||
ShipAttachments: ship.ShipAttachments,
|
||||
SkinFlavourItem: ship.SkinFlavourItem
|
||||
},
|
||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||
|
@ -2,13 +2,12 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { areFriends } from "@/src/services/friendService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
export const giftingController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||
@ -45,7 +44,10 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
||||
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
||||
|
||||
const senderInventory = await getInventory(senderAccount._id.toString());
|
||||
const senderInventory = await getInventory(
|
||||
senderAccount._id.toString(),
|
||||
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
|
||||
);
|
||||
|
||||
if (senderInventory.GiftsRemaining == 0) {
|
||||
res.status(400).send("10").end();
|
||||
@ -53,20 +55,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
senderInventory.GiftsRemaining -= 1;
|
||||
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(
|
||||
senderInventory,
|
||||
data.PurchaseParams.ExpectedPrice,
|
||||
true
|
||||
);
|
||||
if (data.PurchaseParams.StoreItem in ExportBundles) {
|
||||
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
|
||||
if (bundle.giftingBonus) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||
await senderInventory.save();
|
||||
|
||||
const senderName = getSuffixedName(senderAccount);
|
||||
@ -94,9 +83,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
]);
|
||||
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IGiftingRequest {
|
||||
|
@ -104,7 +104,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
) {
|
||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||
}
|
||||
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId!)) {
|
||||
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId)) {
|
||||
throw new Error(
|
||||
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
||||
);
|
||||
|
@ -25,10 +25,10 @@ import { logger } from "@/src/utils/logger";
|
||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||
import { Types } from "mongoose";
|
||||
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
|
||||
import { version_compare } from "@/src/services/worldStateService";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { Ship } from "@/src/models/shipModel";
|
||||
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
export const inventoryController: RequestHandler = async (request, response) => {
|
||||
const account = await getAccountForRequest(request);
|
||||
@ -306,34 +306,19 @@ export const getInventoryResponse = async (
|
||||
// Set 2FA enabled so trading post can be used
|
||||
inventoryResponse.HWIDProtectEnabled = true;
|
||||
|
||||
if (buildLabel) {
|
||||
// Fix nemesis for older versions
|
||||
if (inventoryResponse.Nemesis && !isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)) {
|
||||
inventoryResponse.Nemesis = undefined;
|
||||
}
|
||||
// Fix nemesis for older versions
|
||||
if (
|
||||
inventoryResponse.Nemesis &&
|
||||
buildLabel &&
|
||||
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
|
||||
) {
|
||||
inventoryResponse.Nemesis = undefined;
|
||||
}
|
||||
|
||||
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
||||
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||
inventoryResponse.Ship = personalRooms.Ship;
|
||||
|
||||
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
||||
// U19.5 and below use $id instead of $oid
|
||||
for (const category of equipmentKeys) {
|
||||
for (const item of inventoryResponse[category]) {
|
||||
toLegacyOid(item.ItemId);
|
||||
}
|
||||
}
|
||||
for (const upgrade of inventoryResponse.Upgrades) {
|
||||
toLegacyOid(upgrade.ItemId);
|
||||
}
|
||||
if (inventoryResponse.BrandedSuits) {
|
||||
for (const id of inventoryResponse.BrandedSuits) {
|
||||
toLegacyOid(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buildLabel && version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
||||
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||
inventoryResponse.Ship = personalRooms.Ship;
|
||||
}
|
||||
|
||||
return inventoryResponse;
|
||||
|
@ -7,7 +7,7 @@ import { Account } from "@/src/models/loginModel";
|
||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { version_compare } from "@/src/services/worldStateService";
|
||||
|
||||
export const loginController: RequestHandler = async (request, response) => {
|
||||
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||
|
@ -2,7 +2,7 @@ import { RequestHandler } from "express";
|
||||
import { ExportWeapons } from "warframe-public-export-plus";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { CRng } from "@/src/services/rngService";
|
||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import {
|
||||
@ -140,7 +140,7 @@ const getModularWeaponSale = (
|
||||
partTypes: string[],
|
||||
getItemType: (parts: string[]) => string
|
||||
): IModularWeaponSaleInfo => {
|
||||
const rng = new SRng(day);
|
||||
const rng = new CRng(day);
|
||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
||||
let partsCost = 0;
|
||||
for (const part of parts) {
|
||||
|
@ -24,8 +24,7 @@ import {
|
||||
IUpgradeClient,
|
||||
IWeaponSkinClient,
|
||||
LoadoutIndex,
|
||||
TEquipmentKey,
|
||||
TNemesisFaction
|
||||
TEquipmentKey
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
@ -270,7 +269,7 @@ interface INemesisStartRequest {
|
||||
WeaponIdx: number;
|
||||
AgentIdx: number;
|
||||
BirthNode: string;
|
||||
Faction: TNemesisFaction;
|
||||
Faction: string;
|
||||
Rank: number;
|
||||
k: boolean;
|
||||
Traded: boolean;
|
||||
|
@ -2,16 +2,13 @@ import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
|
||||
export const projectionManagerController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
||||
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
||||
const upgradeCost = config.dontSubtractVoidTraces
|
||||
? 0
|
||||
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||
const upgradeCost = (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
|
@ -1,13 +1,8 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { parallelForeach } from "@/src/utils/async-utils";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export const removeFriendGetController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -27,7 +22,7 @@ export const removeFriendGetController: RequestHandler = async (req, res) => {
|
||||
await Promise.all(promises);
|
||||
res.json({
|
||||
Friends: friends
|
||||
} satisfies IRemoveFriendsResponse);
|
||||
});
|
||||
} else {
|
||||
const friendId = req.query.friendId as string;
|
||||
await Promise.all([
|
||||
@ -35,65 +30,7 @@ export const removeFriendGetController: RequestHandler = async (req, res) => {
|
||||
Friendship.deleteOne({ owner: friendId, friend: accountId })
|
||||
]);
|
||||
res.json({
|
||||
Friends: [{ $oid: friendId }]
|
||||
} satisfies IRemoveFriendsResponse);
|
||||
Friends: [{ $oid: friendId } satisfies IOid]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const removeFriendPostController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString<IBatchRemoveFriendsRequest>(String(req.body));
|
||||
const friends = new Set((await Friendship.find({ owner: accountId }, "friend")).map(x => x.friend));
|
||||
// TOVERIFY: Should pending friendships also be kept?
|
||||
|
||||
// Keep friends that have been online within threshold
|
||||
await parallelForeach([...friends], async friend => {
|
||||
const account = (await Account.findById(friend, "LastLogin"))!;
|
||||
const daysLoggedOut = (Date.now() - account.LastLogin.getTime()) / 86400_000;
|
||||
if (daysLoggedOut < data.DaysLoggedOut) {
|
||||
friends.delete(friend);
|
||||
}
|
||||
});
|
||||
|
||||
if (data.SkipClanmates) {
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
await parallelForeach([...friends], async friend => {
|
||||
const friendInventory = await getInventory(friend.toString(), "GuildId");
|
||||
if (friendInventory.GuildId?.equals(inventory.GuildId)) {
|
||||
friends.delete(friend);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all remaining friends that aren't in SkipFriendIds & give response.
|
||||
const promises = [];
|
||||
const response: IOid[] = [];
|
||||
for (const friend of friends) {
|
||||
if (!data.SkipFriendIds.find(skipFriendId => checkFriendId(skipFriendId, friend))) {
|
||||
promises.push(Friendship.deleteOne({ owner: accountId, friend: friend }));
|
||||
promises.push(Friendship.deleteOne({ owner: friend, friend: accountId }));
|
||||
response.push(toOid(friend));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
res.json({
|
||||
Friends: response
|
||||
} satisfies IRemoveFriendsResponse);
|
||||
};
|
||||
|
||||
// The friend ids format is a bit weird, e.g. when 6633b81e9dba0b714f28ff02 (A) is friends with 67cdac105ef1f4b49741c267 (B), A's friend id for B is 808000105ef1f40560ca079e and B's friend id for A is 8000b81e9dba0b06408a8075.
|
||||
const checkFriendId = (friendId: string, b: Types.ObjectId): boolean => {
|
||||
return friendId.substring(6, 6 + 8) == b.toString().substring(6, 6 + 8);
|
||||
};
|
||||
|
||||
interface IBatchRemoveFriendsRequest {
|
||||
DaysLoggedOut: number;
|
||||
SkipClanmates: boolean;
|
||||
SkipFriendIds: string[];
|
||||
}
|
||||
|
||||
interface IRemoveFriendsResponse {
|
||||
Friends: IOid[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const setDojoComponentSettingsController: RequestHandler = async (req, re
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
const component = guild.DojoComponents.id(req.query.componentId as string)!;
|
||||
const component = guild.DojoComponents.id(req.query.componentId)!;
|
||||
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
||||
component.Settings = data.Settings;
|
||||
await guild.save();
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { version_compare } from "@/src/services/worldStateService";
|
||||
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
@ -12,7 +11,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
||||
const request = req.body as IAddXpRequest;
|
||||
for (const [category, gear] of Object.entries(request)) {
|
||||
for (const clientItem of gear) {
|
||||
const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
|
||||
const dbItem = inventory[category as TEquipmentKey].id(clientItem.ItemId.$oid);
|
||||
if (dbItem) {
|
||||
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
||||
if ((dbItem.Polarized ?? 0) < 5) {
|
||||
|
@ -1,46 +1,9 @@
|
||||
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { Types } from "mongoose";
|
||||
import { TRarity } from "warframe-public-export-plus";
|
||||
|
||||
export const version_compare = (a: string, b: string): number => {
|
||||
const a_digits = a
|
||||
.split("/")[0]
|
||||
.split(".")
|
||||
.map(x => parseInt(x));
|
||||
const b_digits = b
|
||||
.split("/")[0]
|
||||
.split(".")
|
||||
.map(x => parseInt(x));
|
||||
for (let i = 0; i != a_digits.length; ++i) {
|
||||
if (a_digits[i] != b_digits[i]) {
|
||||
return a_digits[i] > b_digits[i] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const toOid = (objectId: Types.ObjectId): IOid => {
|
||||
return { $oid: objectId.toString() };
|
||||
};
|
||||
|
||||
export function toOid2(objectId: Types.ObjectId, buildLabel: undefined): IOid;
|
||||
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport;
|
||||
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport {
|
||||
if (buildLabel && version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
||||
return { $id: objectId.toString() };
|
||||
}
|
||||
return { $oid: objectId.toString() };
|
||||
}
|
||||
|
||||
export const toLegacyOid = (oid: IOidWithLegacySupport): void => {
|
||||
if (!("$id" in oid)) {
|
||||
oid.$id = oid.$oid;
|
||||
delete oid.$oid;
|
||||
}
|
||||
};
|
||||
|
||||
export const fromOid = (oid: IOidWithLegacySupport): string => {
|
||||
return (oid.$oid ?? oid.$id)!;
|
||||
return { $oid: objectId.toString() } satisfies IOid;
|
||||
};
|
||||
|
||||
export const toMongoDate = (date: Date): IMongoDate => {
|
||||
|
@ -1,17 +1,16 @@
|
||||
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
||||
import { IInfNode, ITypeCount, TNemesisFaction } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
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, generateRewardSeed } from "../services/inventoryService";
|
||||
import { isArchwingMission } from "../services/worldStateService";
|
||||
import { isArchwingMission, version_compare } from "../services/worldStateService";
|
||||
import { fromStoreItem, toStoreItem } from "../services/itemDataService";
|
||||
import { createMessage } from "../services/inboxService";
|
||||
import { version_compare } from "./inventoryHelpers";
|
||||
|
||||
export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[] => {
|
||||
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
||||
const infNodes = [];
|
||||
const systemIndex = systemIndexes[faction][rank];
|
||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||
@ -35,20 +34,20 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[]
|
||||
return infNodes;
|
||||
};
|
||||
|
||||
const systemIndexes: Record<TNemesisFaction, number[]> = {
|
||||
const systemIndexes: Record<string, number[]> = {
|
||||
FC_GRINEER: [2, 3, 9, 11, 18],
|
||||
FC_CORPUS: [1, 15, 4, 7, 8],
|
||||
FC_INFESTATION: [23]
|
||||
};
|
||||
|
||||
export const showdownNodes: Record<TNemesisFaction, string> = {
|
||||
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: TNemesisFaction }): number[] => {
|
||||
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
|
||||
const rng = new SRng(nemesis.fp);
|
||||
const choices = [0, 1, 2, 3, 5, 6, 7];
|
||||
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
||||
@ -87,7 +86,7 @@ const antivirusMods: readonly string[] = [
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||
];
|
||||
|
||||
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
|
||||
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => {
|
||||
const passcode = getNemesisPasscode(nemesis);
|
||||
return nemesis.Faction == "FC_INFESTATION"
|
||||
? passcode.map(i => antivirusMods[i])
|
||||
@ -248,7 +247,7 @@ export const getWeaponsForManifest = (manifest: string): readonly string[] => {
|
||||
};
|
||||
|
||||
export const isNemesisCompatibleWithVersion = (
|
||||
nemesis: { manifest: string; Faction: TNemesisFaction },
|
||||
nemesis: { manifest: string; Faction: string },
|
||||
buildLabel: string
|
||||
): boolean => {
|
||||
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
||||
|
@ -38,8 +38,7 @@ import {
|
||||
IPeriodicMissionCompletionResponse,
|
||||
ILoreFragmentScan,
|
||||
IEvolutionProgress,
|
||||
IEndlessXpProgressDatabase,
|
||||
IEndlessXpProgressClient,
|
||||
IEndlessXpProgress,
|
||||
ICrewShipCustomization,
|
||||
ICrewShipWeapon,
|
||||
ICrewShipWeaponEmplacements,
|
||||
@ -98,8 +97,7 @@ import {
|
||||
IInvasionProgressClient,
|
||||
IAccolades,
|
||||
IHubNpcCustomization,
|
||||
ILotusCustomization,
|
||||
IEndlessXpReward
|
||||
ILotusCustomization
|
||||
} from "../../types/inventoryTypes/inventoryTypes";
|
||||
import { IOid } from "../../types/commonTypes";
|
||||
import {
|
||||
@ -114,7 +112,6 @@ import {
|
||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
|
||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
||||
|
||||
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
||||
|
||||
@ -813,48 +810,14 @@ const evolutionProgressSchema = new Schema<IEvolutionProgress>(
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const countedStoreItemSchema = new Schema<ICountedStoreItem>(
|
||||
const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
|
||||
{
|
||||
StoreItem: String,
|
||||
ItemCount: Number
|
||||
Category: String,
|
||||
Choices: [String]
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const endlessXpRewardSchema = new Schema<IEndlessXpReward>(
|
||||
{
|
||||
RequiredTotalXp: Number,
|
||||
Rewards: [countedStoreItemSchema]
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const endlessXpProgressSchema = new Schema<IEndlessXpProgressDatabase>(
|
||||
{
|
||||
Category: { type: String, required: true },
|
||||
Earn: { type: Number, default: 0 },
|
||||
Claim: { type: Number, default: 0 },
|
||||
BonusAvailable: Date,
|
||||
Expiry: Date,
|
||||
Choices: { type: [String], required: true },
|
||||
PendingRewards: { type: [endlessXpRewardSchema], default: [] }
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
endlessXpProgressSchema.set("toJSON", {
|
||||
transform(_doc, ret) {
|
||||
const db = ret as IEndlessXpProgressDatabase;
|
||||
const client = ret as IEndlessXpProgressClient;
|
||||
|
||||
if (db.BonusAvailable) {
|
||||
client.BonusAvailable = toMongoDate(db.BonusAvailable);
|
||||
}
|
||||
if (db.Expiry) {
|
||||
client.Expiry = toMongoDate(db.Expiry);
|
||||
}
|
||||
}
|
||||
});
|
||||
const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
|
||||
{
|
||||
PRIMARY_A: EquipmentSelectionSchema,
|
||||
|
@ -103,7 +103,7 @@ import { questControlController } from "@/src/controllers/api/questControlContro
|
||||
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
||||
import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
|
||||
import { releasePetController } from "@/src/controllers/api/releasePetController";
|
||||
import { removeFriendGetController, removeFriendPostController } from "@/src/controllers/api/removeFriendController";
|
||||
import { removeFriendGetController } from "@/src/controllers/api/removeFriendController";
|
||||
import { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
|
||||
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
|
||||
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
||||
@ -187,7 +187,6 @@ apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
|
||||
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
|
||||
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
||||
apiRouter.get("/getShip.php", getShipController);
|
||||
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
|
||||
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
|
||||
apiRouter.get("/hub", hubController);
|
||||
apiRouter.get("/hubInstances", hubInstancesController);
|
||||
@ -291,7 +290,6 @@ apiRouter.post("/purchase.php", purchaseController);
|
||||
apiRouter.post("/questControl.php", questControlController); // U17
|
||||
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
|
||||
apiRouter.post("/releasePet.php", releasePetController);
|
||||
apiRouter.post("/removeFriend.php", removeFriendPostController);
|
||||
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
|
||||
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
|
||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
||||
|
@ -24,8 +24,6 @@ interface IConfig {
|
||||
infiniteEndo?: boolean;
|
||||
infiniteRegalAya?: boolean;
|
||||
infiniteHelminthMaterials?: boolean;
|
||||
claimingBlueprintRefundsIngredients?: boolean;
|
||||
dontSubtractVoidTraces?: boolean;
|
||||
dontSubtractConsumables?: boolean;
|
||||
unlockAllShipFeatures?: boolean;
|
||||
unlockAllShipDecorations?: boolean;
|
||||
|
@ -377,6 +377,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
|
||||
db[key] = client[key];
|
||||
}
|
||||
}
|
||||
if (client.EndlessXP !== undefined) {
|
||||
db.EndlessXP = client.EndlessXP;
|
||||
}
|
||||
if (client.SongChallenges !== undefined) {
|
||||
db.SongChallenges = client.SongChallenges;
|
||||
}
|
||||
|
@ -70,7 +70,6 @@ import { createShip } from "./shipService";
|
||||
import {
|
||||
catbrowDetails,
|
||||
fromMongoDate,
|
||||
fromOid,
|
||||
kubrowDetails,
|
||||
kubrowFurPatternsWeights,
|
||||
kubrowWeights,
|
||||
@ -424,7 +423,7 @@ export const addItem = async (
|
||||
changes.push({
|
||||
ItemType: egg.ItemType,
|
||||
ExpirationDate: { $date: { $numberLong: "2000000000000" } },
|
||||
ItemId: toOid(egg._id) // TODO: Pass on buildLabel from purchaseService
|
||||
ItemId: toOid(egg._id)
|
||||
});
|
||||
}
|
||||
return {
|
||||
@ -870,14 +869,10 @@ const addSentinel = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, ExportSentinels[sentinelName]?.defaultUpgrades);
|
||||
|
||||
const features = premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined;
|
||||
const sentinelIndex =
|
||||
inventory.Sentinels.push({
|
||||
ItemType: sentinelName,
|
||||
Configs: configs,
|
||||
XP: 0,
|
||||
Features: premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined,
|
||||
IsNew: inventory.Sentinels.find(x => x.ItemType == sentinelName) ? undefined : true
|
||||
}) - 1;
|
||||
inventory.Sentinels.push({ ItemType: sentinelName, Configs: configs, XP: 0, Features: features, IsNew: true }) -
|
||||
1;
|
||||
inventoryChanges.Sentinels ??= [];
|
||||
inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
|
||||
|
||||
@ -926,9 +921,6 @@ export const addPowerSuit = async (
|
||||
},
|
||||
defaultOverwrites
|
||||
);
|
||||
if (suit.IsNew) {
|
||||
suit.IsNew = !inventory.Suits.find(x => x.ItemType == powersuitName);
|
||||
}
|
||||
if (!suit.IsNew) {
|
||||
suit.IsNew = undefined;
|
||||
}
|
||||
@ -963,7 +955,7 @@ export const addMechSuit = async (
|
||||
UpgradeVer: 101,
|
||||
XP: 0,
|
||||
Features: features,
|
||||
IsNew: inventory.MechSuits.find(x => x.ItemType == mechsuitName) ? undefined : true
|
||||
IsNew: true
|
||||
}) - 1;
|
||||
inventoryChanges.MechSuits ??= [];
|
||||
inventoryChanges.MechSuits.push(inventory.MechSuits[suitIndex].toJSON<IEquipmentClient>());
|
||||
@ -1003,7 +995,7 @@ export const addSpaceSuit = (
|
||||
UpgradeVer: 101,
|
||||
XP: 0,
|
||||
Features: features,
|
||||
IsNew: inventory.SpaceSuits.find(x => x.ItemType == spacesuitName) ? undefined : true
|
||||
IsNew: true
|
||||
}) - 1;
|
||||
inventoryChanges.SpaceSuits ??= [];
|
||||
inventoryChanges.SpaceSuits.push(inventory.SpaceSuits[suitIndex].toJSON<IEquipmentClient>());
|
||||
@ -1085,7 +1077,7 @@ export const addKubrowPet = (
|
||||
Configs: configs,
|
||||
XP: 0,
|
||||
Details: details,
|
||||
IsNew: inventory.KubrowPets.find(x => x.ItemType == kubrowPetName) ? undefined : true
|
||||
IsNew: true
|
||||
}) - 1;
|
||||
inventoryChanges.KubrowPets ??= [];
|
||||
inventoryChanges.KubrowPets.push(inventory.KubrowPets[kubrowPetIndex].toJSON<IEquipmentClient>());
|
||||
@ -1113,29 +1105,24 @@ const isCurrencyTracked = (usePremium: boolean): boolean => {
|
||||
export const updateCurrency = (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
price: number,
|
||||
usePremium: boolean,
|
||||
inventoryChanges: IInventoryChanges = {}
|
||||
usePremium: boolean
|
||||
): IInventoryChanges => {
|
||||
const currencyChanges: IInventoryChanges = {};
|
||||
if (price != 0 && isCurrencyTracked(usePremium)) {
|
||||
if (usePremium) {
|
||||
if (inventory.PremiumCreditsFree > 0) {
|
||||
const premiumCreditsFreeDelta = Math.min(price, inventory.PremiumCreditsFree) * -1;
|
||||
inventoryChanges.PremiumCreditsFree ??= 0;
|
||||
inventoryChanges.PremiumCreditsFree += premiumCreditsFreeDelta;
|
||||
inventory.PremiumCreditsFree += premiumCreditsFreeDelta;
|
||||
currencyChanges.PremiumCreditsFree = Math.min(price, inventory.PremiumCreditsFree) * -1;
|
||||
inventory.PremiumCreditsFree += currencyChanges.PremiumCreditsFree;
|
||||
}
|
||||
inventoryChanges.PremiumCredits ??= 0;
|
||||
inventoryChanges.PremiumCredits -= price;
|
||||
inventory.PremiumCredits -= price;
|
||||
logger.debug(`currency changes `, { PremiumCredits: -price });
|
||||
currencyChanges.PremiumCredits = -price;
|
||||
inventory.PremiumCredits += currencyChanges.PremiumCredits;
|
||||
} else {
|
||||
inventoryChanges.RegularCredits ??= 0;
|
||||
inventoryChanges.RegularCredits -= price;
|
||||
inventory.RegularCredits -= price;
|
||||
logger.debug(`currency changes `, { RegularCredits: -price });
|
||||
currencyChanges.RegularCredits = -price;
|
||||
inventory.RegularCredits += currencyChanges.RegularCredits;
|
||||
}
|
||||
logger.debug(`currency changes `, currencyChanges);
|
||||
}
|
||||
return inventoryChanges;
|
||||
return currencyChanges;
|
||||
};
|
||||
|
||||
export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
|
||||
@ -1270,9 +1257,6 @@ export const addEquipment = (
|
||||
},
|
||||
defaultOverwrites
|
||||
);
|
||||
if (equipment.IsNew) {
|
||||
equipment.IsNew = !inventory[category].find(x => x.ItemType == type);
|
||||
}
|
||||
if (!equipment.IsNew) {
|
||||
equipment.IsNew = undefined;
|
||||
}
|
||||
@ -1507,9 +1491,9 @@ export const applyClientEquipmentUpdates = (
|
||||
const category = inventory[categoryName];
|
||||
|
||||
gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
|
||||
const item = category.id(fromOid(ItemId));
|
||||
const item = category.id(ItemId.$oid);
|
||||
if (!item) {
|
||||
throw new Error(`No item with id ${fromOid(ItemId)} in ${categoryName}`);
|
||||
throw new Error(`No item with id ${ItemId.$oid} in ${categoryName}`);
|
||||
}
|
||||
|
||||
if (XP) {
|
||||
@ -1585,17 +1569,12 @@ export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray:
|
||||
if (MiscItems[itemIndex].ItemCount == 0) {
|
||||
MiscItems.splice(itemIndex, 1);
|
||||
} else if (MiscItems[itemIndex].ItemCount <= 0) {
|
||||
logger.warn(`inventory.MiscItems has a negative count for ${ItemType}`);
|
||||
logger.warn(`account now owns a negative amount of ${ItemType}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const applyArrayChanges = (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
key: "ShipDecorations" | "Consumables" | "CrewShipRawSalvage" | "CrewShipAmmo" | "Recipes" | "LevelKeys",
|
||||
changes: ITypeCount[]
|
||||
): void => {
|
||||
const arr: ITypeCount[] = inventory[key];
|
||||
const applyArrayChanges = (arr: ITypeCount[], changes: ITypeCount[]): void => {
|
||||
for (const change of changes) {
|
||||
if (change.ItemCount != 0) {
|
||||
let itemIndex = arr.findIndex(x => x.ItemType === change.ItemType);
|
||||
@ -1607,34 +1586,34 @@ const applyArrayChanges = (
|
||||
if (arr[itemIndex].ItemCount == 0) {
|
||||
arr.splice(itemIndex, 1);
|
||||
} else if (arr[itemIndex].ItemCount <= 0) {
|
||||
logger.warn(`inventory.${key} has a negative count for ${change.ItemType}`);
|
||||
logger.warn(`account now owns a negative amount of ${change.ItemType}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const addShipDecorations = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||
applyArrayChanges(inventory, "ShipDecorations", itemsArray);
|
||||
applyArrayChanges(inventory.ShipDecorations, itemsArray);
|
||||
};
|
||||
|
||||
export const addConsumables = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||
applyArrayChanges(inventory, "Consumables", itemsArray);
|
||||
applyArrayChanges(inventory.Consumables, itemsArray);
|
||||
};
|
||||
|
||||
export const addCrewShipRawSalvage = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||
applyArrayChanges(inventory, "CrewShipRawSalvage", itemsArray);
|
||||
applyArrayChanges(inventory.CrewShipRawSalvage, itemsArray);
|
||||
};
|
||||
|
||||
export const addCrewShipAmmo = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||
applyArrayChanges(inventory, "CrewShipAmmo", itemsArray);
|
||||
applyArrayChanges(inventory.CrewShipAmmo, itemsArray);
|
||||
};
|
||||
|
||||
export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||
applyArrayChanges(inventory, "Recipes", itemsArray);
|
||||
applyArrayChanges(inventory.Recipes, itemsArray);
|
||||
};
|
||||
|
||||
export const addLevelKeys = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||
applyArrayChanges(inventory, "LevelKeys", itemsArray);
|
||||
applyArrayChanges(inventory.LevelKeys, itemsArray);
|
||||
};
|
||||
|
||||
export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawUpgrade[]): void => {
|
||||
@ -1654,7 +1633,7 @@ export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawU
|
||||
if (RawUpgrades[itemIndex].ItemCount == 0) {
|
||||
RawUpgrades.splice(itemIndex, 1);
|
||||
} else if (RawUpgrades[itemIndex].ItemCount <= 0) {
|
||||
logger.warn(`inventory.RawUpgrades has a negative count for ${ItemType}`);
|
||||
logger.warn(`account now owns a negative amount of ${ItemType}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -1669,7 +1648,7 @@ export const addFusionTreasures = (inventory: TInventoryDatabaseDocument, itemsA
|
||||
if (FusionTreasures[itemIndex].ItemCount == 0) {
|
||||
FusionTreasures.splice(itemIndex, 1);
|
||||
} else if (FusionTreasures[itemIndex].ItemCount <= 0) {
|
||||
logger.warn(`inventory.FusionTreasures has a negative count for ${ItemType}`);
|
||||
logger.warn(`account now owns a negative amount of ${ItemType}`);
|
||||
}
|
||||
} else {
|
||||
FusionTreasures.push({ ItemCount, ItemType, Sockets });
|
||||
|
@ -1,7 +1,7 @@
|
||||
import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
|
||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
||||
import { TAccountDocument } from "./loginService";
|
||||
import { mixSeeds, SRng } from "./rngService";
|
||||
import { CRng, mixSeeds } from "./rngService";
|
||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||
import { addBooster, updateCurrency } from "./inventoryService";
|
||||
import { handleStoreItemAcquisition } from "./purchaseService";
|
||||
@ -49,8 +49,8 @@ const scaleAmount = (day: number, amount: number, scalingMultiplier: number): nu
|
||||
// Always produces the same result for the same account _id & LoginDays pair.
|
||||
export const isLoginRewardAChoice = (account: TAccountDocument): boolean => {
|
||||
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
||||
const rng = new SRng(mixSeeds(accountSeed, account.LoginDays));
|
||||
return rng.randomFloat() < 0.25;
|
||||
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
|
||||
return rng.random() < 0.25; // Using 25% as an approximate chance for pick-a-doors. More conclusive data analysis is needed.
|
||||
};
|
||||
|
||||
// Always produces the same result for the same account _id & LoginDays pair.
|
||||
@ -59,8 +59,8 @@ export const getRandomLoginRewards = (
|
||||
inventory: TInventoryDatabaseDocument
|
||||
): ILoginReward[] => {
|
||||
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
||||
const rng = new SRng(mixSeeds(accountSeed, account.LoginDays));
|
||||
const pick_a_door = rng.randomFloat() < 0.25;
|
||||
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
|
||||
const pick_a_door = rng.random() < 0.25; // Using 25% as an approximate chance for pick-a-doors. More conclusive data analysis is needed.
|
||||
const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)];
|
||||
if (pick_a_door) {
|
||||
do {
|
||||
@ -73,7 +73,7 @@ export const getRandomLoginRewards = (
|
||||
return rewards;
|
||||
};
|
||||
|
||||
const getRandomLoginReward = (rng: SRng, day: number, inventory: TInventoryDatabaseDocument): ILoginReward => {
|
||||
const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatabaseDocument): ILoginReward => {
|
||||
const reward = rng.randomReward(randomRewards)!;
|
||||
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
|
||||
if (reward.RewardType == "RT_RANDOM_RECIPE") {
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
||||
import { equipmentKeys, IMission, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import {
|
||||
addBooster,
|
||||
addChallenges,
|
||||
@ -62,7 +62,6 @@ import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClass
|
||||
import { config } from "./configService";
|
||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
|
||||
import { fromOid } from "../helpers/inventoryHelpers";
|
||||
|
||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||
@ -400,14 +399,8 @@ export const addMissionInventoryUpdates = async (
|
||||
break;
|
||||
case "Upgrades":
|
||||
value.forEach(clientUpgrade => {
|
||||
const id = fromOid(clientUpgrade.ItemId);
|
||||
if (id == "") {
|
||||
// U19 does not provide RawUpgrades and instead interleaves them with riven progress here
|
||||
addMods(inventory, [clientUpgrade]);
|
||||
} else {
|
||||
const upgrade = inventory.Upgrades.id(id)!;
|
||||
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
|
||||
}
|
||||
const upgrade = inventory.Upgrades.id(clientUpgrade.ItemId.$oid)!;
|
||||
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
|
||||
});
|
||||
break;
|
||||
case "WeaponSkins":
|
||||
@ -631,7 +624,7 @@ export const addMissionInventoryUpdates = async (
|
||||
Rank: inventory.Nemesis.Rank,
|
||||
Traded: inventory.Nemesis.Traded,
|
||||
PrevOwners: inventory.Nemesis.PrevOwners,
|
||||
SecondInCommand: false,
|
||||
SecondInCommand: inventory.Nemesis.SecondInCommand,
|
||||
Weakened: inventory.Nemesis.Weakened,
|
||||
// And set killed flag
|
||||
k: value.killed
|
||||
@ -825,13 +818,6 @@ const hexConquestRewards: IConquestReward[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const droptableAliases: Record<string, string> = {
|
||||
"/Lotus/Types/DropTables/ManInTheWall/MITWGruzzlingArcanesDropTable":
|
||||
"/Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable",
|
||||
"/Lotus/Types/DropTables/WF1999DropTables/LasrianTankSteelPathDropTable":
|
||||
"/Lotus/Types/DropTables/WF1999DropTables/LasrianTankHardModeDropTable"
|
||||
};
|
||||
|
||||
//TODO: return type of partial missioninventoryupdate response
|
||||
export const addMissionRewards = async (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
@ -854,13 +840,7 @@ export const addMissionRewards = async (
|
||||
}
|
||||
|
||||
//TODO: check double reward merging
|
||||
const MissionRewards: IMissionReward[] = getRandomMissionDrops(
|
||||
inventory,
|
||||
rewardInfo,
|
||||
missions,
|
||||
wagerTier,
|
||||
firstCompletion
|
||||
);
|
||||
const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion);
|
||||
logger.debug("random mission drops:", MissionRewards);
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
const AffiliationMods: IAffiliationMods[] = [];
|
||||
@ -1040,9 +1020,11 @@ export const addMissionRewards = async (
|
||||
|
||||
if (strippedItems) {
|
||||
for (const si of strippedItems) {
|
||||
if (si.DropTable in droptableAliases) {
|
||||
logger.debug(`rewriting ${si.DropTable} to ${droptableAliases[si.DropTable]}`);
|
||||
si.DropTable = droptableAliases[si.DropTable];
|
||||
if (si.DropTable == "/Lotus/Types/DropTables/ManInTheWall/MITWGruzzlingArcanesDropTable") {
|
||||
logger.debug(
|
||||
`rewriting ${si.DropTable} to /Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable`
|
||||
);
|
||||
si.DropTable = "/Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable";
|
||||
}
|
||||
const droptables = ExportEnemies.droptables[si.DropTable] ?? [];
|
||||
if (si.DROP_MOD) {
|
||||
@ -1307,7 +1289,6 @@ function getLevelCreditRewards(node: IRegion): number {
|
||||
function getRandomMissionDrops(
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
RewardInfo: IRewardInfo,
|
||||
mission: IMission | undefined,
|
||||
tierOverride: number | undefined,
|
||||
firstCompletion: boolean
|
||||
): IMissionReward[] {
|
||||
@ -1390,16 +1371,20 @@ function getRandomMissionDrops(
|
||||
// TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
|
||||
rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
|
||||
} else if (RewardInfo.sortieId) {
|
||||
// Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes.
|
||||
// Assassinations in non-lite sorties are an exception to this.
|
||||
// Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes. Assassinations are an exception to this.
|
||||
if (region.missionIndex == 0) {
|
||||
const arr = RewardInfo.sortieId.split("_");
|
||||
let giveNodeReward = false;
|
||||
if (arr[1] != "Lite") {
|
||||
const sortie = getSortie(idToDay(arr[1]));
|
||||
giveNodeReward = sortie.Variants.find(x => x.node == arr[0])!.missionType == "MT_ASSASSINATION";
|
||||
let sortieId = arr[1];
|
||||
if (sortieId == "Lite") {
|
||||
sortieId = arr[2];
|
||||
}
|
||||
const sortie = getSortie(idToDay(sortieId));
|
||||
const mission = sortie.Variants.find(x => x.node == arr[0])!;
|
||||
if (mission.missionType == "MT_ASSASSINATION") {
|
||||
rewardManifests = region.rewardManifests;
|
||||
} else {
|
||||
rewardManifests = [];
|
||||
}
|
||||
rewardManifests = giveNodeReward ? region.rewardManifests : [];
|
||||
} else {
|
||||
rewardManifests = [];
|
||||
}
|
||||
@ -1522,7 +1507,7 @@ function getRandomMissionDrops(
|
||||
ZarimanSyndicate: [
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierATableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierBTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierCTableARewards", // [sic]
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierCTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierDTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierETableRewards"
|
||||
],
|
||||
@ -1549,35 +1534,6 @@ function getRandomMissionDrops(
|
||||
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
|
||||
}
|
||||
} else {
|
||||
if (RewardInfo.node == "SolNode238") {
|
||||
// The Circuit
|
||||
const category = mission?.Tier == 1 ? "EXC_HARD" : "EXC_NORMAL";
|
||||
const progress = inventory.EndlessXP?.find(x => x.Category == category);
|
||||
if (progress) {
|
||||
// https://wiki.warframe.com/w/The%20Circuit#Tiers_and_Weekly_Rewards
|
||||
const roundsCompleted = RewardInfo.rewardQualifications?.length || 0;
|
||||
if (roundsCompleted >= 1) {
|
||||
progress.Earn += 100;
|
||||
}
|
||||
if (roundsCompleted >= 2) {
|
||||
progress.Earn += 110;
|
||||
}
|
||||
if (roundsCompleted >= 3) {
|
||||
progress.Earn += 125;
|
||||
}
|
||||
if (roundsCompleted >= 4) {
|
||||
progress.Earn += 145;
|
||||
if (progress.BonusAvailable && progress.BonusAvailable.getTime() <= Date.now()) {
|
||||
progress.Earn += 50;
|
||||
progress.BonusAvailable = new Date(Date.now() + 24 * 3600_000); // TOVERIFY
|
||||
}
|
||||
}
|
||||
if (roundsCompleted >= 5) {
|
||||
progress.Earn += (roundsCompleted - 4) * 170;
|
||||
}
|
||||
}
|
||||
tierOverride = 0;
|
||||
}
|
||||
rotations = getRotations(RewardInfo, tierOverride);
|
||||
}
|
||||
if (rewardManifests.length != 0) {
|
||||
|
@ -86,12 +86,54 @@ export const mixSeeds = (seed1: number, seed2: number): number => {
|
||||
return seed >>> 0;
|
||||
};
|
||||
|
||||
// Seeded RNG with identical results to the game client. Based on work by Donald Knuth.
|
||||
// Seeded RNG for internal usage. Based on recommendations in the ISO C standards.
|
||||
export class CRng {
|
||||
state: number;
|
||||
|
||||
constructor(seed: number = 1) {
|
||||
this.state = seed;
|
||||
}
|
||||
|
||||
random(): number {
|
||||
this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
|
||||
return (this.state & 0x3fffffff) / 0x3fffffff;
|
||||
}
|
||||
|
||||
randomInt(min: number, max: number): number {
|
||||
const diff = max - min;
|
||||
if (diff != 0) {
|
||||
if (diff < 0) {
|
||||
throw new Error(`max must be greater than min`);
|
||||
}
|
||||
if (diff > 0x3fffffff) {
|
||||
throw new Error(`insufficient entropy`);
|
||||
}
|
||||
min += Math.floor(this.random() * (diff + 1));
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
randomElement<T>(arr: readonly T[]): T | undefined {
|
||||
return arr[Math.floor(this.random() * arr.length)];
|
||||
}
|
||||
|
||||
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
||||
return getRewardAtPercentage(pool, this.random());
|
||||
}
|
||||
|
||||
churnSeed(its: number): void {
|
||||
while (its--) {
|
||||
this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth.
|
||||
export class SRng {
|
||||
state: bigint;
|
||||
|
||||
constructor(seed: bigint | number) {
|
||||
this.state = BigInt(seed);
|
||||
constructor(seed: bigint) {
|
||||
this.state = seed;
|
||||
}
|
||||
|
||||
randomInt(min: number, max: number): number {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||
import { mixSeeds, SRng } from "@/src/services/rngService";
|
||||
import { CRng, mixSeeds } from "@/src/services/rngService";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
||||
import { ExportVendors, IRange } from "warframe-public-export-plus";
|
||||
@ -204,7 +204,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
||||
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
||||
const cycleDuration = vendorInfo.cycleDuration;
|
||||
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
|
||||
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
||||
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
|
||||
const manifest = ExportVendors[vendorInfo.TypeName];
|
||||
const offersToAdd = [];
|
||||
if (manifest.numItems && !manifest.isOneBinPerCycle) {
|
||||
@ -247,7 +247,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
||||
$oid:
|
||||
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
||||
vendorInfo._id.$oid.substring(8, 16) +
|
||||
rng.randomInt(0, 0xffff_ffff).toString(16).padStart(8, "0")
|
||||
rng.randomInt(0, 0xffff).toString(16).padStart(4, "0") +
|
||||
rng.randomInt(0, 0xffff).toString(16).padStart(4, "0")
|
||||
}
|
||||
};
|
||||
if (rawItem.numRandomItemPrices) {
|
||||
@ -282,9 +283,9 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
||||
item.PremiumPrice = [value, value];
|
||||
}
|
||||
if (vendorInfo.RandomSeedType) {
|
||||
item.LocTagRandSeed = rng.randomInt(0, 0xffff_ffff);
|
||||
item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
|
||||
if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
|
||||
const highDword = rng.randomInt(0, 0xffff_ffff);
|
||||
const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
|
||||
item.LocTagRandSeed = (BigInt(highDword) << 32n) | (BigInt(item.LocTagRandSeed) & 0xffffffffn);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMiss
|
||||
import { buildConfig } from "@/src/services/buildConfigService";
|
||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { CRng } from "@/src/services/rngService";
|
||||
import { ExportNightwave, ExportRegions, IRegion } from "warframe-public-export-plus";
|
||||
import {
|
||||
ICalendarDay,
|
||||
@ -18,7 +18,6 @@ import {
|
||||
ISyndicateMissionInfo,
|
||||
IWorldState
|
||||
} from "../types/worldStateTypes";
|
||||
import { version_compare } from "../helpers/inventoryHelpers";
|
||||
|
||||
const sortieBosses = [
|
||||
"SORTIE_BOSS_HYENA",
|
||||
@ -193,7 +192,7 @@ const pushSyndicateMissions = (
|
||||
): void => {
|
||||
const nodeOptions: string[] = [...syndicateMissions];
|
||||
|
||||
const rng = new SRng(seed);
|
||||
const rng = new CRng(seed);
|
||||
const nodes: string[] = [];
|
||||
for (let i = 0; i != 6; ++i) {
|
||||
const index = rng.randomInt(0, nodeOptions.length - 1);
|
||||
@ -235,8 +234,8 @@ const pushTilesetModifiers = (modifiers: string[], tileset: TSortieTileset): voi
|
||||
};
|
||||
|
||||
export const getSortie = (day: number): ISortie => {
|
||||
const seed = new SRng(day).randomInt(0, 100_000);
|
||||
const rng = new SRng(seed);
|
||||
const seed = new CRng(day).randomInt(0, 0xffff);
|
||||
const rng = new CRng(seed);
|
||||
|
||||
const boss = rng.randomElement(sortieBosses)!;
|
||||
|
||||
@ -253,10 +252,12 @@ export const getSortie = (day: number): ISortie => {
|
||||
|
||||
const selectedNodes: ISortieMission[] = [];
|
||||
const missionTypes = new Set();
|
||||
const modifierTypes = new Set();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const randomIndex = rng.randomInt(0, nodes.length - 1);
|
||||
const node = nodes[randomIndex];
|
||||
let modifierType: string;
|
||||
|
||||
const modifiers = [
|
||||
"SORTIE_MODIFIER_LOW_ENERGY",
|
||||
@ -284,7 +285,9 @@ export const getSortie = (day: number): ISortie => {
|
||||
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset;
|
||||
pushTilesetModifiers(modifiers, tileset);
|
||||
|
||||
const modifierType = rng.randomElement(modifiers)!;
|
||||
do {
|
||||
modifierType = rng.randomElement(modifiers)!;
|
||||
} while (modifierTypes.has(modifierType));
|
||||
|
||||
selectedNodes.push({
|
||||
missionType: "MT_ASSASSINATION",
|
||||
@ -319,7 +322,9 @@ export const getSortie = (day: number): ISortie => {
|
||||
modifiers.push("SORTIE_MODIFIER_SHIELDS");
|
||||
}
|
||||
|
||||
const modifierType = rng.randomElement(modifiers)!;
|
||||
do {
|
||||
modifierType = rng.randomElement(modifiers)!;
|
||||
} while (modifierTypes.has(modifierType));
|
||||
|
||||
selectedNodes.push({
|
||||
missionType,
|
||||
@ -329,6 +334,7 @@ export const getSortie = (day: number): ISortie => {
|
||||
});
|
||||
nodes.splice(randomIndex, 1);
|
||||
missionTypes.add(missionType);
|
||||
modifierTypes.add(modifierType);
|
||||
}
|
||||
|
||||
const dayStart = getSortieTime(day);
|
||||
@ -351,7 +357,7 @@ const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
||||
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
|
||||
const dayStart = EPOCH + day * 86400000;
|
||||
const dayEnd = EPOCH + (day + 3) * 86400000;
|
||||
const rng = new SRng(new SRng(day).randomInt(0, 100_000));
|
||||
const rng = new CRng(new CRng(day).randomInt(0, 0xffff));
|
||||
return {
|
||||
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
||||
Daily: true,
|
||||
@ -371,7 +377,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
|
||||
const weekStart = EPOCH + week * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
const challengeId = week * 7 + id;
|
||||
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
|
||||
const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
|
||||
return {
|
||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
@ -388,7 +394,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
|
||||
const weekStart = EPOCH + week * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
const challengeId = week * 7 + id;
|
||||
const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
|
||||
const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
|
||||
return {
|
||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
@ -432,12 +438,12 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
||||
|
||||
// TODO: xpAmounts need to be calculated based on the jobType somehow?
|
||||
|
||||
const seed = new SRng(bountyCycle).randomInt(0, 100_000);
|
||||
const seed = new CRng(bountyCycle).randomInt(0, 0xffff);
|
||||
const bountyCycleStart = bountyCycle * 9000000;
|
||||
const bountyCycleEnd = bountyCycleStart + 9000000;
|
||||
|
||||
{
|
||||
const rng = new SRng(seed);
|
||||
const rng = new CRng(seed);
|
||||
syndicateMissions.push({
|
||||
_id: {
|
||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
|
||||
@ -509,7 +515,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
||||
}
|
||||
|
||||
{
|
||||
const rng = new SRng(seed);
|
||||
const rng = new CRng(seed);
|
||||
syndicateMissions.push({
|
||||
_id: {
|
||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
|
||||
@ -581,7 +587,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
|
||||
}
|
||||
|
||||
{
|
||||
const rng = new SRng(seed);
|
||||
const rng = new CRng(seed);
|
||||
syndicateMissions.push({
|
||||
_id: {
|
||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
|
||||
@ -701,7 +707,7 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
|
||||
//logger.debug(`birthday on day ${day}`);
|
||||
eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0
|
||||
}
|
||||
const rng = new SRng(new SRng(week).randomInt(0, 100_000));
|
||||
const rng = new CRng(new CRng(week).randomInt(0, 0xffff));
|
||||
const challenges = [
|
||||
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
|
||||
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
|
||||
@ -982,7 +988,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
||||
}
|
||||
|
||||
// Elite Sanctuary Onslaught cycling every week
|
||||
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new SRng(week).randomInt(0, 0xff_ffff);
|
||||
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new CRng(week).randomInt(0, 0xffff);
|
||||
|
||||
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
|
||||
let bountyCycle = Math.trunc(Date.now() / 9000000);
|
||||
@ -1067,14 +1073,14 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
||||
}
|
||||
|
||||
// The client does not seem to respect activation for classic syndicate missions, so only pushing current ones.
|
||||
const sdy = Date.now() >= rollover ? day : day - 1;
|
||||
const rng = new SRng(sdy);
|
||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48049", "ArbitersSyndicate");
|
||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4804a", "CephalonSudaSyndicate");
|
||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4804e", "NewLokaSyndicate");
|
||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48050", "PerrinSyndicate");
|
||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4805e", "RedVeilSyndicate");
|
||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48061", "SteelMeridianSyndicate");
|
||||
const sday = Date.now() >= rollover ? day : day - 1;
|
||||
const rng = new CRng(sday);
|
||||
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
|
||||
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
|
||||
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
|
||||
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
|
||||
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
|
||||
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
|
||||
}
|
||||
|
||||
// Archon Hunt cycling every week
|
||||
@ -1178,16 +1184,14 @@ export const getLiteSortie = (week: number): ILiteSortie => {
|
||||
value.factionIndex < 2 &&
|
||||
!isArchwingMission(value) &&
|
||||
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
||||
value.missionIndex != 23 && // Exclude junctions
|
||||
value.missionIndex != 28 && // Exclude open worlds
|
||||
value.missionIndex != 32 // Exclude railjack
|
||||
) {
|
||||
nodes.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const seed = new SRng(week).randomInt(0, 100_000);
|
||||
const rng = new SRng(seed);
|
||||
const seed = new CRng(week).randomInt(0, 0xffff);
|
||||
const rng = new CRng(seed);
|
||||
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
|
||||
const firstNode = nodes[firstNodeIndex];
|
||||
nodes.splice(firstNodeIndex, 1);
|
||||
@ -1242,3 +1246,20 @@ export const isArchwingMission = (node: IRegion): boolean => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const version_compare = (a: string, b: string): number => {
|
||||
const a_digits = a
|
||||
.split("/")[0]
|
||||
.split(".")
|
||||
.map(x => parseInt(x));
|
||||
const b_digits = b
|
||||
.split("/")[0]
|
||||
.split(".")
|
||||
.map(x => parseInt(x));
|
||||
for (let i = 0; i != a_digits.length; ++i) {
|
||||
if (a_digits[i] != b_digits[i]) {
|
||||
return a_digits[i] > b_digits[i] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
@ -4,11 +4,6 @@ export interface IOid {
|
||||
$oid: string;
|
||||
}
|
||||
|
||||
export interface IOidWithLegacySupport {
|
||||
$oid?: string;
|
||||
$id?: string;
|
||||
}
|
||||
|
||||
export interface IMongoDate {
|
||||
$date: {
|
||||
$numberLong: string;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
ICrewShipCustomization,
|
||||
@ -92,7 +92,7 @@ export interface IEquipmentClient
|
||||
IEquipmentDatabase,
|
||||
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
|
||||
> {
|
||||
ItemId: IOidWithLegacySupport;
|
||||
ItemId: IOid;
|
||||
InfestationDate?: IMongoDate;
|
||||
Expiry?: IMongoDate;
|
||||
UpgradesExpiry?: IMongoDate;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Types } from "mongoose";
|
||||
import { IOid, IMongoDate, IOidWithLegacySupport } from "../commonTypes";
|
||||
import { IOid, IMongoDate } from "../commonTypes";
|
||||
import {
|
||||
IColor,
|
||||
IItemConfig,
|
||||
@ -12,7 +12,6 @@ import {
|
||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||
import { IOrbiter } from "../personalRoomsTypes";
|
||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
||||
|
||||
export type InventoryDatabaseEquipment = {
|
||||
[_ in TEquipmentKey]: IEquipmentDatabase[];
|
||||
@ -55,7 +54,6 @@ export interface IInventoryDatabase
|
||||
| "CrewMembers"
|
||||
| "QualifyingInvasions"
|
||||
| "LastInventorySync"
|
||||
| "EndlessXP"
|
||||
| TEquipmentKey
|
||||
>,
|
||||
InventoryDatabaseEquipment {
|
||||
@ -94,7 +92,6 @@ export interface IInventoryDatabase
|
||||
CrewMembers: ICrewMemberDatabase[];
|
||||
QualifyingInvasions: IInvasionProgressDatabase[];
|
||||
LastInventorySync?: Types.ObjectId;
|
||||
EndlessXP?: IEndlessXpProgressDatabase[];
|
||||
}
|
||||
|
||||
export interface IQuestKeyDatabase {
|
||||
@ -359,7 +356,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
||||
PendingCoupon?: IPendingCouponClient;
|
||||
Harvestable: boolean;
|
||||
DeathSquadable: boolean;
|
||||
EndlessXP?: IEndlessXpProgressClient[];
|
||||
EndlessXP?: IEndlessXpProgress[];
|
||||
DialogueHistory?: IDialogueHistoryClient;
|
||||
CalendarProgress?: ICalendarProgress;
|
||||
SongChallenges?: ISongChallenge[];
|
||||
@ -374,7 +371,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
||||
EchoesHexConquestCacheScoreMission?: number;
|
||||
EchoesHexConquestActiveFrameVariants?: string[];
|
||||
EchoesHexConquestActiveStickers?: string[];
|
||||
BrandedSuits?: IOidWithLegacySupport[];
|
||||
BrandedSuits?: IOid[];
|
||||
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
||||
HubNpcCustomizations?: IHubNpcCustomization[];
|
||||
Ship?: IOrbiter; // U22 and below, response only
|
||||
@ -535,16 +532,6 @@ export interface IUpgradeDatabase extends Omit<IUpgradeClient, "ItemId"> {
|
||||
_id: Types.ObjectId;
|
||||
}
|
||||
|
||||
export interface IUpgradeFromClient {
|
||||
ItemType: string;
|
||||
ItemId: IOidWithLegacySupport;
|
||||
FromSKU?: boolean;
|
||||
UpgradeFingerprint: string;
|
||||
PendingRerollFingerprint: string;
|
||||
ItemCount: number;
|
||||
LastAdded: IOidWithLegacySupport;
|
||||
}
|
||||
|
||||
export interface ICrewShipMembersClient {
|
||||
SLOT_A?: ICrewShipMemberClient;
|
||||
SLOT_B?: ICrewShipMemberClient;
|
||||
@ -863,8 +850,6 @@ export interface IMission extends IMissionDatabase {
|
||||
RewardsCooldownTime?: IMongoDate;
|
||||
}
|
||||
|
||||
export type TNemesisFaction = "FC_GRINEER" | "FC_CORPUS" | "FC_INFESTATION";
|
||||
|
||||
export interface INemesisBaseClient {
|
||||
fp: bigint | number;
|
||||
manifest: string;
|
||||
@ -874,7 +859,7 @@ export interface INemesisBaseClient {
|
||||
WeaponIdx: number;
|
||||
AgentIdx: number;
|
||||
BirthNode: string;
|
||||
Faction: TNemesisFaction;
|
||||
Faction: string;
|
||||
Rank: number;
|
||||
k: boolean;
|
||||
Traded: boolean;
|
||||
@ -1065,7 +1050,7 @@ export interface IQuestStage {
|
||||
export interface IRawUpgrade {
|
||||
ItemType: string;
|
||||
ItemCount: number;
|
||||
LastAdded?: IOidWithLegacySupport;
|
||||
LastAdded?: IOid;
|
||||
}
|
||||
|
||||
export interface ISeasonChallenge {
|
||||
@ -1158,24 +1143,9 @@ export interface IEvolutionProgress {
|
||||
|
||||
export type TEndlessXpCategory = "EXC_NORMAL" | "EXC_HARD";
|
||||
|
||||
export interface IEndlessXpProgressDatabase {
|
||||
export interface IEndlessXpProgress {
|
||||
Category: TEndlessXpCategory;
|
||||
Earn: number;
|
||||
Claim: number;
|
||||
BonusAvailable?: Date;
|
||||
Expiry?: Date;
|
||||
Choices: string[];
|
||||
PendingRewards: IEndlessXpReward[];
|
||||
}
|
||||
|
||||
export interface IEndlessXpProgressClient extends Omit<IEndlessXpProgressDatabase, "BonusAvailable" | "Expiry"> {
|
||||
BonusAvailable?: IMongoDate;
|
||||
Expiry?: IMongoDate;
|
||||
}
|
||||
|
||||
export interface IEndlessXpReward {
|
||||
RequiredTotalXp: number;
|
||||
Rewards: ICountedStoreItem[];
|
||||
}
|
||||
|
||||
export interface IDialogueHistoryClient {
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
IPlayerSkills,
|
||||
IQuestKeyDatabase,
|
||||
ILoreFragmentScan,
|
||||
IUpgradeFromClient,
|
||||
IUpgradeClient,
|
||||
ICollectibleEntry,
|
||||
IDiscoveredMarker,
|
||||
ILockedWeaponGroupClient,
|
||||
@ -111,7 +111,7 @@ export type IMissionInventoryUpdateRequest = {
|
||||
Standing: number;
|
||||
}[];
|
||||
CollectibleScans?: ICollectibleEntry[];
|
||||
Upgrades?: IUpgradeFromClient[]; // riven challenge progress
|
||||
Upgrades?: IUpgradeClient[]; // riven challenge progress
|
||||
WeaponSkins?: IWeaponSkinClient[];
|
||||
StrippedItems?: {
|
||||
DropTable: string;
|
||||
|
@ -7,9 +7,9 @@
|
||||
<link rel="stylesheet" href="/webui/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg sticky-top bg-body-tertiary">
|
||||
<nav class="navbar navbar-expand sticky-top bg-body-tertiary">
|
||||
<div class="container">
|
||||
<button class="navbar-toggler d-lg-none me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebar" aria-controls="sidebar" aria-label="Toggle sidebar">
|
||||
<button class="navbar-toggler d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebar" aria-controls="sidebar" aria-label="Toggle sidebar">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand">OpenWF WebUI</a>
|
||||
@ -49,7 +49,7 @@
|
||||
<div class="container pt-3 pb-3" id="main-view">
|
||||
<div class="offcanvas-lg offcanvas-start" tabindex="-1" id="sidebar" aria-labelledby="sidebarLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="sidebarLabel">OpenWF WebUI</h5>
|
||||
<h5 class="offcanvas-title" id="sidebarLabel">Sidebar</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
@ -590,18 +590,10 @@
|
||||
<input class="form-check-input" type="checkbox" id="infiniteRegalAya" />
|
||||
<label class="form-check-label" for="infiniteRegalAya" data-loc="cheats_infiniteRegalAya"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="claimingBlueprintRefundsIngredients" />
|
||||
<label class="form-check-label" for="claimingBlueprintRefundsIngredients" data-loc="cheats_claimingBlueprintRefundsIngredients"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
|
||||
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="dontSubtractVoidTraces" />
|
||||
<label class="form-check-label" for="dontSubtractVoidTraces" data-loc="cheats_dontSubtractVoidTraces"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="dontSubtractConsumables" />
|
||||
<label class="form-check-label" for="dontSubtractConsumables" data-loc="cheats_dontSubtractConsumables"></label>
|
||||
|
@ -29,13 +29,3 @@ td.text-end > a > svg {
|
||||
.card-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* fixes for navbar on small resolutions due to not being navbar-expand */
|
||||
.navbar.sticky-top .navbar-nav {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
padding-right: var(--bs-navbar-nav-link-padding-x);
|
||||
padding-left: var(--bs-navbar-nav-link-padding-x);
|
||||
}
|
||||
|
@ -131,8 +131,6 @@ dict = {
|
||||
cheats_infiniteEndo: `Unendlich Endo`,
|
||||
cheats_infiniteRegalAya: `Unendlich Reines Aya`,
|
||||
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
|
||||
cheats_claimingBlueprintRefundsIngredients: `Fertige Blaupausen erstatten Ressourcen zurück`,
|
||||
cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
|
||||
cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
|
||||
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
|
||||
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
|
||||
|
@ -130,8 +130,6 @@ dict = {
|
||||
cheats_infiniteEndo: `Infinite Endo`,
|
||||
cheats_infiniteRegalAya: `Infinite Regal Aya`,
|
||||
cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`,
|
||||
cheats_claimingBlueprintRefundsIngredients: `Claiming Blueprint Refunds Ingredients`,
|
||||
cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`,
|
||||
cheats_dontSubtractConsumables: `Don't Subtract Consumables`,
|
||||
cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
|
||||
cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
|
||||
|
@ -131,8 +131,6 @@ dict = {
|
||||
cheats_infiniteEndo: `Endo infinito`,
|
||||
cheats_infiniteRegalAya: `Aya Real infinita`,
|
||||
cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`,
|
||||
cheats_claimingBlueprintRefundsIngredients: `Reclamar ingredientes devueltos por planos`,
|
||||
cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`,
|
||||
cheats_dontSubtractConsumables: `No restar consumibles`,
|
||||
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
|
||||
cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
|
||||
|
@ -131,8 +131,6 @@ dict = {
|
||||
cheats_infiniteEndo: `Endo infini`,
|
||||
cheats_infiniteRegalAya: `Aya Raffiné infini`,
|
||||
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
|
||||
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`,
|
||||
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`,
|
||||
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
|
||||
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
|
||||
cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
|
||||
|
@ -131,8 +131,6 @@ dict = {
|
||||
cheats_infiniteEndo: `Бесконечное эндо`,
|
||||
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
|
||||
cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`,
|
||||
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`,
|
||||
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`,
|
||||
cheats_dontSubtractConsumables: `Не уменьшать количество расходников`,
|
||||
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
|
||||
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
|
||||
|
@ -131,8 +131,6 @@ dict = {
|
||||
cheats_infiniteEndo: `无限内融核心`,
|
||||
cheats_infiniteRegalAya: `无限御品阿耶`,
|
||||
cheats_infiniteHelminthMaterials: `无限Helminth材料`,
|
||||
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`,
|
||||
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`,
|
||||
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
|
||||
cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
|
||||
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
|
||||
|
Loading…
x
Reference in New Issue
Block a user