2024-07-03 12:30:32 +02:00
|
|
|
import { TRarity } from "warframe-public-export-plus";
|
|
|
|
|
|
|
|
export interface IRngResult {
|
|
|
|
type: string;
|
|
|
|
itemCount: number;
|
|
|
|
probability: number;
|
|
|
|
}
|
|
|
|
|
2025-01-06 05:35:57 +01:00
|
|
|
export const getRandomElement = <T>(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;
|
|
|
|
};
|
|
|
|
|
2025-03-21 05:19:42 -07:00
|
|
|
const getRewardAtPercentage = <T extends { probability: number }>(pool: T[], percentage: number): T | undefined => {
|
2024-07-03 12:30:32 +02:00
|
|
|
if (pool.length == 0) return;
|
|
|
|
|
|
|
|
const totalChance = pool.reduce((accum, item) => accum + item.probability, 0);
|
2025-03-21 05:19:42 -07:00
|
|
|
const randomValue = percentage * totalChance;
|
2024-07-03 12:30:32 +02:00
|
|
|
|
|
|
|
let cumulativeChance = 0;
|
|
|
|
for (const item of pool) {
|
|
|
|
cumulativeChance += item.probability;
|
|
|
|
if (randomValue <= cumulativeChance) {
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new Error("What the fuck?");
|
|
|
|
};
|
|
|
|
|
2025-03-21 05:19:42 -07:00
|
|
|
export const getRandomReward = <T extends { probability: number }>(pool: T[]): T | undefined => {
|
|
|
|
return getRewardAtPercentage(pool, Math.random());
|
|
|
|
};
|
|
|
|
|
2025-03-03 12:48:46 -08:00
|
|
|
export const getRandomWeightedReward = <T extends { rarity: TRarity }>(
|
|
|
|
pool: T[],
|
2025-01-06 05:35:36 +01:00
|
|
|
weights: Record<TRarity, number>
|
2025-03-03 12:48:46 -08:00
|
|
|
): (T & { probability: number }) | undefined => {
|
|
|
|
const resultPool: (T & { probability: number })[] = [];
|
2025-01-06 05:35:36 +01:00
|
|
|
const rarityCounts: Record<TRarity, number> = { COMMON: 0, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
|
|
|
|
for (const entry of pool) {
|
|
|
|
++rarityCounts[entry.rarity];
|
|
|
|
}
|
|
|
|
for (const entry of pool) {
|
|
|
|
resultPool.push({
|
2025-03-03 12:48:46 -08:00
|
|
|
...entry,
|
2025-01-06 05:35:36 +01:00
|
|
|
probability: weights[entry.rarity] / rarityCounts[entry.rarity]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return getRandomReward(resultPool);
|
|
|
|
};
|
2025-03-03 05:48:46 -08:00
|
|
|
|
2025-03-03 12:48:46 -08:00
|
|
|
export const getRandomWeightedRewardUc = <T extends { Rarity: TRarity }>(
|
2025-03-03 05:48:46 -08:00
|
|
|
pool: T[],
|
|
|
|
weights: Record<TRarity, number>
|
|
|
|
): (T & { probability: number }) | undefined => {
|
|
|
|
const resultPool: (T & { probability: number })[] = [];
|
|
|
|
const rarityCounts: Record<TRarity, number> = { 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);
|
|
|
|
};
|
2025-03-16 04:32:57 -07:00
|
|
|
|
2025-03-21 05:19:42 -07:00
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
|
2025-03-20 05:36:09 -07:00
|
|
|
// Seeded RNG for internal usage. Based on recommendations in the ISO C standards.
|
2025-03-16 04:32:57 -07:00
|
|
|
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<T>(arr: T[]): T {
|
|
|
|
return arr[Math.floor(this.random() * arr.length)];
|
|
|
|
}
|
2025-03-21 05:19:42 -07:00
|
|
|
|
|
|
|
randomReward<T extends { probability: number }>(pool: T[]): T | undefined {
|
|
|
|
return getRewardAtPercentage(pool, this.random());
|
|
|
|
}
|
2025-04-14 07:13:36 -07:00
|
|
|
|
|
|
|
churnSeed(its: number): void {
|
|
|
|
while (its--) {
|
|
|
|
this.state = (this.state * 1103515245 + 12345) & 0x7fffffff;
|
|
|
|
}
|
|
|
|
}
|
2025-03-16 04:32:57 -07:00
|
|
|
}
|
2025-03-20 05:36:09 -07:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
2025-04-01 15:49:08 -07:00
|
|
|
|
|
|
|
randomElement<T>(arr: T[]): T {
|
|
|
|
return arr[this.randomInt(0, arr.length - 1)];
|
|
|
|
}
|
|
|
|
|
|
|
|
randomFloat(): number {
|
|
|
|
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
|
|
|
|
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;
|
|
|
|
}
|
2025-03-20 05:36:09 -07:00
|
|
|
}
|