forked from OpenWF/SpaceNinjaServer
		
	
		
			
				
	
	
		
			131 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { TRarity } from "warframe-public-export-plus";
 | 
						|
 | 
						|
export interface IRngResult {
 | 
						|
    type: string;
 | 
						|
    itemCount: number;
 | 
						|
    probability: number;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
};
 | 
						|
 | 
						|
const getRewardAtPercentage = <T extends { probability: number }>(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 = <T extends { probability: number }>(pool: T[]): T | undefined => {
 | 
						|
    return getRewardAtPercentage(pool, Math.random());
 | 
						|
};
 | 
						|
 | 
						|
export const getRandomWeightedReward = <T extends { rarity: TRarity }>(
 | 
						|
    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);
 | 
						|
};
 | 
						|
 | 
						|
export const getRandomWeightedRewardUc = <T extends { Rarity: TRarity }>(
 | 
						|
    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);
 | 
						|
};
 | 
						|
 | 
						|
// 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<T>(arr: T[]): T {
 | 
						|
        return arr[Math.floor(this.random() * arr.length)];
 | 
						|
    }
 | 
						|
 | 
						|
    randomReward<T extends { probability: number }>(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;
 | 
						|
    }
 | 
						|
}
 |