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_ENDO=false
|
||||||
ENV APP_INFINITE_REGAL_AYA=false
|
ENV APP_INFINITE_REGAL_AYA=false
|
||||||
ENV APP_INFINITE_HELMINTH_MATERIALS=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_DONT_SUBTRACT_CONSUMABLES=false
|
||||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
||||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
"infiniteEndo": false,
|
"infiniteEndo": false,
|
||||||
"infiniteRegalAya": false,
|
"infiniteRegalAya": false,
|
||||||
"infiniteHelminthMaterials": false,
|
"infiniteHelminthMaterials": false,
|
||||||
"claimingBlueprintRefundsIngredients": false,
|
|
||||||
"dontSubtractVoidTraces": false,
|
|
||||||
"dontSubtractConsumables": false,
|
"dontSubtractConsumables": false,
|
||||||
"unlockAllShipFeatures": false,
|
"unlockAllShipFeatures": false,
|
||||||
"unlockAllShipDecorations": false,
|
"unlockAllShipDecorations": false,
|
||||||
|
@ -21,8 +21,6 @@ services:
|
|||||||
# APP_INFINITE_ENDO: false
|
# APP_INFINITE_ENDO: false
|
||||||
# APP_INFINITE_REGAL_AYA: false
|
# APP_INFINITE_REGAL_AYA: false
|
||||||
# APP_INFINITE_HELMINTH_MATERIALS: false
|
# APP_INFINITE_HELMINTH_MATERIALS: false
|
||||||
# APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false
|
|
||||||
# APP_DONT_SUBTRACT_VOIDTRACES: false
|
|
||||||
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
||||||
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
||||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
||||||
|
10
package-lock.json
generated
10
package-lock.json
generated
@ -13,12 +13,12 @@
|
|||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"crc-32": "^1.2.2",
|
"crc-32": "^1.2.2",
|
||||||
"express": "^5",
|
"express": "^5",
|
||||||
"json-with-bigint": "^3.4.4",
|
"json-with-bigint": "^3.2.2",
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.62",
|
"warframe-public-export-plus": "^0.5.60",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
@ -3703,9 +3703,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/warframe-public-export-plus": {
|
"node_modules/warframe-public-export-plus": {
|
||||||
"version": "0.5.62",
|
"version": "0.5.60",
|
||||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.62.tgz",
|
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.60.tgz",
|
||||||
"integrity": "sha512-D8ZzjkU9rrK/59VqCfpMoV31HVmwHZV1dNZxPO85AOlcjg/G81Fu3kgITQTaw9sdNagLPLQnFaiXY58pxxRwgA=="
|
"integrity": "sha512-vMfytUc4xRi+b7RTSq+TJEl91vwEegpQKxLtXwRPfs9ZHhntxc4rmDYSNWJTvgf/aWXsFUxQlqL/GV5OLPGM7g=="
|
||||||
},
|
},
|
||||||
"node_modules/warframe-riven-info": {
|
"node_modules/warframe-riven-info": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
@ -20,12 +20,12 @@
|
|||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"crc-32": "^1.2.2",
|
"crc-32": "^1.2.2",
|
||||||
"express": "^5",
|
"express": "^5",
|
||||||
"json-with-bigint": "^3.4.4",
|
"json-with-bigint": "^3.2.2",
|
||||||
"mongoose": "^8.11.0",
|
"mongoose": "^8.11.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.62",
|
"warframe-public-export-plus": "^0.5.60",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
|
@ -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 { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
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 { RequestHandler } from "express";
|
||||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawRivenType = getRandomRawRivenType();
|
const rawRivenType = getRandomRawRivenType();
|
||||||
@ -57,8 +57,8 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
|||||||
payload.Consumed.forEach(upgrade => {
|
payload.Consumed.forEach(upgrade => {
|
||||||
const meta = ExportUpgrades[upgrade.ItemType];
|
const meta = ExportUpgrades[upgrade.ItemType];
|
||||||
counts[meta.rarity] += upgrade.ItemCount;
|
counts[meta.rarity] += upgrade.ItemCount;
|
||||||
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||||
} else {
|
} else {
|
||||||
addMods(inventory, [
|
addMods(inventory, [
|
||||||
{
|
{
|
||||||
@ -128,14 +128,24 @@ const getRandomRawRivenType = (): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IArtifactTransmutationRequest {
|
interface IArtifactTransmutationRequest {
|
||||||
Upgrade: IUpgradeFromClient;
|
Upgrade: IAgnosticUpgradeClient;
|
||||||
LevelDiff: number;
|
LevelDiff: number;
|
||||||
Consumed: IUpgradeFromClient[];
|
Consumed: IAgnosticUpgradeClient[];
|
||||||
Cost: number;
|
Cost: number;
|
||||||
FusionPointCost: number;
|
FusionPointCost: number;
|
||||||
RivenTransmute?: boolean;
|
RivenTransmute?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IAgnosticUpgradeClient {
|
||||||
|
ItemType: string;
|
||||||
|
ItemId: IOid;
|
||||||
|
FromSKU: boolean;
|
||||||
|
UpgradeFingerprint: string;
|
||||||
|
PendingRerollFingerprint: string;
|
||||||
|
ItemCount: number;
|
||||||
|
LastAdded: IOid;
|
||||||
|
}
|
||||||
|
|
||||||
const specialModSets: string[][] = [
|
const specialModSets: string[][] = [
|
||||||
[
|
[
|
||||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
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 { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { getAccountForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import {
|
import {
|
||||||
getInventory,
|
getInventory,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
@ -17,11 +17,8 @@ import {
|
|||||||
} from "@/src/services/inventoryService";
|
} from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { IRecipe } from "warframe-public-export-plus";
|
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
interface IClaimCompletedRecipeRequest {
|
interface IClaimCompletedRecipeRequest {
|
||||||
RecipeIds: IOid[];
|
RecipeIds: IOid[];
|
||||||
@ -29,8 +26,10 @@ interface IClaimCompletedRecipeRequest {
|
|||||||
|
|
||||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||||
const account = await getAccountForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(account._id.toString());
|
if (!accountId) throw new Error("no account id");
|
||||||
|
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||||
if (!pendingRecipe) {
|
if (!pendingRecipe) {
|
||||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
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) {
|
if (req.query.cancel) {
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {
|
||||||
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
...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();
|
await inventory.save();
|
||||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||||
|
|
||||||
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
let BrandedSuits: undefined | IOid[];
|
||||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||||
inventory.PendingSpectreLoadouts ??= [];
|
inventory.PendingSpectreLoadouts ??= [];
|
||||||
inventory.SpectreLoadouts ??= [];
|
inventory.SpectreLoadouts ??= [];
|
||||||
@ -81,7 +106,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
BrandedSuits = [toOid(pendingRecipe.SuitToUnbrand!)];
|
||||||
}
|
}
|
||||||
|
|
||||||
let InventoryChanges: IInventoryChanges = {};
|
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();
|
await inventory.save();
|
||||||
res.json({ InventoryChanges, BrandedSuits });
|
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
@ -8,15 +7,8 @@ import { Types } from "mongoose";
|
|||||||
|
|
||||||
export const crewMembersController: RequestHandler = async (req, res) => {
|
export const crewMembersController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory");
|
const inventory = await getInventory(accountId, "CrewMembers");
|
||||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||||
if (data.crewMember.SecondInCommand) {
|
|
||||||
clearOnCall(inventory);
|
|
||||||
}
|
|
||||||
if (data.crewMember.ItemId.$oid == "000000000000000000000000") {
|
|
||||||
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!;
|
|
||||||
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand;
|
|
||||||
} else {
|
|
||||||
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
||||||
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
||||||
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
||||||
@ -24,7 +16,6 @@ export const crewMembersController: RequestHandler = async (req, res) => {
|
|||||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||||
dbCrewMember.Configs = data.crewMember.Configs;
|
dbCrewMember.Configs = data.crewMember.Configs;
|
||||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
||||||
}
|
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
res.json({
|
res.json({
|
||||||
crewMemberId: data.crewMember.ItemId.$oid,
|
crewMemberId: data.crewMember.ItemId.$oid,
|
||||||
@ -35,20 +26,3 @@ export const crewMembersController: RequestHandler = async (req, res) => {
|
|||||||
interface ICrewMembersRequest {
|
interface ICrewMembersRequest {
|
||||||
crewMember: ICrewMemberClient;
|
crewMember: ICrewMemberClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearOnCall = (inventory: TInventoryDatabaseDocument): void => {
|
|
||||||
for (const cm of inventory.CrewMembers) {
|
|
||||||
if (cm.SecondInCommand) {
|
|
||||||
cm.SecondInCommand = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inventory.NemesisHistory) {
|
|
||||||
for (const cm of inventory.NemesisHistory) {
|
|
||||||
if (cm.SecondInCommand) {
|
|
||||||
cm.SecondInCommand = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,529 +1,60 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
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 { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import { IEndlessXpReward, IInventoryClient, TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { 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";
|
|
||||||
|
|
||||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId);
|
||||||
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||||
if (payload.Mode == "r") {
|
|
||||||
const inventory = await getInventory(accountId, "EndlessXP");
|
|
||||||
inventory.EndlessXP ??= [];
|
inventory.EndlessXP ??= [];
|
||||||
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||||
if (!entry) {
|
if (entry) {
|
||||||
entry = {
|
entry.Choices = payload.Choices;
|
||||||
|
} else {
|
||||||
|
inventory.EndlessXP.push({
|
||||||
|
Category: payload.Category,
|
||||||
|
Choices: payload.Choices
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await inventory.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
NewProgress: {
|
||||||
Category: payload.Category,
|
Category: payload.Category,
|
||||||
Earn: 0,
|
Earn: 0,
|
||||||
Claim: 0,
|
Claim: 0,
|
||||||
|
BonusAvailable: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: "9999999999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expiry: {
|
||||||
|
$date: {
|
||||||
|
$numberLong: "9999999999999"
|
||||||
|
}
|
||||||
|
},
|
||||||
Choices: payload.Choices,
|
Choices: payload.Choices,
|
||||||
PendingRewards: []
|
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);
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
RequiredTotalXp: 190,
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 400,
|
|
||||||
Rewards: [
|
Rewards: [
|
||||||
{
|
{
|
||||||
StoreItem: choiceRewards[0],
|
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||||
ItemCount: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1540,
|
|
||||||
Rewards: generateRandomRewards(
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
RequiredTotalXp: 1950,
|
|
||||||
Rewards: [
|
|
||||||
{
|
|
||||||
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
|
ItemCount: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
// ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hardModeChosenRewards: Record<string, string> = {
|
interface IEndlessXpRequest {
|
||||||
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
Mode: string; // "r"
|
||||||
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
Category: TEndlessXpCategory;
|
||||||
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
Choices: string[];
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
@ -3,6 +3,7 @@ import { config } from "@/src/services/configService";
|
|||||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
|
import { getShip } from "@/src/services/shipService";
|
||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
@ -20,6 +21,7 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||||
const loadout = await getLoadout(accountId);
|
const loadout = await getLoadout(accountId);
|
||||||
|
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||||
|
|
||||||
const getShipResponse: IGetShipResponse = {
|
const getShipResponse: IGetShipResponse = {
|
||||||
ShipOwnerId: accountId,
|
ShipOwnerId: accountId,
|
||||||
@ -29,8 +31,8 @@ export const getShipController: RequestHandler = async (req, res) => {
|
|||||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||||
ShipInterior: {
|
ShipInterior: {
|
||||||
Colors: personalRooms.ShipInteriorColors,
|
Colors: personalRooms.ShipInteriorColors,
|
||||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
ShipAttachments: ship.ShipAttachments,
|
||||||
SkinFlavourItem: ""
|
SkinFlavourItem: ship.SkinFlavourItem
|
||||||
},
|
},
|
||||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||||
|
@ -2,13 +2,12 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|||||||
import { Account } from "@/src/models/loginModel";
|
import { Account } from "@/src/models/loginModel";
|
||||||
import { areFriends } from "@/src/services/friendService";
|
import { areFriends } from "@/src/services/friendService";
|
||||||
import { createMessage } from "@/src/services/inboxService";
|
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 { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
|
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
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) => {
|
export const giftingController: RequestHandler = async (req, res) => {
|
||||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
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 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)
|
// 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) {
|
if (senderInventory.GiftsRemaining == 0) {
|
||||||
res.status(400).send("10").end();
|
res.status(400).send("10").end();
|
||||||
@ -53,20 +55,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
senderInventory.GiftsRemaining -= 1;
|
senderInventory.GiftsRemaining -= 1;
|
||||||
|
|
||||||
const inventoryChanges: IInventoryChanges = updateCurrency(
|
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await senderInventory.save();
|
await senderInventory.save();
|
||||||
|
|
||||||
const senderName = getSuffixedName(senderAccount);
|
const senderName = getSuffixedName(senderAccount);
|
||||||
@ -94,9 +83,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.json({
|
res.end();
|
||||||
InventoryChanges: inventoryChanges
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IGiftingRequest {
|
interface IGiftingRequest {
|
||||||
|
@ -104,7 +104,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
) {
|
) {
|
||||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
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(
|
throw new Error(
|
||||||
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
`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 { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
|
import { isNemesisCompatibleWithVersion } from "@/src/helpers/nemesisHelpers";
|
||||||
|
import { version_compare } from "@/src/services/worldStateService";
|
||||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||||
import { Ship } from "@/src/models/shipModel";
|
import { Ship } from "@/src/models/shipModel";
|
||||||
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
export const inventoryController: RequestHandler = async (request, response) => {
|
export const inventoryController: RequestHandler = async (request, response) => {
|
||||||
const account = await getAccountForRequest(request);
|
const account = await getAccountForRequest(request);
|
||||||
@ -306,34 +306,19 @@ export const getInventoryResponse = async (
|
|||||||
// Set 2FA enabled so trading post can be used
|
// Set 2FA enabled so trading post can be used
|
||||||
inventoryResponse.HWIDProtectEnabled = true;
|
inventoryResponse.HWIDProtectEnabled = true;
|
||||||
|
|
||||||
if (buildLabel) {
|
|
||||||
// Fix nemesis for older versions
|
// Fix nemesis for older versions
|
||||||
if (inventoryResponse.Nemesis && !isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)) {
|
if (
|
||||||
|
inventoryResponse.Nemesis &&
|
||||||
|
buildLabel &&
|
||||||
|
!isNemesisCompatibleWithVersion(inventoryResponse.Nemesis, buildLabel)
|
||||||
|
) {
|
||||||
inventoryResponse.Nemesis = undefined;
|
inventoryResponse.Nemesis = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
if (buildLabel && version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
||||||
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
||||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||||
inventoryResponse.Ship = personalRooms.Ship;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return inventoryResponse;
|
return inventoryResponse;
|
||||||
|
@ -7,7 +7,7 @@ import { Account } from "@/src/models/loginModel";
|
|||||||
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
|
||||||
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
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) => {
|
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
|
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
|
||||||
|
@ -61,11 +61,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
||||||
!(
|
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
|
||||||
missionReport.RewardInfo?.jobId ||
|
|
||||||
missionReport.RewardInfo?.challengeMissionId ||
|
|
||||||
missionReport.RewardInfo?.T
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
if (missionReport.EndOfMatchUpload) {
|
if (missionReport.EndOfMatchUpload) {
|
||||||
inventory.RewardSeed = generateRewardSeed();
|
inventory.RewardSeed = generateRewardSeed();
|
||||||
|
@ -2,7 +2,7 @@ import { RequestHandler } from "express";
|
|||||||
import { ExportWeapons } from "warframe-public-export-plus";
|
import { ExportWeapons } from "warframe-public-export-plus";
|
||||||
import { IMongoDate } from "@/src/types/commonTypes";
|
import { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
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 { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
import {
|
import {
|
||||||
@ -140,7 +140,7 @@ const getModularWeaponSale = (
|
|||||||
partTypes: string[],
|
partTypes: string[],
|
||||||
getItemType: (parts: string[]) => string
|
getItemType: (parts: string[]) => string
|
||||||
): IModularWeaponSaleInfo => {
|
): IModularWeaponSaleInfo => {
|
||||||
const rng = new SRng(day);
|
const rng = new CRng(day);
|
||||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
||||||
let partsCost = 0;
|
let partsCost = 0;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
@ -24,8 +24,7 @@ import {
|
|||||||
IUpgradeClient,
|
IUpgradeClient,
|
||||||
IWeaponSkinClient,
|
IWeaponSkinClient,
|
||||||
LoadoutIndex,
|
LoadoutIndex,
|
||||||
TEquipmentKey,
|
TEquipmentKey
|
||||||
TNemesisFaction
|
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -270,7 +269,7 @@ interface INemesisStartRequest {
|
|||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: TNemesisFaction;
|
Faction: string;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
|
@ -2,16 +2,13 @@ import { RequestHandler } from "express";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||||
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
||||||
import { config } from "@/src/services/configService";
|
|
||||||
|
|
||||||
export const projectionManagerController: RequestHandler = async (req, res) => {
|
export const projectionManagerController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
||||||
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
||||||
const upgradeCost = config.dontSubtractVoidTraces
|
const upgradeCost = (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||||
? 0
|
|
||||||
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
|
||||||
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
||||||
addMiscItems(inventory, [
|
addMiscItems(inventory, [
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
import { Friendship } from "@/src/models/friendModel";
|
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 { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
import { IOid } from "@/src/types/commonTypes";
|
||||||
import { parallelForeach } from "@/src/utils/async-utils";
|
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { Types } from "mongoose";
|
|
||||||
|
|
||||||
export const removeFriendGetController: RequestHandler = async (req, res) => {
|
export const removeFriendGetController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
@ -27,7 +22,7 @@ export const removeFriendGetController: RequestHandler = async (req, res) => {
|
|||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
res.json({
|
res.json({
|
||||||
Friends: friends
|
Friends: friends
|
||||||
} satisfies IRemoveFriendsResponse);
|
});
|
||||||
} else {
|
} else {
|
||||||
const friendId = req.query.friendId as string;
|
const friendId = req.query.friendId as string;
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -35,65 +30,7 @@ export const removeFriendGetController: RequestHandler = async (req, res) => {
|
|||||||
Friendship.deleteOne({ owner: friendId, friend: accountId })
|
Friendship.deleteOne({ owner: friendId, friend: accountId })
|
||||||
]);
|
]);
|
||||||
res.json({
|
res.json({
|
||||||
Friends: [{ $oid: friendId }]
|
Friends: [{ $oid: friendId } satisfies IOid]
|
||||||
} satisfies IRemoveFriendsResponse);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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[];
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { config } from "@/src/services/configService";
|
import { config } from "@/src/services/configService";
|
||||||
import { addEmailItem, getDialogue, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
@ -106,3 +107,26 @@ interface IOtherDialogueInfo {
|
|||||||
Tag: string;
|
Tag: string;
|
||||||
Value: number;
|
Value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
|
||||||
|
let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == dialogueName);
|
||||||
|
if (!dialogue) {
|
||||||
|
dialogue =
|
||||||
|
inventory.DialogueHistory!.Dialogues![
|
||||||
|
inventory.DialogueHistory!.Dialogues!.push({
|
||||||
|
Rank: 0,
|
||||||
|
Chemistry: 0,
|
||||||
|
AvailableDate: new Date(0),
|
||||||
|
AvailableGiftDate: new Date(0),
|
||||||
|
RankUpExpiry: new Date(0),
|
||||||
|
BountyChemExpiry: new Date(0),
|
||||||
|
QueuedDialogues: [],
|
||||||
|
Gifts: [],
|
||||||
|
Booleans: [],
|
||||||
|
Completed: [],
|
||||||
|
DialogueName: dialogueName
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return dialogue;
|
||||||
|
};
|
||||||
|
@ -2,12 +2,11 @@ import { RequestHandler } from "express";
|
|||||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
||||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|
||||||
|
|
||||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
|
||||||
const body: ISaveLoadoutRequest = getJSONfromString<ISaveLoadoutRequest>(String(req.body));
|
const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
|
||||||
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
@ -13,7 +13,7 @@ export const setDojoComponentSettingsController: RequestHandler = async (req, re
|
|||||||
res.json({ DojoRequestStatus: -1 });
|
res.json({ DojoRequestStatus: -1 });
|
||||||
return;
|
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));
|
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
||||||
component.Settings = data.Settings;
|
component.Settings = data.Settings;
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
|
||||||
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
||||||
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
||||||
import { getInventory } from "@/src/services/inventoryService";
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||||
|
import { version_compare } from "@/src/services/worldStateService";
|
||||||
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
import { GuildPermission, ILongMOTD } from "@/src/types/guildTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { IOid } from "@/src/types/commonTypes";
|
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
@ -12,7 +11,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
|||||||
const request = req.body as IAddXpRequest;
|
const request = req.body as IAddXpRequest;
|
||||||
for (const [category, gear] of Object.entries(request)) {
|
for (const [category, gear] of Object.entries(request)) {
|
||||||
for (const clientItem of gear) {
|
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) {
|
||||||
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
||||||
if ((dbItem.Polarized ?? 0) < 5) {
|
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 { Types } from "mongoose";
|
||||||
import { TRarity } from "warframe-public-export-plus";
|
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 => {
|
export const toOid = (objectId: Types.ObjectId): IOid => {
|
||||||
return { $oid: objectId.toString() };
|
return { $oid: objectId.toString() } satisfies IOid;
|
||||||
};
|
|
||||||
|
|
||||||
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)!;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toMongoDate = (date: Date): IMongoDate => {
|
export const toMongoDate = (date: Date): IMongoDate => {
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
|
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 { getRewardAtPercentage, SRng } from "@/src/services/rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { IOid } from "../types/commonTypes";
|
import { IOid } from "../types/commonTypes";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { addMods, generateRewardSeed } from "../services/inventoryService";
|
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 { fromStoreItem, toStoreItem } from "../services/itemDataService";
|
||||||
import { createMessage } from "../services/inboxService";
|
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 infNodes = [];
|
||||||
const systemIndex = systemIndexes[faction][rank];
|
const systemIndex = systemIndexes[faction][rank];
|
||||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||||
@ -35,56 +34,20 @@ export const getInfNodes = (faction: TNemesisFaction, rank: number): IInfNode[]
|
|||||||
return infNodes;
|
return infNodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const systemIndexes: Record<TNemesisFaction, number[]> = {
|
const systemIndexes: Record<string, number[]> = {
|
||||||
FC_GRINEER: [2, 3, 9, 11, 18],
|
FC_GRINEER: [2, 3, 9, 11, 18],
|
||||||
FC_CORPUS: [1, 15, 4, 7, 8],
|
FC_CORPUS: [1, 15, 4, 7, 8],
|
||||||
FC_INFESTATION: [23]
|
FC_INFESTATION: [23]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showdownNodes: Record<TNemesisFaction, string> = {
|
export const showdownNodes: Record<string, string> = {
|
||||||
FC_GRINEER: "CrewBattleNode557",
|
FC_GRINEER: "CrewBattleNode557",
|
||||||
FC_CORPUS: "CrewBattleNode558",
|
FC_CORPUS: "CrewBattleNode558",
|
||||||
FC_INFESTATION: "CrewBattleNode559"
|
FC_INFESTATION: "CrewBattleNode559"
|
||||||
};
|
};
|
||||||
|
|
||||||
const ephemeraProbabilities: Record<TNemesisFaction, number> = {
|
|
||||||
FC_GRINEER: 0.05,
|
|
||||||
FC_CORPUS: 0.2,
|
|
||||||
FC_INFESTATION: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
type TInnateDamageTag =
|
|
||||||
| "InnateElectricityDamage"
|
|
||||||
| "InnateHeatDamage"
|
|
||||||
| "InnateFreezeDamage"
|
|
||||||
| "InnateToxinDamage"
|
|
||||||
| "InnateMagDamage"
|
|
||||||
| "InnateRadDamage"
|
|
||||||
| "InnateImpactDamage";
|
|
||||||
|
|
||||||
const ephmeraTypes: Record<"FC_GRINEER" | "FC_CORPUS", Record<TInnateDamageTag, string>> = {
|
|
||||||
FC_GRINEER: {
|
|
||||||
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaLightningEphemera",
|
|
||||||
InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaFireEphemera",
|
|
||||||
InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaIceEphemera",
|
|
||||||
InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaToxinEphemera",
|
|
||||||
InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaMagneticEphemera",
|
|
||||||
InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaTricksterEphemera",
|
|
||||||
InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/Kuva/KuvaImpactEphemera"
|
|
||||||
},
|
|
||||||
FC_CORPUS: {
|
|
||||||
InnateElectricityDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraA",
|
|
||||||
InnateHeatDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraB",
|
|
||||||
InnateFreezeDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraC",
|
|
||||||
InnateToxinDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraD",
|
|
||||||
InnateMagDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraE",
|
|
||||||
InnateRadDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraF",
|
|
||||||
InnateImpactDamage: "/Lotus/Upgrades/Skins/Effects/CorpusLichEphemeraG"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
|
// 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 rng = new SRng(nemesis.fp);
|
||||||
const choices = [0, 1, 2, 3, 5, 6, 7];
|
const choices = [0, 1, 2, 3, 5, 6, 7];
|
||||||
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
let choiceIndex = rng.randomInt(0, choices.length - 1);
|
||||||
@ -123,7 +86,7 @@ const antivirusMods: readonly string[] = [
|
|||||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
"/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);
|
const passcode = getNemesisPasscode(nemesis);
|
||||||
return nemesis.Faction == "FC_INFESTATION"
|
return nemesis.Faction == "FC_INFESTATION"
|
||||||
? passcode.map(i => antivirusMods[i])
|
? passcode.map(i => antivirusMods[i])
|
||||||
@ -284,7 +247,7 @@ export const getWeaponsForManifest = (manifest: string): readonly string[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isNemesisCompatibleWithVersion = (
|
export const isNemesisCompatibleWithVersion = (
|
||||||
nemesis: { manifest: string; Faction: TNemesisFaction },
|
nemesis: { manifest: string; Faction: string },
|
||||||
buildLabel: string
|
buildLabel: string
|
||||||
): boolean => {
|
): boolean => {
|
||||||
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
// Anything below 35.6.0 is not going to be okay given our set of supported manifests.
|
||||||
@ -307,25 +270,21 @@ export const isNemesisCompatibleWithVersion = (
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInnateDamageTag = (KillingSuit: string): TInnateDamageTag => {
|
export const getInnateDamageTag = (
|
||||||
|
KillingSuit: string
|
||||||
|
):
|
||||||
|
| "InnateElectricityDamage"
|
||||||
|
| "InnateFreezeDamage"
|
||||||
|
| "InnateHeatDamage"
|
||||||
|
| "InnateImpactDamage"
|
||||||
|
| "InnateMagDamage"
|
||||||
|
| "InnateRadDamage"
|
||||||
|
| "InnateToxinDamage" => {
|
||||||
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
return ExportWarframes[KillingSuit].nemesisUpgradeTag!;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface INemesisProfile {
|
// TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
||||||
innateDamageTag: TInnateDamageTag;
|
export const getInnateDamageValue = (fp: bigint): number => {
|
||||||
innateDamageValue: number;
|
|
||||||
ephemera?: string;
|
|
||||||
petHead?: string;
|
|
||||||
petBody?: string;
|
|
||||||
petLegs?: string;
|
|
||||||
petTail?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateNemesisProfile = (
|
|
||||||
fp: bigint = generateRewardSeed(),
|
|
||||||
Faction: TNemesisFaction = "FC_CORPUS",
|
|
||||||
killingSuit: string = "/Lotus/Powersuits/Ember/Ember"
|
|
||||||
): INemesisProfile => {
|
|
||||||
const rng = new SRng(fp);
|
const rng = new SRng(fp);
|
||||||
rng.randomFloat(); // used for the weapon index
|
rng.randomFloat(); // used for the weapon index
|
||||||
const WeaponUpgradeValueAttenuationExponent = 2.25;
|
const WeaponUpgradeValueAttenuationExponent = 2.25;
|
||||||
@ -333,37 +292,7 @@ export const generateNemesisProfile = (
|
|||||||
if (value >= 0.941428) {
|
if (value >= 0.941428) {
|
||||||
value = 1;
|
value = 1;
|
||||||
}
|
}
|
||||||
const profile: INemesisProfile = {
|
return Math.trunc(value * 0x40000000);
|
||||||
innateDamageTag: getInnateDamageTag(killingSuit),
|
|
||||||
innateDamageValue: Math.trunc(value * 0x40000000) // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003.
|
|
||||||
};
|
|
||||||
if (rng.randomFloat() <= ephemeraProbabilities[Faction] && Faction != "FC_INFESTATION") {
|
|
||||||
profile.ephemera = ephmeraTypes[Faction][profile.innateDamageTag];
|
|
||||||
}
|
|
||||||
rng.randomFloat(); // something related to sentinel agent maybe
|
|
||||||
if (Faction == "FC_CORPUS") {
|
|
||||||
profile.petHead = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
|
|
||||||
])!;
|
|
||||||
profile.petBody = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
|
|
||||||
])!;
|
|
||||||
profile.petLegs = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
|
|
||||||
])!;
|
|
||||||
profile.petTail = rng.randomElement([
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
|
|
||||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
|
|
||||||
])!;
|
|
||||||
}
|
|
||||||
return profile;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getKillTokenRewardCount = (fp: bigint): number => {
|
export const getKillTokenRewardCount = (fp: bigint): number => {
|
||||||
|
@ -38,8 +38,7 @@ import {
|
|||||||
IPeriodicMissionCompletionResponse,
|
IPeriodicMissionCompletionResponse,
|
||||||
ILoreFragmentScan,
|
ILoreFragmentScan,
|
||||||
IEvolutionProgress,
|
IEvolutionProgress,
|
||||||
IEndlessXpProgressDatabase,
|
IEndlessXpProgress,
|
||||||
IEndlessXpProgressClient,
|
|
||||||
ICrewShipCustomization,
|
ICrewShipCustomization,
|
||||||
ICrewShipWeapon,
|
ICrewShipWeapon,
|
||||||
ICrewShipWeaponEmplacements,
|
ICrewShipWeaponEmplacements,
|
||||||
@ -98,8 +97,7 @@ import {
|
|||||||
IInvasionProgressClient,
|
IInvasionProgressClient,
|
||||||
IAccolades,
|
IAccolades,
|
||||||
IHubNpcCustomization,
|
IHubNpcCustomization,
|
||||||
ILotusCustomization,
|
ILotusCustomization
|
||||||
IEndlessXpReward
|
|
||||||
} from "../../types/inventoryTypes/inventoryTypes";
|
} from "../../types/inventoryTypes/inventoryTypes";
|
||||||
import { IOid } from "../../types/commonTypes";
|
import { IOid } from "../../types/commonTypes";
|
||||||
import {
|
import {
|
||||||
@ -114,7 +112,6 @@ import {
|
|||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||||
import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
|
import { EquipmentSelectionSchema, oidSchema } from "./loadoutModel";
|
||||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
|
||||||
|
|
||||||
@ -813,48 +810,14 @@ const evolutionProgressSchema = new Schema<IEvolutionProgress>(
|
|||||||
{ _id: false }
|
{ _id: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const countedStoreItemSchema = new Schema<ICountedStoreItem>(
|
const endlessXpProgressSchema = new Schema<IEndlessXpProgress>(
|
||||||
{
|
{
|
||||||
StoreItem: String,
|
Category: String,
|
||||||
ItemCount: Number
|
Choices: [String]
|
||||||
},
|
},
|
||||||
{ _id: false }
|
{ _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>(
|
const crewShipWeaponEmplacementsSchema = new Schema<ICrewShipWeaponEmplacements>(
|
||||||
{
|
{
|
||||||
PRIMARY_A: EquipmentSelectionSchema,
|
PRIMARY_A: EquipmentSelectionSchema,
|
||||||
@ -1318,7 +1281,7 @@ const nemesisSchema = new Schema<INemesisDatabase>(
|
|||||||
InfNodes: { type: [infNodeSchema], default: undefined },
|
InfNodes: { type: [infNodeSchema], default: undefined },
|
||||||
HenchmenKilled: Number,
|
HenchmenKilled: Number,
|
||||||
HintProgress: Number,
|
HintProgress: Number,
|
||||||
Hints: { type: [Number], default: [] },
|
Hints: { type: [Number], default: undefined },
|
||||||
GuessHistory: { type: [Number], default: undefined },
|
GuessHistory: { type: [Number], default: undefined },
|
||||||
MissionCount: Number,
|
MissionCount: Number,
|
||||||
LastEnc: Number
|
LastEnc: Number
|
||||||
|
@ -103,7 +103,7 @@ import { questControlController } from "@/src/controllers/api/questControlContro
|
|||||||
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
|
||||||
import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
|
import { redeemPromoCodeController } from "@/src/controllers/api/redeemPromoCodeController";
|
||||||
import { releasePetController } from "@/src/controllers/api/releasePetController";
|
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 { removeFromAllianceController } from "@/src/controllers/api/removeFromAllianceController";
|
||||||
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
|
import { removeFromGuildController } from "@/src/controllers/api/removeFromGuildController";
|
||||||
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
|
||||||
@ -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("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
|
||||||
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
||||||
apiRouter.get("/getShip.php", getShipController);
|
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("/getVendorInfo.php", getVendorInfoController);
|
||||||
apiRouter.get("/hub", hubController);
|
apiRouter.get("/hub", hubController);
|
||||||
apiRouter.get("/hubInstances", hubInstancesController);
|
apiRouter.get("/hubInstances", hubInstancesController);
|
||||||
@ -291,7 +290,6 @@ apiRouter.post("/purchase.php", purchaseController);
|
|||||||
apiRouter.post("/questControl.php", questControlController); // U17
|
apiRouter.post("/questControl.php", questControlController); // U17
|
||||||
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
|
apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
|
||||||
apiRouter.post("/releasePet.php", releasePetController);
|
apiRouter.post("/releasePet.php", releasePetController);
|
||||||
apiRouter.post("/removeFriend.php", removeFriendPostController);
|
|
||||||
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
|
apiRouter.post("/removeFromGuild.php", removeFromGuildController);
|
||||||
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
|
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
|
||||||
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
|
||||||
|
@ -24,8 +24,6 @@ interface IConfig {
|
|||||||
infiniteEndo?: boolean;
|
infiniteEndo?: boolean;
|
||||||
infiniteRegalAya?: boolean;
|
infiniteRegalAya?: boolean;
|
||||||
infiniteHelminthMaterials?: boolean;
|
infiniteHelminthMaterials?: boolean;
|
||||||
claimingBlueprintRefundsIngredients?: boolean;
|
|
||||||
dontSubtractVoidTraces?: boolean;
|
|
||||||
dontSubtractConsumables?: boolean;
|
dontSubtractConsumables?: boolean;
|
||||||
unlockAllShipFeatures?: boolean;
|
unlockAllShipFeatures?: boolean;
|
||||||
unlockAllShipDecorations?: boolean;
|
unlockAllShipDecorations?: boolean;
|
||||||
|
@ -377,6 +377,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
|
|||||||
db[key] = client[key];
|
db[key] = client[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (client.EndlessXP !== undefined) {
|
||||||
|
db.EndlessXP = client.EndlessXP;
|
||||||
|
}
|
||||||
if (client.SongChallenges !== undefined) {
|
if (client.SongChallenges !== undefined) {
|
||||||
db.SongChallenges = client.SongChallenges;
|
db.SongChallenges = client.SongChallenges;
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,7 @@ import {
|
|||||||
ITraits,
|
ITraits,
|
||||||
ICalendarProgress,
|
ICalendarProgress,
|
||||||
INemesisWeaponTargetFingerprint,
|
INemesisWeaponTargetFingerprint,
|
||||||
INemesisPetTargetFingerprint,
|
INemesisPetTargetFingerprint
|
||||||
IDialogueDatabase
|
|
||||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
|
||||||
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
|
||||||
@ -71,7 +70,6 @@ import { createShip } from "./shipService";
|
|||||||
import {
|
import {
|
||||||
catbrowDetails,
|
catbrowDetails,
|
||||||
fromMongoDate,
|
fromMongoDate,
|
||||||
fromOid,
|
|
||||||
kubrowDetails,
|
kubrowDetails,
|
||||||
kubrowFurPatternsWeights,
|
kubrowFurPatternsWeights,
|
||||||
kubrowWeights,
|
kubrowWeights,
|
||||||
@ -84,7 +82,7 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ".
|
|||||||
import { createMessage } from "./inboxService";
|
import { createMessage } from "./inboxService";
|
||||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||||
import { getWorldState } from "./worldStateService";
|
import { getWorldState } from "./worldStateService";
|
||||||
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
import { getInnateDamageTag, getInnateDamageValue } from "../helpers/nemesisHelpers";
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -425,7 +423,7 @@ export const addItem = async (
|
|||||||
changes.push({
|
changes.push({
|
||||||
ItemType: egg.ItemType,
|
ItemType: egg.ItemType,
|
||||||
ExpirationDate: { $date: { $numberLong: "2000000000000" } },
|
ExpirationDate: { $date: { $numberLong: "2000000000000" } },
|
||||||
ItemId: toOid(egg._id) // TODO: Pass on buildLabel from purchaseService
|
ItemId: toOid(egg._id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -871,14 +869,10 @@ const addSentinel = (
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, ExportSentinels[sentinelName]?.defaultUpgrades);
|
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, ExportSentinels[sentinelName]?.defaultUpgrades);
|
||||||
|
|
||||||
|
const features = premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined;
|
||||||
const sentinelIndex =
|
const sentinelIndex =
|
||||||
inventory.Sentinels.push({
|
inventory.Sentinels.push({ ItemType: sentinelName, Configs: configs, XP: 0, Features: features, IsNew: true }) -
|
||||||
ItemType: sentinelName,
|
1;
|
||||||
Configs: configs,
|
|
||||||
XP: 0,
|
|
||||||
Features: premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined,
|
|
||||||
IsNew: inventory.Sentinels.find(x => x.ItemType == sentinelName) ? undefined : true
|
|
||||||
}) - 1;
|
|
||||||
inventoryChanges.Sentinels ??= [];
|
inventoryChanges.Sentinels ??= [];
|
||||||
inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
|
||||||
|
|
||||||
@ -927,9 +921,6 @@ export const addPowerSuit = async (
|
|||||||
},
|
},
|
||||||
defaultOverwrites
|
defaultOverwrites
|
||||||
);
|
);
|
||||||
if (suit.IsNew) {
|
|
||||||
suit.IsNew = !inventory.Suits.find(x => x.ItemType == powersuitName);
|
|
||||||
}
|
|
||||||
if (!suit.IsNew) {
|
if (!suit.IsNew) {
|
||||||
suit.IsNew = undefined;
|
suit.IsNew = undefined;
|
||||||
}
|
}
|
||||||
@ -964,7 +955,7 @@ export const addMechSuit = async (
|
|||||||
UpgradeVer: 101,
|
UpgradeVer: 101,
|
||||||
XP: 0,
|
XP: 0,
|
||||||
Features: features,
|
Features: features,
|
||||||
IsNew: inventory.MechSuits.find(x => x.ItemType == mechsuitName) ? undefined : true
|
IsNew: true
|
||||||
}) - 1;
|
}) - 1;
|
||||||
inventoryChanges.MechSuits ??= [];
|
inventoryChanges.MechSuits ??= [];
|
||||||
inventoryChanges.MechSuits.push(inventory.MechSuits[suitIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.MechSuits.push(inventory.MechSuits[suitIndex].toJSON<IEquipmentClient>());
|
||||||
@ -1004,7 +995,7 @@ export const addSpaceSuit = (
|
|||||||
UpgradeVer: 101,
|
UpgradeVer: 101,
|
||||||
XP: 0,
|
XP: 0,
|
||||||
Features: features,
|
Features: features,
|
||||||
IsNew: inventory.SpaceSuits.find(x => x.ItemType == spacesuitName) ? undefined : true
|
IsNew: true
|
||||||
}) - 1;
|
}) - 1;
|
||||||
inventoryChanges.SpaceSuits ??= [];
|
inventoryChanges.SpaceSuits ??= [];
|
||||||
inventoryChanges.SpaceSuits.push(inventory.SpaceSuits[suitIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.SpaceSuits.push(inventory.SpaceSuits[suitIndex].toJSON<IEquipmentClient>());
|
||||||
@ -1086,7 +1077,7 @@ export const addKubrowPet = (
|
|||||||
Configs: configs,
|
Configs: configs,
|
||||||
XP: 0,
|
XP: 0,
|
||||||
Details: details,
|
Details: details,
|
||||||
IsNew: inventory.KubrowPets.find(x => x.ItemType == kubrowPetName) ? undefined : true
|
IsNew: true
|
||||||
}) - 1;
|
}) - 1;
|
||||||
inventoryChanges.KubrowPets ??= [];
|
inventoryChanges.KubrowPets ??= [];
|
||||||
inventoryChanges.KubrowPets.push(inventory.KubrowPets[kubrowPetIndex].toJSON<IEquipmentClient>());
|
inventoryChanges.KubrowPets.push(inventory.KubrowPets[kubrowPetIndex].toJSON<IEquipmentClient>());
|
||||||
@ -1114,29 +1105,24 @@ const isCurrencyTracked = (usePremium: boolean): boolean => {
|
|||||||
export const updateCurrency = (
|
export const updateCurrency = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
price: number,
|
price: number,
|
||||||
usePremium: boolean,
|
usePremium: boolean
|
||||||
inventoryChanges: IInventoryChanges = {}
|
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
|
const currencyChanges: IInventoryChanges = {};
|
||||||
if (price != 0 && isCurrencyTracked(usePremium)) {
|
if (price != 0 && isCurrencyTracked(usePremium)) {
|
||||||
if (usePremium) {
|
if (usePremium) {
|
||||||
if (inventory.PremiumCreditsFree > 0) {
|
if (inventory.PremiumCreditsFree > 0) {
|
||||||
const premiumCreditsFreeDelta = Math.min(price, inventory.PremiumCreditsFree) * -1;
|
currencyChanges.PremiumCreditsFree = Math.min(price, inventory.PremiumCreditsFree) * -1;
|
||||||
inventoryChanges.PremiumCreditsFree ??= 0;
|
inventory.PremiumCreditsFree += currencyChanges.PremiumCreditsFree;
|
||||||
inventoryChanges.PremiumCreditsFree += premiumCreditsFreeDelta;
|
|
||||||
inventory.PremiumCreditsFree += premiumCreditsFreeDelta;
|
|
||||||
}
|
}
|
||||||
inventoryChanges.PremiumCredits ??= 0;
|
currencyChanges.PremiumCredits = -price;
|
||||||
inventoryChanges.PremiumCredits -= price;
|
inventory.PremiumCredits += currencyChanges.PremiumCredits;
|
||||||
inventory.PremiumCredits -= price;
|
|
||||||
logger.debug(`currency changes `, { PremiumCredits: -price });
|
|
||||||
} else {
|
} else {
|
||||||
inventoryChanges.RegularCredits ??= 0;
|
currencyChanges.RegularCredits = -price;
|
||||||
inventoryChanges.RegularCredits -= price;
|
inventory.RegularCredits += currencyChanges.RegularCredits;
|
||||||
inventory.RegularCredits -= price;
|
|
||||||
logger.debug(`currency changes `, { RegularCredits: -price });
|
|
||||||
}
|
}
|
||||||
|
logger.debug(`currency changes `, currencyChanges);
|
||||||
}
|
}
|
||||||
return inventoryChanges;
|
return currencyChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
|
export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
|
||||||
@ -1271,9 +1257,6 @@ export const addEquipment = (
|
|||||||
},
|
},
|
||||||
defaultOverwrites
|
defaultOverwrites
|
||||||
);
|
);
|
||||||
if (equipment.IsNew) {
|
|
||||||
equipment.IsNew = !inventory[category].find(x => x.ItemType == type);
|
|
||||||
}
|
|
||||||
if (!equipment.IsNew) {
|
if (!equipment.IsNew) {
|
||||||
equipment.IsNew = undefined;
|
equipment.IsNew = undefined;
|
||||||
}
|
}
|
||||||
@ -1508,9 +1491,9 @@ export const applyClientEquipmentUpdates = (
|
|||||||
const category = inventory[categoryName];
|
const category = inventory[categoryName];
|
||||||
|
|
||||||
gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
|
gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
|
||||||
const item = category.id(fromOid(ItemId));
|
const item = category.id(ItemId.$oid);
|
||||||
if (!item) {
|
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) {
|
if (XP) {
|
||||||
@ -1586,17 +1569,12 @@ export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray:
|
|||||||
if (MiscItems[itemIndex].ItemCount == 0) {
|
if (MiscItems[itemIndex].ItemCount == 0) {
|
||||||
MiscItems.splice(itemIndex, 1);
|
MiscItems.splice(itemIndex, 1);
|
||||||
} else if (MiscItems[itemIndex].ItemCount <= 0) {
|
} 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 = (
|
const applyArrayChanges = (arr: ITypeCount[], changes: ITypeCount[]): void => {
|
||||||
inventory: TInventoryDatabaseDocument,
|
|
||||||
key: "ShipDecorations" | "Consumables" | "CrewShipRawSalvage" | "CrewShipAmmo" | "Recipes" | "LevelKeys",
|
|
||||||
changes: ITypeCount[]
|
|
||||||
): void => {
|
|
||||||
const arr: ITypeCount[] = inventory[key];
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
if (change.ItemCount != 0) {
|
if (change.ItemCount != 0) {
|
||||||
let itemIndex = arr.findIndex(x => x.ItemType === change.ItemType);
|
let itemIndex = arr.findIndex(x => x.ItemType === change.ItemType);
|
||||||
@ -1608,34 +1586,34 @@ const applyArrayChanges = (
|
|||||||
if (arr[itemIndex].ItemCount == 0) {
|
if (arr[itemIndex].ItemCount == 0) {
|
||||||
arr.splice(itemIndex, 1);
|
arr.splice(itemIndex, 1);
|
||||||
} else if (arr[itemIndex].ItemCount <= 0) {
|
} 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 => {
|
export const addShipDecorations = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "ShipDecorations", itemsArray);
|
applyArrayChanges(inventory.ShipDecorations, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addConsumables = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addConsumables = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "Consumables", itemsArray);
|
applyArrayChanges(inventory.Consumables, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addCrewShipRawSalvage = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addCrewShipRawSalvage = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "CrewShipRawSalvage", itemsArray);
|
applyArrayChanges(inventory.CrewShipRawSalvage, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addCrewShipAmmo = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addCrewShipAmmo = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "CrewShipAmmo", itemsArray);
|
applyArrayChanges(inventory.CrewShipAmmo, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "Recipes", itemsArray);
|
applyArrayChanges(inventory.Recipes, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addLevelKeys = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
export const addLevelKeys = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[]): void => {
|
||||||
applyArrayChanges(inventory, "LevelKeys", itemsArray);
|
applyArrayChanges(inventory.LevelKeys, itemsArray);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawUpgrade[]): void => {
|
export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawUpgrade[]): void => {
|
||||||
@ -1655,7 +1633,7 @@ export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawU
|
|||||||
if (RawUpgrades[itemIndex].ItemCount == 0) {
|
if (RawUpgrades[itemIndex].ItemCount == 0) {
|
||||||
RawUpgrades.splice(itemIndex, 1);
|
RawUpgrades.splice(itemIndex, 1);
|
||||||
} else if (RawUpgrades[itemIndex].ItemCount <= 0) {
|
} 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}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1670,7 +1648,7 @@ export const addFusionTreasures = (inventory: TInventoryDatabaseDocument, itemsA
|
|||||||
if (FusionTreasures[itemIndex].ItemCount == 0) {
|
if (FusionTreasures[itemIndex].ItemCount == 0) {
|
||||||
FusionTreasures.splice(itemIndex, 1);
|
FusionTreasures.splice(itemIndex, 1);
|
||||||
} else if (FusionTreasures[itemIndex].ItemCount <= 0) {
|
} 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 {
|
} else {
|
||||||
FusionTreasures.push({ ItemCount, ItemType, Sockets });
|
FusionTreasures.push({ ItemCount, ItemType, Sockets });
|
||||||
@ -1907,29 +1885,6 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
|
|
||||||
let dialogue = inventory.DialogueHistory!.Dialogues!.find(x => x.DialogueName == dialogueName);
|
|
||||||
if (!dialogue) {
|
|
||||||
dialogue =
|
|
||||||
inventory.DialogueHistory!.Dialogues![
|
|
||||||
inventory.DialogueHistory!.Dialogues!.push({
|
|
||||||
Rank: 0,
|
|
||||||
Chemistry: 0,
|
|
||||||
AvailableDate: new Date(0),
|
|
||||||
AvailableGiftDate: new Date(0),
|
|
||||||
RankUpExpiry: new Date(0),
|
|
||||||
BountyChemExpiry: new Date(0),
|
|
||||||
QueuedDialogues: [],
|
|
||||||
Gifts: [],
|
|
||||||
Booleans: [],
|
|
||||||
Completed: [],
|
|
||||||
DialogueName: dialogueName
|
|
||||||
}) - 1
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return dialogue;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
|
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
|
||||||
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||||
|
|
||||||
@ -1969,7 +1924,8 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
weaponType: string,
|
weaponType: string,
|
||||||
nemesisName: string = "AGOR ROK",
|
nemesisName: string = "AGOR ROK",
|
||||||
weaponLoc?: string,
|
weaponLoc?: string,
|
||||||
profile: INemesisProfile = generateNemesisProfile()
|
KillingSuit: string = "/Lotus/Powersuits/Ember/Ember",
|
||||||
|
fp: bigint = generateRewardSeed()
|
||||||
): void => {
|
): void => {
|
||||||
if (!weaponLoc) {
|
if (!weaponLoc) {
|
||||||
weaponLoc = ExportWeapons[weaponType].name;
|
weaponLoc = ExportWeapons[weaponType].name;
|
||||||
@ -1990,8 +1946,8 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
compat: weaponType,
|
compat: weaponType,
|
||||||
buffs: [
|
buffs: [
|
||||||
{
|
{
|
||||||
Tag: profile.innateDamageTag,
|
Tag: getInnateDamageTag(KillingSuit),
|
||||||
Value: profile.innateDamageValue
|
Value: getInnateDamageValue(fp)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -2000,15 +1956,27 @@ export const giveNemesisWeaponRecipe = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const giveNemesisPetRecipe = (
|
export const giveNemesisPetRecipe = (inventory: TInventoryDatabaseDocument, nemesisName: string = "AGOR ROK"): void => {
|
||||||
inventory: TInventoryDatabaseDocument,
|
const head = getRandomElement([
|
||||||
nemesisName: string = "AGOR ROK",
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA",
|
||||||
profile: INemesisProfile = generateNemesisProfile()
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB",
|
||||||
): void => {
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
|
||||||
const head = profile.petHead!;
|
])!;
|
||||||
const body = profile.petBody!;
|
const body = getRandomElement([
|
||||||
const legs = profile.petLegs!;
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyA",
|
||||||
const tail = profile.petTail!;
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartBodyC"
|
||||||
|
])!;
|
||||||
|
const legs = getRandomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartLegsC"
|
||||||
|
])!;
|
||||||
|
const tail = getRandomElement([
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailA",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailB",
|
||||||
|
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartTailC"
|
||||||
|
])!;
|
||||||
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
|
const recipeType = Object.entries(ExportRecipes).find(arr => arr[1].resultType == head)![0];
|
||||||
addRecipes(inventory, [
|
addRecipes(inventory, [
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
|
import randomRewards from "@/static/fixed_responses/loginRewards/randomRewards.json";
|
||||||
import { IInventoryChanges } from "../types/purchaseTypes";
|
import { IInventoryChanges } from "../types/purchaseTypes";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "./loginService";
|
||||||
import { mixSeeds, SRng } from "./rngService";
|
import { CRng, mixSeeds } from "./rngService";
|
||||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||||
import { addBooster, updateCurrency } from "./inventoryService";
|
import { addBooster, updateCurrency } from "./inventoryService";
|
||||||
import { handleStoreItemAcquisition } from "./purchaseService";
|
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.
|
// Always produces the same result for the same account _id & LoginDays pair.
|
||||||
export const isLoginRewardAChoice = (account: TAccountDocument): boolean => {
|
export const isLoginRewardAChoice = (account: TAccountDocument): boolean => {
|
||||||
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
||||||
const rng = new SRng(mixSeeds(accountSeed, account.LoginDays));
|
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
|
||||||
return rng.randomFloat() < 0.25;
|
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.
|
// Always produces the same result for the same account _id & LoginDays pair.
|
||||||
@ -59,8 +59,8 @@ export const getRandomLoginRewards = (
|
|||||||
inventory: TInventoryDatabaseDocument
|
inventory: TInventoryDatabaseDocument
|
||||||
): ILoginReward[] => {
|
): ILoginReward[] => {
|
||||||
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
const accountSeed = parseInt(account._id.toString().substring(16), 16);
|
||||||
const rng = new SRng(mixSeeds(accountSeed, account.LoginDays));
|
const rng = new CRng(mixSeeds(accountSeed, account.LoginDays));
|
||||||
const pick_a_door = rng.randomFloat() < 0.25;
|
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)];
|
const rewards = [getRandomLoginReward(rng, account.LoginDays, inventory)];
|
||||||
if (pick_a_door) {
|
if (pick_a_door) {
|
||||||
do {
|
do {
|
||||||
@ -73,7 +73,7 @@ export const getRandomLoginRewards = (
|
|||||||
return rewards;
|
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 = rng.randomReward(randomRewards)!;
|
||||||
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
|
//const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!;
|
||||||
if (reward.RewardType == "RT_RANDOM_RECIPE") {
|
if (reward.RewardType == "RT_RANDOM_RECIPE") {
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
|
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 {
|
import {
|
||||||
addBooster,
|
addBooster,
|
||||||
addChallenges,
|
addChallenges,
|
||||||
@ -35,7 +35,6 @@ import {
|
|||||||
combineInventoryChanges,
|
combineInventoryChanges,
|
||||||
generateRewardSeed,
|
generateRewardSeed,
|
||||||
getCalendarProgress,
|
getCalendarProgress,
|
||||||
getDialogue,
|
|
||||||
giveNemesisPetRecipe,
|
giveNemesisPetRecipe,
|
||||||
giveNemesisWeaponRecipe,
|
giveNemesisWeaponRecipe,
|
||||||
updateCurrency,
|
updateCurrency,
|
||||||
@ -56,28 +55,13 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.
|
|||||||
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
|
||||||
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
|
||||||
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
|
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
|
||||||
import {
|
import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers";
|
||||||
generateNemesisProfile,
|
|
||||||
getInfNodes,
|
|
||||||
getNemesisPasscode,
|
|
||||||
getWeaponsForManifest,
|
|
||||||
sendCodaFinishedMessage
|
|
||||||
} from "@/src/helpers/nemesisHelpers";
|
|
||||||
import { Loadout } from "../models/inventoryModels/loadoutModel";
|
import { Loadout } from "../models/inventoryModels/loadoutModel";
|
||||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
||||||
import {
|
import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService";
|
||||||
getLiteSortie,
|
|
||||||
getSortie,
|
|
||||||
getWorldState,
|
|
||||||
idToBountyCycle,
|
|
||||||
idToDay,
|
|
||||||
idToWeek,
|
|
||||||
pushClassicBounties
|
|
||||||
} from "./worldStateService";
|
|
||||||
import { config } from "./configService";
|
import { config } from "./configService";
|
||||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||||
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
|
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
|
||||||
import { fromOid } from "../helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
||||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||||
@ -183,14 +167,6 @@ export const addMissionInventoryUpdates = async (
|
|||||||
}
|
}
|
||||||
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
|
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
|
||||||
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
|
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
|
||||||
if (inventory.Nemesis.Faction != "FC_INFESTATION" && inventory.Nemesis.Hints.length != 3) {
|
|
||||||
const progressNeeded = [35, 60, 100][inventory.Nemesis.Hints.length];
|
|
||||||
if (inventory.Nemesis.HintProgress >= progressNeeded) {
|
|
||||||
inventory.Nemesis.HintProgress -= progressNeeded;
|
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis);
|
|
||||||
inventory.Nemesis.Hints.push(passcode[inventory.Nemesis.Hints.length]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
|
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
|
||||||
// e.g. for Profit-Taker Phase 1:
|
// e.g. for Profit-Taker Phase 1:
|
||||||
@ -423,14 +399,8 @@ export const addMissionInventoryUpdates = async (
|
|||||||
break;
|
break;
|
||||||
case "Upgrades":
|
case "Upgrades":
|
||||||
value.forEach(clientUpgrade => {
|
value.forEach(clientUpgrade => {
|
||||||
const id = fromOid(clientUpgrade.ItemId);
|
const upgrade = inventory.Upgrades.id(clientUpgrade.ItemId.$oid)!;
|
||||||
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
|
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
|
||||||
}
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "WeaponSkins":
|
case "WeaponSkins":
|
||||||
@ -654,18 +624,12 @@ export const addMissionInventoryUpdates = async (
|
|||||||
Rank: inventory.Nemesis.Rank,
|
Rank: inventory.Nemesis.Rank,
|
||||||
Traded: inventory.Nemesis.Traded,
|
Traded: inventory.Nemesis.Traded,
|
||||||
PrevOwners: inventory.Nemesis.PrevOwners,
|
PrevOwners: inventory.Nemesis.PrevOwners,
|
||||||
SecondInCommand: false,
|
SecondInCommand: inventory.Nemesis.SecondInCommand,
|
||||||
Weakened: inventory.Nemesis.Weakened,
|
Weakened: inventory.Nemesis.Weakened,
|
||||||
// And set killed flag
|
// And set killed flag
|
||||||
k: value.killed
|
k: value.killed
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = generateNemesisProfile(
|
|
||||||
inventory.Nemesis.fp,
|
|
||||||
inventory.Nemesis.Faction,
|
|
||||||
inventory.Nemesis.KillingSuit
|
|
||||||
);
|
|
||||||
|
|
||||||
if (value.killed) {
|
if (value.killed) {
|
||||||
if (
|
if (
|
||||||
value.weaponLoc &&
|
value.weaponLoc &&
|
||||||
@ -674,46 +638,23 @@ export const addMissionInventoryUpdates = async (
|
|||||||
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
|
const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[
|
||||||
inventory.Nemesis.WeaponIdx
|
inventory.Nemesis.WeaponIdx
|
||||||
];
|
];
|
||||||
giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
|
giveNemesisWeaponRecipe(
|
||||||
|
inventory,
|
||||||
|
weaponType,
|
||||||
|
value.nemesisName,
|
||||||
|
value.weaponLoc,
|
||||||
|
inventory.Nemesis.KillingSuit,
|
||||||
|
inventory.Nemesis.fp
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (value.petLoc) {
|
if (value.petLoc) {
|
||||||
giveNemesisPetRecipe(inventory, value.nemesisName, profile);
|
giveNemesisPetRecipe(inventory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Players will receive a Lich's Ephemera regardless of whether they Vanquish or Convert them."
|
|
||||||
if (profile.ephemera) {
|
|
||||||
addSkin(inventory, profile.ephemera);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (inventory.Nemesis.Faction) {
|
|
||||||
case "FC_GRINEER":
|
|
||||||
addSkin(
|
|
||||||
inventory,
|
|
||||||
value.killed
|
|
||||||
? "/Lotus/Upgrades/Skins/Clan/LichKillerBadgeItem"
|
|
||||||
: "/Lotus/Upgrades/Skins/Sigils/KuvaLichSigil"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "FC_CORPUS":
|
|
||||||
addSkin(
|
|
||||||
inventory,
|
|
||||||
value.killed
|
|
||||||
? "/Lotus/Upgrades/Skins/Clan/CorpusLichBadgeItem"
|
|
||||||
: "/Lotus/Upgrades/Skins/Sigils/CorpusLichSigil"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "FC_INFESTATION":
|
|
||||||
// TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given?
|
// TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given?
|
||||||
await sendCodaFinishedMessage(
|
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
||||||
inventory,
|
await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed);
|
||||||
inventory.Nemesis.fp,
|
|
||||||
value.nemesisName,
|
|
||||||
value.killed
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.Nemesis = undefined;
|
inventory.Nemesis = undefined;
|
||||||
@ -877,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
|
//TODO: return type of partial missioninventoryupdate response
|
||||||
export const addMissionRewards = async (
|
export const addMissionRewards = async (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
@ -906,13 +840,7 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO: check double reward merging
|
//TODO: check double reward merging
|
||||||
const MissionRewards: IMissionReward[] = getRandomMissionDrops(
|
const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion);
|
||||||
inventory,
|
|
||||||
rewardInfo,
|
|
||||||
missions,
|
|
||||||
wagerTier,
|
|
||||||
firstCompletion
|
|
||||||
);
|
|
||||||
logger.debug("random mission drops:", MissionRewards);
|
logger.debug("random mission drops:", MissionRewards);
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
const inventoryChanges: IInventoryChanges = {};
|
||||||
const AffiliationMods: IAffiliationMods[] = [];
|
const AffiliationMods: IAffiliationMods[] = [];
|
||||||
@ -1092,9 +1020,11 @@ export const addMissionRewards = async (
|
|||||||
|
|
||||||
if (strippedItems) {
|
if (strippedItems) {
|
||||||
for (const si of strippedItems) {
|
for (const si of strippedItems) {
|
||||||
if (si.DropTable in droptableAliases) {
|
if (si.DropTable == "/Lotus/Types/DropTables/ManInTheWall/MITWGruzzlingArcanesDropTable") {
|
||||||
logger.debug(`rewriting ${si.DropTable} to ${droptableAliases[si.DropTable]}`);
|
logger.debug(
|
||||||
si.DropTable = droptableAliases[si.DropTable];
|
`rewriting ${si.DropTable} to /Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable`
|
||||||
|
);
|
||||||
|
si.DropTable = "/Lotus/Types/DropTables/EntratiLabDropTables/DoppelgangerDropTable";
|
||||||
}
|
}
|
||||||
const droptables = ExportEnemies.droptables[si.DropTable] ?? [];
|
const droptables = ExportEnemies.droptables[si.DropTable] ?? [];
|
||||||
if (si.DROP_MOD) {
|
if (si.DROP_MOD) {
|
||||||
@ -1227,9 +1157,8 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rewardInfo.challengeMissionId) {
|
if (rewardInfo.challengeMissionId) {
|
||||||
const [syndicateTag, tierStr, chemistryStr] = rewardInfo.challengeMissionId.split("_");
|
const [syndicateTag, tierStr] = rewardInfo.challengeMissionId.split("_"); // TODO: third part in HexSyndicate jobs - Chemistry points
|
||||||
const tier = Number(tierStr);
|
const tier = Number(tierStr);
|
||||||
const chemistry = Number(chemistryStr);
|
|
||||||
const isSteelPath = missions?.Tier;
|
const isSteelPath = missions?.Tier;
|
||||||
if (syndicateTag === "ZarimanSyndicate") {
|
if (syndicateTag === "ZarimanSyndicate") {
|
||||||
let medallionAmount = tier + 1;
|
let medallionAmount = tier + 1;
|
||||||
@ -1246,23 +1175,6 @@ export const addMissionRewards = async (
|
|||||||
if (isSteelPath) standingAmount *= 1.5;
|
if (isSteelPath) standingAmount *= 1.5;
|
||||||
AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount));
|
AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount));
|
||||||
}
|
}
|
||||||
if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) {
|
|
||||||
const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed;
|
|
||||||
const { nodes, buddies } = getHexBounties(seed);
|
|
||||||
const buddy = buddies[tier];
|
|
||||||
logger.debug(`Hex seed is ${seed}, giving chemistry for ${buddy}`);
|
|
||||||
if (missions?.Tag != nodes[tier]) {
|
|
||||||
logger.warn(
|
|
||||||
`Uh-oh, tier ${tier} bounty should've been on ${nodes[tier]} but you were just on ${missions?.Tag}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const tomorrowAt0Utc = config.noKimCooldowns
|
|
||||||
? Date.now()
|
|
||||||
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
|
||||||
const dialogue = getDialogue(inventory, buddy);
|
|
||||||
dialogue.Chemistry += chemistry;
|
|
||||||
dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc);
|
|
||||||
}
|
|
||||||
if (isSteelPath) {
|
if (isSteelPath) {
|
||||||
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
|
await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1);
|
||||||
MissionRewards.push({
|
MissionRewards.push({
|
||||||
@ -1377,7 +1289,6 @@ function getLevelCreditRewards(node: IRegion): number {
|
|||||||
function getRandomMissionDrops(
|
function getRandomMissionDrops(
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
RewardInfo: IRewardInfo,
|
RewardInfo: IRewardInfo,
|
||||||
mission: IMission | undefined,
|
|
||||||
tierOverride: number | undefined,
|
tierOverride: number | undefined,
|
||||||
firstCompletion: boolean
|
firstCompletion: boolean
|
||||||
): IMissionReward[] {
|
): IMissionReward[] {
|
||||||
@ -1460,50 +1371,23 @@ function getRandomMissionDrops(
|
|||||||
// TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
|
// 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"];
|
rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
|
||||||
} else if (RewardInfo.sortieId) {
|
} else if (RewardInfo.sortieId) {
|
||||||
// Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes.
|
// 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.
|
||||||
// Assassinations in non-lite sorties are an exception to this.
|
|
||||||
if (region.missionIndex == 0) {
|
if (region.missionIndex == 0) {
|
||||||
const arr = RewardInfo.sortieId.split("_");
|
const arr = RewardInfo.sortieId.split("_");
|
||||||
let giveNodeReward = false;
|
let sortieId = arr[1];
|
||||||
if (arr[1] != "Lite") {
|
if (sortieId == "Lite") {
|
||||||
const sortie = getSortie(idToDay(arr[1]));
|
sortieId = arr[2];
|
||||||
giveNodeReward = sortie.Variants.find(x => x.node == arr[0])!.missionType == "MT_ASSASSINATION";
|
|
||||||
}
|
}
|
||||||
rewardManifests = giveNodeReward ? region.rewardManifests : [];
|
const sortie = getSortie(idToDay(sortieId));
|
||||||
|
const mission = sortie.Variants.find(x => x.node == arr[0])!;
|
||||||
|
if (mission.missionType == "MT_ASSASSINATION") {
|
||||||
|
rewardManifests = region.rewardManifests;
|
||||||
} else {
|
} else {
|
||||||
rewardManifests = [];
|
rewardManifests = [];
|
||||||
}
|
}
|
||||||
} else if (RewardInfo.T == 13) {
|
} else {
|
||||||
// Undercroft extra/side portal (normal mode), gives 1 Pathos Clamp + Duviri Arcane.
|
|
||||||
drops.push({
|
|
||||||
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
|
|
||||||
ItemCount: 1
|
|
||||||
});
|
|
||||||
rewardManifests = [
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriStaticUndercroftResourceRewards"
|
|
||||||
];
|
|
||||||
} else if (RewardInfo.T == 14) {
|
|
||||||
// Undercroft extra/side portal (steel path), gives 3 Pathos Clamps + Eidolon Arcane.
|
|
||||||
drops.push({
|
|
||||||
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
|
|
||||||
ItemCount: 3
|
|
||||||
});
|
|
||||||
rewardManifests = [
|
|
||||||
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriSteelPathStaticUndercroftResourceRewards"
|
|
||||||
];
|
|
||||||
} else if (RewardInfo.T == 15) {
|
|
||||||
rewardManifests = [
|
|
||||||
mission?.Tier == 1
|
|
||||||
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
|
|
||||||
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
|
|
||||||
];
|
|
||||||
} else if (RewardInfo.T == 70) {
|
|
||||||
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
|
|
||||||
drops.push({
|
|
||||||
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
|
|
||||||
ItemCount: mission?.Tier == 1 ? 15 : 10
|
|
||||||
});
|
|
||||||
rewardManifests = [];
|
rewardManifests = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
rewardManifests = region.rewardManifests;
|
rewardManifests = region.rewardManifests;
|
||||||
}
|
}
|
||||||
@ -1623,7 +1507,7 @@ function getRandomMissionDrops(
|
|||||||
ZarimanSyndicate: [
|
ZarimanSyndicate: [
|
||||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierATableRewards",
|
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierATableRewards",
|
||||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierBTableRewards",
|
"/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/TierDTableRewards",
|
||||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierETableRewards"
|
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierETableRewards"
|
||||||
],
|
],
|
||||||
@ -1650,35 +1534,6 @@ function getRandomMissionDrops(
|
|||||||
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
|
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
rotations = getRotations(RewardInfo, tierOverride);
|
||||||
}
|
}
|
||||||
if (rewardManifests.length != 0) {
|
if (rewardManifests.length != 0) {
|
||||||
@ -1822,55 +1677,3 @@ const libraryPersonalTargetToAvatar: Record<string, string> = {
|
|||||||
"/Lotus/Types/Game/Library/Targets/Research10Target":
|
"/Lotus/Types/Game/Library/Targets/Research10Target":
|
||||||
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
"/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar"
|
||||||
};
|
};
|
||||||
|
|
||||||
const node_excluded_buddies: Record<string, string> = {
|
|
||||||
SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
|
||||||
SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
|
||||||
SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
|
||||||
SolNode850: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
|
||||||
SolNode853: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
|
||||||
SolNode854: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
|
|
||||||
};
|
|
||||||
|
|
||||||
const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } => {
|
|
||||||
// We're gonna shuffle these arrays, so they're not truly 'const'.
|
|
||||||
const nodes: string[] = [
|
|
||||||
"SolNode850",
|
|
||||||
"SolNode851",
|
|
||||||
"SolNode852",
|
|
||||||
"SolNode853",
|
|
||||||
"SolNode854",
|
|
||||||
"SolNode856",
|
|
||||||
"SolNode858"
|
|
||||||
];
|
|
||||||
const excludable_nodes: string[] = ["SolNode851", "SolNode852", "SolNode853", "SolNode854"];
|
|
||||||
const buddies: string[] = [
|
|
||||||
"/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
|
|
||||||
"/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
|
|
||||||
"/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
|
|
||||||
"/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
|
|
||||||
"/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
|
|
||||||
"/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue"
|
|
||||||
];
|
|
||||||
|
|
||||||
const rng = new SRng(seed);
|
|
||||||
rng.shuffleArray(nodes);
|
|
||||||
rng.shuffleArray(excludable_nodes);
|
|
||||||
while (nodes.length > buddies.length) {
|
|
||||||
nodes.splice(
|
|
||||||
nodes.findIndex(x => x == excludable_nodes[0]),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
excludable_nodes.splice(0, 1);
|
|
||||||
}
|
|
||||||
rng.shuffleArray(buddies);
|
|
||||||
for (let i = 0; i != 6; ++i) {
|
|
||||||
if (buddies[i] == node_excluded_buddies[nodes[i]]) {
|
|
||||||
const swapIdx = (i + 1) % buddies.length;
|
|
||||||
const tmp = buddies[swapIdx];
|
|
||||||
buddies[swapIdx] = buddies[i];
|
|
||||||
buddies[i] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { nodes, buddies };
|
|
||||||
};
|
|
||||||
|
@ -86,12 +86,54 @@ export const mixSeeds = (seed1: number, seed2: number): number => {
|
|||||||
return seed >>> 0;
|
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 {
|
export class SRng {
|
||||||
state: bigint;
|
state: bigint;
|
||||||
|
|
||||||
constructor(seed: bigint | number) {
|
constructor(seed: bigint) {
|
||||||
this.state = BigInt(seed);
|
this.state = seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
randomInt(min: number, max: number): number {
|
randomInt(min: number, max: number): number {
|
||||||
@ -115,19 +157,4 @@ export class SRng {
|
|||||||
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
||||||
return getRewardAtPercentage(pool, this.randomFloat());
|
return getRewardAtPercentage(pool, this.randomFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
churnSeed(its: number): void {
|
|
||||||
while (its--) {
|
|
||||||
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shuffleArray<T>(arr: T[]): void {
|
|
||||||
for (let lastIdx = arr.length - 1; lastIdx >= 1; --lastIdx) {
|
|
||||||
const swapIdx = this.randomInt(0, lastIdx);
|
|
||||||
const tmp = arr[swapIdx];
|
|
||||||
arr[swapIdx] = arr[lastIdx];
|
|
||||||
arr[lastIdx] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
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 { IMongoDate } from "@/src/types/commonTypes";
|
||||||
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
|
||||||
import { ExportVendors, IRange } from "warframe-public-export-plus";
|
import { ExportVendors, IRange } from "warframe-public-export-plus";
|
||||||
@ -204,7 +204,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
|
||||||
const cycleDuration = vendorInfo.cycleDuration;
|
const cycleDuration = vendorInfo.cycleDuration;
|
||||||
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
|
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
|
||||||
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
|
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
|
||||||
const manifest = ExportVendors[vendorInfo.TypeName];
|
const manifest = ExportVendors[vendorInfo.TypeName];
|
||||||
const offersToAdd = [];
|
const offersToAdd = [];
|
||||||
if (manifest.numItems && !manifest.isOneBinPerCycle) {
|
if (manifest.numItems && !manifest.isOneBinPerCycle) {
|
||||||
@ -247,7 +247,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
$oid:
|
$oid:
|
||||||
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
|
||||||
vendorInfo._id.$oid.substring(8, 16) +
|
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) {
|
if (rawItem.numRandomItemPrices) {
|
||||||
@ -282,9 +283,9 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
|
|||||||
item.PremiumPrice = [value, value];
|
item.PremiumPrice = [value, value];
|
||||||
}
|
}
|
||||||
if (vendorInfo.RandomSeedType) {
|
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") {
|
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);
|
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 { buildConfig } from "@/src/services/buildConfigService";
|
||||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { config } from "@/src/services/configService";
|
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 { ExportNightwave, ExportRegions, IRegion } from "warframe-public-export-plus";
|
||||||
import {
|
import {
|
||||||
ICalendarDay,
|
ICalendarDay,
|
||||||
@ -18,7 +18,6 @@ import {
|
|||||||
ISyndicateMissionInfo,
|
ISyndicateMissionInfo,
|
||||||
IWorldState
|
IWorldState
|
||||||
} from "../types/worldStateTypes";
|
} from "../types/worldStateTypes";
|
||||||
import { version_compare } from "../helpers/inventoryHelpers";
|
|
||||||
|
|
||||||
const sortieBosses = [
|
const sortieBosses = [
|
||||||
"SORTIE_BOSS_HYENA",
|
"SORTIE_BOSS_HYENA",
|
||||||
@ -193,7 +192,7 @@ const pushSyndicateMissions = (
|
|||||||
): void => {
|
): void => {
|
||||||
const nodeOptions: string[] = [...syndicateMissions];
|
const nodeOptions: string[] = [...syndicateMissions];
|
||||||
|
|
||||||
const rng = new SRng(seed);
|
const rng = new CRng(seed);
|
||||||
const nodes: string[] = [];
|
const nodes: string[] = [];
|
||||||
for (let i = 0; i != 6; ++i) {
|
for (let i = 0; i != 6; ++i) {
|
||||||
const index = rng.randomInt(0, nodeOptions.length - 1);
|
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 => {
|
export const getSortie = (day: number): ISortie => {
|
||||||
const seed = new SRng(day).randomInt(0, 100_000);
|
const seed = new CRng(day).randomInt(0, 0xffff);
|
||||||
const rng = new SRng(seed);
|
const rng = new CRng(seed);
|
||||||
|
|
||||||
const boss = rng.randomElement(sortieBosses)!;
|
const boss = rng.randomElement(sortieBosses)!;
|
||||||
|
|
||||||
@ -253,10 +252,12 @@ export const getSortie = (day: number): ISortie => {
|
|||||||
|
|
||||||
const selectedNodes: ISortieMission[] = [];
|
const selectedNodes: ISortieMission[] = [];
|
||||||
const missionTypes = new Set();
|
const missionTypes = new Set();
|
||||||
|
const modifierTypes = new Set();
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const randomIndex = rng.randomInt(0, nodes.length - 1);
|
const randomIndex = rng.randomInt(0, nodes.length - 1);
|
||||||
const node = nodes[randomIndex];
|
const node = nodes[randomIndex];
|
||||||
|
let modifierType: string;
|
||||||
|
|
||||||
const modifiers = [
|
const modifiers = [
|
||||||
"SORTIE_MODIFIER_LOW_ENERGY",
|
"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;
|
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset;
|
||||||
pushTilesetModifiers(modifiers, tileset);
|
pushTilesetModifiers(modifiers, tileset);
|
||||||
|
|
||||||
const modifierType = rng.randomElement(modifiers)!;
|
do {
|
||||||
|
modifierType = rng.randomElement(modifiers)!;
|
||||||
|
} while (modifierTypes.has(modifierType));
|
||||||
|
|
||||||
selectedNodes.push({
|
selectedNodes.push({
|
||||||
missionType: "MT_ASSASSINATION",
|
missionType: "MT_ASSASSINATION",
|
||||||
@ -319,7 +322,9 @@ export const getSortie = (day: number): ISortie => {
|
|||||||
modifiers.push("SORTIE_MODIFIER_SHIELDS");
|
modifiers.push("SORTIE_MODIFIER_SHIELDS");
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifierType = rng.randomElement(modifiers)!;
|
do {
|
||||||
|
modifierType = rng.randomElement(modifiers)!;
|
||||||
|
} while (modifierTypes.has(modifierType));
|
||||||
|
|
||||||
selectedNodes.push({
|
selectedNodes.push({
|
||||||
missionType,
|
missionType,
|
||||||
@ -329,6 +334,7 @@ export const getSortie = (day: number): ISortie => {
|
|||||||
});
|
});
|
||||||
nodes.splice(randomIndex, 1);
|
nodes.splice(randomIndex, 1);
|
||||||
missionTypes.add(missionType);
|
missionTypes.add(missionType);
|
||||||
|
modifierTypes.add(modifierType);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayStart = getSortieTime(day);
|
const dayStart = getSortieTime(day);
|
||||||
@ -351,7 +357,7 @@ const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
|||||||
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
|
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
|
||||||
const dayStart = EPOCH + day * 86400000;
|
const dayStart = EPOCH + day * 86400000;
|
||||||
const dayEnd = EPOCH + (day + 3) * 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 {
|
return {
|
||||||
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
||||||
Daily: true,
|
Daily: true,
|
||||||
@ -371,7 +377,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
|
|||||||
const weekStart = EPOCH + week * 604800000;
|
const weekStart = EPOCH + week * 604800000;
|
||||||
const weekEnd = weekStart + 604800000;
|
const weekEnd = weekStart + 604800000;
|
||||||
const challengeId = week * 7 + id;
|
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 {
|
return {
|
||||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
@ -388,7 +394,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
|
|||||||
const weekStart = EPOCH + week * 604800000;
|
const weekStart = EPOCH + week * 604800000;
|
||||||
const weekEnd = weekStart + 604800000;
|
const weekEnd = weekStart + 604800000;
|
||||||
const challengeId = week * 7 + id;
|
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 {
|
return {
|
||||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
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?
|
// 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 bountyCycleStart = bountyCycle * 9000000;
|
||||||
const bountyCycleEnd = bountyCycleStart + 9000000;
|
const bountyCycleEnd = bountyCycleStart + 9000000;
|
||||||
|
|
||||||
{
|
{
|
||||||
const rng = new SRng(seed);
|
const rng = new CRng(seed);
|
||||||
syndicateMissions.push({
|
syndicateMissions.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
|
$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({
|
syndicateMissions.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
|
$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({
|
syndicateMissions.push({
|
||||||
_id: {
|
_id: {
|
||||||
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
|
$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}`);
|
//logger.debug(`birthday on day ${day}`);
|
||||||
eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0
|
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 = [
|
const challenges = [
|
||||||
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
|
||||||
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
|
||||||
@ -982,7 +988,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Elite Sanctuary Onslaught cycling every week
|
// 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
|
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
|
||||||
let bountyCycle = Math.trunc(Date.now() / 9000000);
|
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.
|
// 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 sday = Date.now() >= rollover ? day : day - 1;
|
||||||
const rng = new SRng(sdy);
|
const rng = new CRng(sday);
|
||||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48049", "ArbitersSyndicate");
|
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
|
||||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4804a", "CephalonSudaSyndicate");
|
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
|
||||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4804e", "NewLokaSyndicate");
|
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
|
||||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48050", "PerrinSyndicate");
|
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
|
||||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa4805e", "RedVeilSyndicate");
|
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
|
||||||
pushSyndicateMissions(worldState, sdy, rng.randomInt(0, 100_000), "ba6f84724fa48061", "SteelMeridianSyndicate");
|
pushSyndicateMissions(worldState, sday, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Archon Hunt cycling every week
|
// Archon Hunt cycling every week
|
||||||
@ -1178,16 +1184,14 @@ export const getLiteSortie = (week: number): ILiteSortie => {
|
|||||||
value.factionIndex < 2 &&
|
value.factionIndex < 2 &&
|
||||||
!isArchwingMission(value) &&
|
!isArchwingMission(value) &&
|
||||||
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
||||||
value.missionIndex != 23 && // Exclude junctions
|
|
||||||
value.missionIndex != 28 && // Exclude open worlds
|
|
||||||
value.missionIndex != 32 // Exclude railjack
|
value.missionIndex != 32 // Exclude railjack
|
||||||
) {
|
) {
|
||||||
nodes.push(key);
|
nodes.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seed = new SRng(week).randomInt(0, 100_000);
|
const seed = new CRng(week).randomInt(0, 0xffff);
|
||||||
const rng = new SRng(seed);
|
const rng = new CRng(seed);
|
||||||
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
|
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
|
||||||
const firstNode = nodes[firstNodeIndex];
|
const firstNode = nodes[firstNodeIndex];
|
||||||
nodes.splice(firstNodeIndex, 1);
|
nodes.splice(firstNodeIndex, 1);
|
||||||
@ -1242,3 +1246,20 @@ export const isArchwingMission = (node: IRegion): boolean => {
|
|||||||
}
|
}
|
||||||
return false;
|
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;
|
$oid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOidWithLegacySupport {
|
|
||||||
$oid?: string;
|
|
||||||
$id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMongoDate {
|
export interface IMongoDate {
|
||||||
$date: {
|
$date: {
|
||||||
$numberLong: string;
|
$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 { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
ICrewShipCustomization,
|
ICrewShipCustomization,
|
||||||
@ -92,7 +92,7 @@ export interface IEquipmentClient
|
|||||||
IEquipmentDatabase,
|
IEquipmentDatabase,
|
||||||
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
|
"_id" | "InfestationDate" | "Expiry" | "UpgradesExpiry" | "UmbraDate" | "CrewMembers" | "Details"
|
||||||
> {
|
> {
|
||||||
ItemId: IOidWithLegacySupport;
|
ItemId: IOid;
|
||||||
InfestationDate?: IMongoDate;
|
InfestationDate?: IMongoDate;
|
||||||
Expiry?: IMongoDate;
|
Expiry?: IMongoDate;
|
||||||
UpgradesExpiry?: IMongoDate;
|
UpgradesExpiry?: IMongoDate;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IOid, IMongoDate, IOidWithLegacySupport } from "../commonTypes";
|
import { IOid, IMongoDate } from "../commonTypes";
|
||||||
import {
|
import {
|
||||||
IColor,
|
IColor,
|
||||||
IItemConfig,
|
IItemConfig,
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
} from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||||
import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper";
|
import { IFingerprintStat, RivenFingerprint } from "@/src/helpers/rivenHelper";
|
||||||
import { IOrbiter } from "../personalRoomsTypes";
|
import { IOrbiter } from "../personalRoomsTypes";
|
||||||
import { ICountedStoreItem } from "warframe-public-export-plus";
|
|
||||||
|
|
||||||
export type InventoryDatabaseEquipment = {
|
export type InventoryDatabaseEquipment = {
|
||||||
[_ in TEquipmentKey]: IEquipmentDatabase[];
|
[_ in TEquipmentKey]: IEquipmentDatabase[];
|
||||||
@ -55,7 +54,6 @@ export interface IInventoryDatabase
|
|||||||
| "CrewMembers"
|
| "CrewMembers"
|
||||||
| "QualifyingInvasions"
|
| "QualifyingInvasions"
|
||||||
| "LastInventorySync"
|
| "LastInventorySync"
|
||||||
| "EndlessXP"
|
|
||||||
| TEquipmentKey
|
| TEquipmentKey
|
||||||
>,
|
>,
|
||||||
InventoryDatabaseEquipment {
|
InventoryDatabaseEquipment {
|
||||||
@ -94,7 +92,6 @@ export interface IInventoryDatabase
|
|||||||
CrewMembers: ICrewMemberDatabase[];
|
CrewMembers: ICrewMemberDatabase[];
|
||||||
QualifyingInvasions: IInvasionProgressDatabase[];
|
QualifyingInvasions: IInvasionProgressDatabase[];
|
||||||
LastInventorySync?: Types.ObjectId;
|
LastInventorySync?: Types.ObjectId;
|
||||||
EndlessXP?: IEndlessXpProgressDatabase[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyDatabase {
|
export interface IQuestKeyDatabase {
|
||||||
@ -359,7 +356,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
PendingCoupon?: IPendingCouponClient;
|
PendingCoupon?: IPendingCouponClient;
|
||||||
Harvestable: boolean;
|
Harvestable: boolean;
|
||||||
DeathSquadable: boolean;
|
DeathSquadable: boolean;
|
||||||
EndlessXP?: IEndlessXpProgressClient[];
|
EndlessXP?: IEndlessXpProgress[];
|
||||||
DialogueHistory?: IDialogueHistoryClient;
|
DialogueHistory?: IDialogueHistoryClient;
|
||||||
CalendarProgress?: ICalendarProgress;
|
CalendarProgress?: ICalendarProgress;
|
||||||
SongChallenges?: ISongChallenge[];
|
SongChallenges?: ISongChallenge[];
|
||||||
@ -374,7 +371,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
EchoesHexConquestCacheScoreMission?: number;
|
EchoesHexConquestCacheScoreMission?: number;
|
||||||
EchoesHexConquestActiveFrameVariants?: string[];
|
EchoesHexConquestActiveFrameVariants?: string[];
|
||||||
EchoesHexConquestActiveStickers?: string[];
|
EchoesHexConquestActiveStickers?: string[];
|
||||||
BrandedSuits?: IOidWithLegacySupport[];
|
BrandedSuits?: IOid[];
|
||||||
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
LockedWeaponGroup?: ILockedWeaponGroupClient;
|
||||||
HubNpcCustomizations?: IHubNpcCustomization[];
|
HubNpcCustomizations?: IHubNpcCustomization[];
|
||||||
Ship?: IOrbiter; // U22 and below, response only
|
Ship?: IOrbiter; // U22 and below, response only
|
||||||
@ -535,16 +532,6 @@ export interface IUpgradeDatabase extends Omit<IUpgradeClient, "ItemId"> {
|
|||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUpgradeFromClient {
|
|
||||||
ItemType: string;
|
|
||||||
ItemId: IOidWithLegacySupport;
|
|
||||||
FromSKU?: boolean;
|
|
||||||
UpgradeFingerprint: string;
|
|
||||||
PendingRerollFingerprint: string;
|
|
||||||
ItemCount: number;
|
|
||||||
LastAdded: IOidWithLegacySupport;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICrewShipMembersClient {
|
export interface ICrewShipMembersClient {
|
||||||
SLOT_A?: ICrewShipMemberClient;
|
SLOT_A?: ICrewShipMemberClient;
|
||||||
SLOT_B?: ICrewShipMemberClient;
|
SLOT_B?: ICrewShipMemberClient;
|
||||||
@ -863,8 +850,6 @@ export interface IMission extends IMissionDatabase {
|
|||||||
RewardsCooldownTime?: IMongoDate;
|
RewardsCooldownTime?: IMongoDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TNemesisFaction = "FC_GRINEER" | "FC_CORPUS" | "FC_INFESTATION";
|
|
||||||
|
|
||||||
export interface INemesisBaseClient {
|
export interface INemesisBaseClient {
|
||||||
fp: bigint | number;
|
fp: bigint | number;
|
||||||
manifest: string;
|
manifest: string;
|
||||||
@ -874,7 +859,7 @@ export interface INemesisBaseClient {
|
|||||||
WeaponIdx: number;
|
WeaponIdx: number;
|
||||||
AgentIdx: number;
|
AgentIdx: number;
|
||||||
BirthNode: string;
|
BirthNode: string;
|
||||||
Faction: TNemesisFaction;
|
Faction: string;
|
||||||
Rank: number;
|
Rank: number;
|
||||||
k: boolean;
|
k: boolean;
|
||||||
Traded: boolean;
|
Traded: boolean;
|
||||||
@ -1065,7 +1050,7 @@ export interface IQuestStage {
|
|||||||
export interface IRawUpgrade {
|
export interface IRawUpgrade {
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
ItemCount: number;
|
ItemCount: number;
|
||||||
LastAdded?: IOidWithLegacySupport;
|
LastAdded?: IOid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISeasonChallenge {
|
export interface ISeasonChallenge {
|
||||||
@ -1158,24 +1143,9 @@ export interface IEvolutionProgress {
|
|||||||
|
|
||||||
export type TEndlessXpCategory = "EXC_NORMAL" | "EXC_HARD";
|
export type TEndlessXpCategory = "EXC_NORMAL" | "EXC_HARD";
|
||||||
|
|
||||||
export interface IEndlessXpProgressDatabase {
|
export interface IEndlessXpProgress {
|
||||||
Category: TEndlessXpCategory;
|
Category: TEndlessXpCategory;
|
||||||
Earn: number;
|
|
||||||
Claim: number;
|
|
||||||
BonusAvailable?: Date;
|
|
||||||
Expiry?: Date;
|
|
||||||
Choices: string[];
|
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 {
|
export interface IDialogueHistoryClient {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
IPlayerSkills,
|
IPlayerSkills,
|
||||||
IQuestKeyDatabase,
|
IQuestKeyDatabase,
|
||||||
ILoreFragmentScan,
|
ILoreFragmentScan,
|
||||||
IUpgradeFromClient,
|
IUpgradeClient,
|
||||||
ICollectibleEntry,
|
ICollectibleEntry,
|
||||||
IDiscoveredMarker,
|
IDiscoveredMarker,
|
||||||
ILockedWeaponGroupClient,
|
ILockedWeaponGroupClient,
|
||||||
@ -111,7 +111,7 @@ export type IMissionInventoryUpdateRequest = {
|
|||||||
Standing: number;
|
Standing: number;
|
||||||
}[];
|
}[];
|
||||||
CollectibleScans?: ICollectibleEntry[];
|
CollectibleScans?: ICollectibleEntry[];
|
||||||
Upgrades?: IUpgradeFromClient[]; // riven challenge progress
|
Upgrades?: IUpgradeClient[]; // riven challenge progress
|
||||||
WeaponSkins?: IWeaponSkinClient[];
|
WeaponSkins?: IWeaponSkinClient[];
|
||||||
StrippedItems?: {
|
StrippedItems?: {
|
||||||
DropTable: string;
|
DropTable: string;
|
||||||
@ -177,7 +177,6 @@ export interface IRewardInfo {
|
|||||||
PurgatoryRewardQualifications?: string;
|
PurgatoryRewardQualifications?: string;
|
||||||
rewardSeed?: number | bigint;
|
rewardSeed?: number | bigint;
|
||||||
periodicMissionTag?: string;
|
periodicMissionTag?: string;
|
||||||
T?: number; // Duviri
|
|
||||||
ConquestType?: string;
|
ConquestType?: string;
|
||||||
ConquestCompleted?: number;
|
ConquestCompleted?: number;
|
||||||
ConquestEquipmentSuggestionsFulfilled?: number;
|
ConquestEquipmentSuggestionsFulfilled?: number;
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
<link rel="stylesheet" href="/webui/style.css" />
|
<link rel="stylesheet" href="/webui/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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">
|
<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>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand">OpenWF WebUI</a>
|
<a class="navbar-brand">OpenWF WebUI</a>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
<div class="container pt-3 pb-3" id="main-view">
|
<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-lg offcanvas-start" tabindex="-1" id="sidebar" aria-labelledby="sidebarLabel">
|
||||||
<div class="offcanvas-header">
|
<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>
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="offcanvas-body">
|
<div class="offcanvas-body">
|
||||||
@ -590,18 +590,10 @@
|
|||||||
<input class="form-check-input" type="checkbox" id="infiniteRegalAya" />
|
<input class="form-check-input" type="checkbox" id="infiniteRegalAya" />
|
||||||
<label class="form-check-label" for="infiniteRegalAya" data-loc="cheats_infiniteRegalAya"></label>
|
<label class="form-check-label" for="infiniteRegalAya" data-loc="cheats_infiniteRegalAya"></label>
|
||||||
</div>
|
</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">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
|
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
|
||||||
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
|
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
|
||||||
</div>
|
</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">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="dontSubtractConsumables" />
|
<input class="form-check-input" type="checkbox" id="dontSubtractConsumables" />
|
||||||
<label class="form-check-label" for="dontSubtractConsumables" data-loc="cheats_dontSubtractConsumables"></label>
|
<label class="form-check-label" for="dontSubtractConsumables" data-loc="cheats_dontSubtractConsumables"></label>
|
||||||
|
@ -29,13 +29,3 @@ td.text-end > a > svg {
|
|||||||
.card-body {
|
.card-body {
|
||||||
overflow: auto;
|
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_infiniteEndo: `Unendlich Endo`,
|
||||||
cheats_infiniteRegalAya: `Unendlich Reines Aya`,
|
cheats_infiniteRegalAya: `Unendlich Reines Aya`,
|
||||||
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
|
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_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
|
||||||
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
|
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
|
||||||
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
|
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
|
||||||
|
@ -130,8 +130,6 @@ dict = {
|
|||||||
cheats_infiniteEndo: `Infinite Endo`,
|
cheats_infiniteEndo: `Infinite Endo`,
|
||||||
cheats_infiniteRegalAya: `Infinite Regal Aya`,
|
cheats_infiniteRegalAya: `Infinite Regal Aya`,
|
||||||
cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`,
|
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_dontSubtractConsumables: `Don't Subtract Consumables`,
|
||||||
cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
|
cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
|
||||||
cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
|
cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
|
||||||
|
@ -131,8 +131,6 @@ dict = {
|
|||||||
cheats_infiniteEndo: `Endo infinito`,
|
cheats_infiniteEndo: `Endo infinito`,
|
||||||
cheats_infiniteRegalAya: `Aya Real infinita`,
|
cheats_infiniteRegalAya: `Aya Real infinita`,
|
||||||
cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`,
|
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_dontSubtractConsumables: `No restar consumibles`,
|
||||||
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
|
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
|
||||||
cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
|
cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
|
||||||
|
@ -131,8 +131,6 @@ dict = {
|
|||||||
cheats_infiniteEndo: `Endo infini`,
|
cheats_infiniteEndo: `Endo infini`,
|
||||||
cheats_infiniteRegalAya: `Aya Raffiné infini`,
|
cheats_infiniteRegalAya: `Aya Raffiné infini`,
|
||||||
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
|
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
|
||||||
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`,
|
|
||||||
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`,
|
|
||||||
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
|
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
|
||||||
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
|
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
|
||||||
cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
|
cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
|
||||||
|
@ -131,8 +131,6 @@ dict = {
|
|||||||
cheats_infiniteEndo: `Бесконечное эндо`,
|
cheats_infiniteEndo: `Бесконечное эндо`,
|
||||||
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
|
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
|
||||||
cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`,
|
cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`,
|
||||||
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`,
|
|
||||||
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`,
|
|
||||||
cheats_dontSubtractConsumables: `Не уменьшать количество расходников`,
|
cheats_dontSubtractConsumables: `Не уменьшать количество расходников`,
|
||||||
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
|
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
|
||||||
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
|
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
|
||||||
|
@ -131,8 +131,6 @@ dict = {
|
|||||||
cheats_infiniteEndo: `无限内融核心`,
|
cheats_infiniteEndo: `无限内融核心`,
|
||||||
cheats_infiniteRegalAya: `无限御品阿耶`,
|
cheats_infiniteRegalAya: `无限御品阿耶`,
|
||||||
cheats_infiniteHelminthMaterials: `无限Helminth材料`,
|
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_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
|
||||||
cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
|
cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
|
||||||
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
|
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user