fix: generate rewards based on RewardSeed to match what's show in client (#1628)

Reviewed-on: OpenWF/SpaceNinjaServer#1628
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-04-15 09:46:08 -07:00 committed by Sainan
parent d28437b658
commit 3f0a2bec48
6 changed files with 23 additions and 9 deletions

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import {
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
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, getRandomElement, getRandomReward } from "@/src/services/rngService"; import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/services/rngService";
import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { equipmentKeys, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { import {
addBooster, addBooster,
@ -31,6 +31,7 @@ import {
addShipDecorations, addShipDecorations,
addStanding, addStanding,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed,
updateCurrency, updateCurrency,
updateSyndicate updateSyndicate
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
@ -70,7 +71,12 @@ const getRotations = (rotationCount: number, tierOverride: number | undefined):
return rotatedValues; return rotatedValues;
}; };
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => { 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;
}
return getRandomReward(pool as IRngResult[]); return getRandomReward(pool as IRngResult[]);
}; };
@ -548,6 +554,11 @@ export const addMissionRewards = async (
return { MissionRewards: [] }; 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 //TODO: check double reward merging
const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier); const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier);
logger.debug("random mission drops:", MissionRewards); logger.debug("random mission drops:", MissionRewards);
@ -1062,6 +1073,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
if (rewardManifests.length != 0) { if (rewardManifests.length != 0) {
logger.debug(`generating random mission rewards`, { rewardManifests, rotations }); logger.debug(`generating random mission rewards`, { rewardManifests, rotations });
} }
const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn);
rewardManifests.forEach(name => { rewardManifests.forEach(name => {
const table = ExportRewards[name]; const table = ExportRewards[name];
if (!table) { if (!table) {
@ -1070,7 +1082,7 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
} }
for (const rotation of rotations) { for (const rotation of rotations) {
const rotationRewards = table[rotation]; const rotationRewards = table[rotation];
const drop = getRandomRewardByChance(rotationRewards); const drop = getRandomRewardByChance(rotationRewards, rng);
if (drop) { if (drop) {
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount }); 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 item;
} }
} }
throw new Error("What the fuck?"); return pool[pool.length - 1];
}; };
export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => { export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
@ -142,4 +142,8 @@ export class SRng {
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn; this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645; 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; Mailbox?: IMailboxClient;
SubscribedToEmails: number; SubscribedToEmails: number;
Created: IMongoDate; Created: IMongoDate;
RewardSeed: number; RewardSeed: number | bigint;
RegularCredits: number; RegularCredits: number;
PremiumCredits: number; PremiumCredits: number;
PremiumCreditsFree: number; PremiumCreditsFree: number;

View File

@ -141,7 +141,7 @@ export interface IRewardInfo {
EOM_AFK?: number; EOM_AFK?: number;
rewardQualifications?: string; // did a Survival for 5 minutes and this was "1" rewardQualifications?: string; // did a Survival for 5 minutes and this was "1"
PurgatoryRewardQualifications?: string; PurgatoryRewardQualifications?: string;
rewardSeed?: number; rewardSeed?: number | bigint;
periodicMissionTag?: string; periodicMissionTag?: string;
// for bounties, only EOM_AFK and node are given from above, plus: // for bounties, only EOM_AFK and node are given from above, plus: