import { TRarity } from "warframe-public-export-plus"; export interface IRngResult { type: string; itemCount: number; probability: number; } export const getRandomElement = (arr: T[]): T => { return arr[Math.floor(Math.random() * arr.length)]; }; // Returns a random integer between min (inclusive) and max (inclusive). // https://stackoverflow.com/a/1527820 export const getRandomInt = (min: number, max: number): number => { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; }; const getRewardAtPercentage = (pool: T[], percentage: number): T | undefined => { if (pool.length == 0) return; const totalChance = pool.reduce((accum, item) => accum + item.probability, 0); const randomValue = percentage * totalChance; let cumulativeChance = 0; for (const item of pool) { cumulativeChance += item.probability; if (randomValue <= cumulativeChance) { return item; } } throw new Error("What the fuck?"); }; export const getRandomReward = (pool: T[]): T | undefined => { return getRewardAtPercentage(pool, Math.random()); }; export const getRandomWeightedReward = ( pool: T[], weights: Record ): (T & { probability: number }) | undefined => { const resultPool: (T & { probability: number })[] = []; const rarityCounts: Record = { COMMON: 0, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 }; for (const entry of pool) { ++rarityCounts[entry.rarity]; } for (const entry of pool) { resultPool.push({ ...entry, probability: weights[entry.rarity] / rarityCounts[entry.rarity] }); } return getRandomReward(resultPool); }; export const getRandomWeightedRewardUc = ( pool: T[], weights: Record ): (T & { probability: number }) | undefined => { const resultPool: (T & { probability: number })[] = []; const rarityCounts: Record = { COMMON: 0, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 }; for (const entry of pool) { ++rarityCounts[entry.Rarity]; } for (const entry of pool) { resultPool.push({ ...entry, probability: weights[entry.Rarity] / rarityCounts[entry.Rarity] }); } return getRandomReward(resultPool); }; // ChatGPT generated this. It seems to have a good enough distribution. export const mixSeeds = (seed1: number, seed2: number): number => { let seed = seed1 ^ seed2; seed ^= seed >>> 21; seed ^= seed << 35; seed ^= seed >>> 4; return seed >>> 0; }; // 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 { min = Math.ceil(min); max = Math.floor(max); return Math.floor(this.random() * (max - min + 1)) + min; } randomElement(arr: T[]): T { return arr[Math.floor(this.random() * arr.length)]; } randomReward(pool: T[]): T | undefined { return getRewardAtPercentage(pool, this.random()); } } // Seeded RNG for cases where we need identical results to the game client. Based on work by Donald Knuth. export class SRng { state: bigint; constructor(seed: bigint) { this.state = seed; } randomInt(min: number, max: number): number { const diff = max - min; if (diff != 0) { this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn; min += (Number(this.state >> 32n) & 0x3fffffff) % (diff + 1); } return min; } }