Compare commits

..

2 Commits

Author SHA1 Message Date
c7034b5e6d make rewardInfo an optional argument
All checks were successful
Build / build (push) Successful in 1m24s
Build / build (pull_request) Successful in 42s
2025-04-15 00:15:37 +02:00
76b6bc3b61 fix: respect VaultsCracked when rolling droptable for level key rewards
Some checks failed
Build / build (push) Failing after 19s
Build / build (pull_request) Failing after 49s
2025-04-15 00:13:57 +02:00
12 changed files with 44 additions and 133 deletions

View File

@ -1,12 +1,14 @@
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { generateRewardSeed } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const rewardSeed = generateRewardSeed();
logger.debug(`generated new reward seed: ${rewardSeed}`);
await Inventory.updateOne(
{
accountOwnerId: accountId

View File

@ -1,20 +0,0 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
addLoreFragmentScans(inventory, data.LoreFragmentScans);
addShipDecorations(inventory, data.ShipDecorations);
await inventory.save();
res.end();
};
interface IGiveShipDecoAndLoreFragmentRequest {
LoreFragmentScans: ILoreFragmentScan[];
ShipDecorations: ITypeCount[];
}

View File

@ -1,5 +1,5 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
import { RequestHandler } from "express";
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
@ -7,17 +7,5 @@ export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
await handleSetPlacedDecoInfo(accountId, payload);
res.json({
DecoId: payload.DecoId,
IsPicture: true,
PictureFrameInfo: payload.PictureFrameInfo,
BootLocation: payload.BootLocation
} satisfies ISetPlacedDecoInfoResponse);
res.end();
};
interface ISetPlacedDecoInfoResponse {
DecoId: string;
IsPicture: boolean;
PictureFrameInfo?: IPictureFrameInfo;
BootLocation?: string;
}

View File

@ -25,13 +25,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
) {
updateCurrency(inventory, 10, true);
} else if (
operation.OperationType != "UOT_SWAP_POLARITY" &&
operation.OperationType != "UOT_ABILITY_OVERRIDE"
) {
if (!operation.UpgradeRequirement) {
throw new Error(`${operation.OperationType} operation should be free?`);
}
} else if (operation.OperationType != "UOT_ABILITY_OVERRIDE") {
addMiscItems(inventory, [
{
ItemType: operation.UpgradeRequirement,

View File

@ -1213,7 +1213,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
accountOwnerId: Schema.Types.ObjectId,
SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
RewardSeed: BigInt,
RewardSeed: Number,
//Credit
RegularCredits: { type: Number, default: 0 },

View File

@ -62,7 +62,6 @@ import { gildWeaponController } from "@/src/controllers/api/gildWeaponController
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController";
import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey";
import { giveShipDecoAndLoreFragmentController } from "@/src/controllers/api/giveShipDecoAndLoreFragmentController";
import { giveStartingGearController } from "@/src/controllers/api/giveStartingGearController";
import { guildTechController } from "@/src/controllers/api/guildTechController";
import { hostSessionController } from "@/src/controllers/api/hostSessionController";
@ -240,7 +239,6 @@ apiRouter.post("/gildWeapon.php", gildWeaponController);
apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController);
apiRouter.post("/giveKeyChainTriggeredMessage.php", giveKeyChainTriggeredMessageController);
apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController);
apiRouter.post("/giveShipDecoAndLoreFragment.php", giveShipDecoAndLoreFragmentController);
apiRouter.post("/giveStartingGear.php", giveStartingGearController);
apiRouter.post("/guildTech.php", guildTechController);
apiRouter.post("/hostSession.php", hostSessionController);

View File

@ -21,8 +21,7 @@ import {
ICalendarProgress,
IDroneClient,
IUpgradeClient,
TPartialStartingGear,
ILoreFragmentScan
TPartialStartingGear
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -1351,17 +1350,6 @@ export const addFocusXpIncreases = (inventory: TInventoryDatabaseDocument, focus
inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0);
};
export const addLoreFragmentScans = (inventory: TInventoryDatabaseDocument, arr: ILoreFragmentScan[]): void => {
arr.forEach(clientFragment => {
const fragment = inventory.LoreFragmentScans.find(x => x.ItemType == clientFragment.ItemType);
if (fragment) {
fragment.Progress += clientFragment.Progress;
} else {
inventory.LoreFragmentScans.push(clientFragment);
}
});
};
export const addChallenges = (
inventory: TInventoryDatabaseDocument,
ChallengeProgress: IChallengeProgress[],

View File

@ -9,7 +9,7 @@ import {
} from "warframe-public-export-plus";
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
import { logger } from "@/src/utils/logger";
import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
import { IRngResult, getRandomElement, getRandomReward } from "@/src/services/rngService";
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import {
addBooster,
@ -23,7 +23,6 @@ import {
addGearExpByCategory,
addItem,
addLevelKeys,
addLoreFragmentScans,
addMiscItems,
addMissionComplete,
addMods,
@ -31,7 +30,6 @@ import {
addShipDecorations,
addStanding,
combineInventoryChanges,
generateRewardSeed,
updateCurrency,
updateSyndicate
} from "@/src/services/inventoryService";
@ -55,22 +53,7 @@ import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getWorldState } from "./worldStateService";
const getRotations = (rewardInfo: IRewardInfo, tierOverride: number | undefined): number[] => {
// For Spy missions, e.g. 3 vaults cracked = A, B, C
if (rewardInfo.VaultsCracked) {
const rotations: number[] = [];
for (let i = 0; i != rewardInfo.VaultsCracked; ++i) {
rotations.push(i);
}
return rotations;
}
// For Rescue missions
if (rewardInfo.rewardTier) {
return [rewardInfo.rewardTier];
}
const rotationCount = rewardInfo.rewardQualifications?.length || 0;
const getRotations = (rotationCount: number, tierOverride: number | undefined): number[] => {
if (rotationCount === 0) return [0];
const rotationPattern =
@ -86,12 +69,7 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride: number | undefined)
return rotatedValues;
};
const getRandomRewardByChance = (pool: IReward[], rng?: SRng): IRngResult | undefined => {
if (rng) {
const res = rng.randomReward(pool as IRngResult[]);
rng.randomFloat(); // something related to rewards multiplier
return res;
}
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
return getRandomReward(pool as IRngResult[]);
};
@ -313,7 +291,14 @@ export const addMissionInventoryUpdates = async (
break;
}
case "LoreFragmentScans":
addLoreFragmentScans(inventory, value);
value.forEach(clientFragment => {
const fragment = inventory.LoreFragmentScans.find(x => x.ItemType == clientFragment.ItemType);
if (fragment) {
fragment.Progress += clientFragment.Progress;
} else {
inventory.LoreFragmentScans.push(clientFragment);
}
});
break;
case "LibraryScans":
value.forEach(scan => {
@ -569,11 +554,6 @@ export const addMissionRewards = async (
return { MissionRewards: [] };
}
if (rewardInfo.rewardSeed) {
// We're using a reward seed, so give the client a new one in the response. On live, missionInventoryUpdate seems to always provide a fresh one in the response.
inventory.RewardSeed = generateRewardSeed();
}
//TODO: check double reward merging
const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier);
logger.debug("random mission drops:", MissionRewards);
@ -612,14 +592,7 @@ export const addMissionRewards = async (
const node = ExportRegions[missions.Tag];
//node based credit rewards for mission completion
if (
node.missionIndex != 23 && // junction
node.missionIndex != 28 && // open world
missions.Tag != "SolNode761" && // the index
missions.Tag != "SolNode762" && // the index
missions.Tag != "SolNode763" && // the index
missions.Tag != "CrewBattleNode556" // free flight
) {
if (node.missionIndex !== 28) {
const levelCreditReward = getLevelCreditRewards(node);
missionCompletionCredits += levelCreditReward;
inventory.RegularCredits += levelCreditReward;
@ -937,12 +910,6 @@ function getLevelCreditRewards(node: IRegion): number {
function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | undefined): IMissionReward[] {
const drops: IMissionReward[] = [];
if (RewardInfo.periodicMissionTag?.startsWith("HardDaily")) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/SteelEssence",
ItemCount: 5
});
}
if (RewardInfo.node in ExportRegions) {
const region = ExportRegions[RewardInfo.node];
let rewardManifests: string[] =
@ -967,7 +934,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
if (syndicateEntry.Tag === "EntratiSyndicate") {
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
if (vault && locationTag) job = vault;
if (vault) job = vault;
// if (
// [
// "DeimosRuinsExterminateBounty",
@ -1042,15 +1009,12 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&
!isEndlessJob
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ExportRewards[job.rewards]) {
rewardManifests.push(job.rewards);
rotations.push(ExportRewards[job.rewards].length - 1);
}
}
}
}
}
} else if (RewardInfo.challengeMissionId) {
const rewardTables: Record<string, string[]> = {
EntratiLabSyndicate: [
@ -1089,23 +1053,24 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
} else {
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
}
} else if (RewardInfo.VaultsCracked) {
// For Spy missions, e.g. 3 vaults cracked = A, B, C
for (let i = 0; i != RewardInfo.VaultsCracked; ++i) {
rotations.push(i);
}
} else {
rotations = getRotations(RewardInfo, tierOverride);
const rotationCount = RewardInfo.rewardQualifications?.length || 0;
rotations = getRotations(rotationCount, tierOverride);
}
if (rewardManifests.length != 0) {
logger.debug(`generating random mission rewards`, { rewardManifests, rotations });
}
const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn);
rewardManifests.forEach(name => {
const table = ExportRewards[name];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!table) {
logger.error(`unknown droptable: ${name}`);
return;
}
rewardManifests
.map(name => ExportRewards[name])
.forEach(table => {
for (const rotation of rotations) {
const rotationRewards = table[rotation];
const drop = getRandomRewardByChance(rotationRewards, rng);
const drop = getRandomRewardByChance(rotationRewards);
if (drop) {
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
}

View File

@ -31,7 +31,7 @@ const getRewardAtPercentage = <T extends { probability: number }>(pool: T[], per
return item;
}
}
return pool[pool.length - 1];
throw new Error("What the fuck?");
};
export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
@ -142,8 +142,4 @@ export class SRng {
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
}
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
return getRewardAtPercentage(pool, this.randomFloat());
}
}

View File

@ -194,7 +194,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Mailbox?: IMailboxClient;
SubscribedToEmails: number;
Created: IMongoDate;
RewardSeed: number | bigint;
RewardSeed: number;
RegularCredits: number;
PremiumCredits: number;
PremiumCreditsFree: number;

View File

@ -130,7 +130,7 @@ export type IMissionInventoryUpdateRequest = {
export interface IRewardInfo {
node: string;
VaultsCracked?: number; // for Spy missions
rewardTier?: number; // for Rescue missions
rewardTier?: number;
nightmareMode?: boolean;
useVaultManifest?: boolean;
EnemyCachesFound?: number;
@ -141,7 +141,7 @@ export interface IRewardInfo {
EOM_AFK?: number;
rewardQualifications?: string; // did a Survival for 5 minutes and this was "1"
PurgatoryRewardQualifications?: string;
rewardSeed?: number | bigint;
rewardSeed?: number;
periodicMissionTag?: string;
// for bounties, only EOM_AFK and node are given from above, plus:

View File

@ -139,7 +139,7 @@ dict = {
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`,
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,