Source

SlayTheSpireAPI/APIComponents/ModificationHelper.ts

/* eslint-disable @typescript-eslint/no-explicit-any */
import java from 'java';

import { AbstractAPIComponent } from './AbstractAPIComponent';
import { PlayableCharacter } from '../MasterLists/PlayableCharacters';
import { Random } from './Random';
import {
    ModificationType, Modification, MasterModificationList, ModificationMap,
} from '../MasterLists/Modifications';

/**
 * Modifications for the daily climb
 * @type DailyModifications
 */
export interface DailyModifications {
    starter: Modification | undefined;
    generic: Modification | undefined;
    difficulty: Modification | undefined;
}

/**
 * Helper to handle daily climb modifications
 */
export class ModificationHelper extends AbstractAPIComponent {
    private static readonly rotationConstant = 5;

    private readonly _seed: number;

    private readonly _character: PlayableCharacter;

    private _starterMods: any;

    private _genericMods: any;

    private _difficultyMods: any;

    private _legacyMods: any;

    /**
     *
     * @param {string} stsJarPath Path to the `desktop-1.0.jar` Slay the Spire game file
     * @param {Random} rng Random number generator
     * @param {PlayableCharacter} character Selected character for the daily climb
     *
     * @example
     * const rng: Random = new Random('./path/to/stsJar', 1337);
     * const modHelper: ModificationHelper = new ModificationHelper('./path/to/stsJar',
     *      rng, PlayableCharacter.Ironclad);
     */
    constructor(stsJarPath: string, rng: Random, character: PlayableCharacter) {
        super(stsJarPath);

        this._seed = rng.seed;
        this._character = character;

        this._starterMods = java.newInstanceSync('java.util.HashMap');
        this._genericMods = java.newInstanceSync('java.util.HashMap');
        this._difficultyMods = java.newInstanceSync('java.util.HashMap');
        this._legacyMods = java.newInstanceSync('java.util.HashMap');

        MasterModificationList.forEach((modification) => {
            switch (modification.type) {
                case ModificationType.STARTER:
                    this._starterMods.putSync(modification.id, modification.id);
                    break;

                case ModificationType.GENERIC:
                    this._genericMods.putSync(modification.id, modification.id);
                    break;

                case ModificationType.DIFFICULTY:
                    this._difficultyMods.putSync(
                        modification.id,
                        modification.id,
                    );
                    break;

                case ModificationType.LEGACY:
                    this._legacyMods.putSync(modification.id, modification.id);
                    break;

                default:
                    throw Error(`Unable to add mod: ${modification.id}`);
            }
        });
    }

    /**
     * The modifications for the daily climb
     * @type {DailyModifications}
     *
     * @example
     * const rng: Random = new Random('./path/to/stsJar', 1337);
     * const modHelper: ModificationHelper = new ModificationHelper('./path/to/stsJar',
     *      rng, PlayableCharacter.Ironclad);
     *
     * const todaysMods: DailyModifications = modHelper.modifications;
     */
    get modifications(): DailyModifications {
        return {
            starter: ModificationMap.get(
                this.getModification(this._starterMods),
            ),
            generic: ModificationMap.get(
                this.getModification(this._genericMods),
            ),
            difficulty: ModificationMap.get(
                this.getModification(this._difficultyMods),
            ),
        };
    }

    /**
     * @private
     *
     * Choose a random {@link Modification} ID from a list of {@link Modification}s
     * @param modificationList list of {@link Modification}s to choose from
     * @returns {string}
     */
    private getModification(modificationList: any): string {
        const shuffledList = java.newInstanceSync('java.util.ArrayList');

        const modificationArray: Array<string> = modificationList
            .entrySetSync()
            .toArraySync()
            .map((mod: any) => mod.toStringSync().replace(/=.*/g, ''));

        const character: PlayableCharacter = this._character;
        modificationArray.forEach((modificationId: string) => {
            const modification = ModificationMap.get(modificationId);
            if (modification && modification.exclude !== character) {
                shuffledList.addSync(modificationId);
            }
        });

        let modSelectionIndex = this._seed % ModificationHelper.rotationConstant;
        if (modSelectionIndex < 0) modSelectionIndex += ModificationHelper.rotationConstant;

        const shuffleInterval = Math.floor(
            this._seed / ModificationHelper.rotationConstant,
        );

        const shuffleRng = java.newInstanceSync(
            'java.util.Random',
            shuffleInterval,
        );
        java.callStaticMethodSync(
            'java.util.Collections',
            'shuffle',
            shuffledList,
            shuffleRng,
        );

        return shuffledList.toArraySync()[modSelectionIndex];
    }
}