Skip to content

Commit

Permalink
Track completed Spooky Shuffle boards
Browse files Browse the repository at this point in the history
  • Loading branch information
hymccord committed Oct 12, 2023
1 parent cee8e70 commit 087cc7c
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as detailingFuncs from './modules/details/legacy';
new successHandlers.KingsGiveawayAjaxHandler(logger, submitConvertible),
new successHandlers.SBFactoryAjaxHandler(logger, submitConvertible),
new successHandlers.SEHAjaxHandler(logger, submitConvertible),
new successHandlers.SpookyShuffleAjaxHandler(logger, submitConvertible),
];

async function main() {
Expand Down
1 change: 1 addition & 0 deletions src/scripts/modules/ajax-handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./golem";
export * from "./kingsGiveaway";
export * from "./springEggHunt";
export * from "./sbFactory";
export * from "./spookyShuffle";
151 changes: 151 additions & 0 deletions src/scripts/modules/ajax-handlers/spookyShuffle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {AjaxSuccessHandler} from "./ajaxSuccessHandler";
import {HgItem} from "@scripts/types/mhct";
import {LoggerService} from "@scripts/util/logger";
import {SpookyShuffleResponse, TitleRange} from "./spookyShuffle.types";
import {CustomConvertibleIds} from "@scripts/util/constants";
import {parseHgInt} from "@scripts/util/number";
import * as hgFuncs from "@scripts/util/hgFunctions";

export class SpookyShuffleAjaxHandler extends AjaxSuccessHandler {
/**
* Create a new instance of SpookyShuffleAjaxHandler
* @param logger logger to log events
* @param submitConvertibleCallback delegate to submit convertibles to mhct
*/
constructor(
private logger: LoggerService,
private submitConvertibleCallback: (convertible: HgItem, items: HgItem[]) => void) {
super();
this.logger = logger;
this.submitConvertibleCallback = submitConvertibleCallback;
}

match(url: string): boolean {
if (!url.includes("mousehuntgame.com/managers/ajax/events/spooky_shuffle.php")) {
return false;
}

return true;
}

async execute(responseJSON: unknown): Promise<void> {
if (!this.isSpookyShuffleResponse(responseJSON)) {
this.logger.warn("Unexpected spooky shuffle response.", responseJSON);
return;
}

await this.recordBoard(responseJSON);
}

/**
* Record complete Spooky Shuffle Board as convertible in MHCT
* @param responseJSON
*/
async recordBoard(responseJSON: SpookyShuffleResponse) {
const result = responseJSON.memory_game;

if (!result.is_complete || !result.has_selected_testing_pair) {
this.logger.debug('Spooky Shuffle board is not complete yet.');
return;
}


const convertibleContent: HgItem[] = [];
const processed = new Set<string>();
try {
// We don't know the item id's of the cards. Hit MH inventory API to get the item names and ids
// to convert the names to ids.
const itemMap = await this.fetchItemNameToIdMap();

result.cards.forEach(c => {
// All cards need to be revealed and also exist in name to id map.
// An error here typically means:
// 1. Didn't fetch the required item classification
// 2. The item isn't in the users inventory (unlikely unless the user used it between start and finish of the board).
if (!c.is_revealed || itemMap[c.name] == null) {
throw new Error(`Item '${c.name}' wasn't found in item map. Check its classification type`);
}

// Two of each card means we don't want to double process the same item
if (processed.has(c.name)) {
return;
}
processed.add(c.name);

convertibleContent.push({
id: itemMap[c.name],
name: c.name,
quantity: c.quantity,
});
});
} catch (error) {
if (error instanceof Error) {
this.logger.warn(error.message);
return;
}
}

const id = result.is_upgraded
? SpookyShuffleAjaxHandler.UpgradedShuffleConvertibleIds[result.title_range]
: SpookyShuffleAjaxHandler.ShuffleConvertibleIds[result.title_range];
const tierName = result.reward_tiers.find(r => r.type == result.title_range)?.name;

// Example convertible names:
// Spooky Shuffle (Novice to Journyperson)
// Upgraded Spooky Shuffle (Grand Duke and up)
let convertibleName = `Spooky Shuffle (${tierName})`;
if (result.is_upgraded) {
convertibleName = `Upgraded ${convertibleName}`;
}

const convertible: HgItem = {
id: id,
name: convertibleName,
quantity: 1,
};

this.logger.debug("Shuffle Board: ", {convertible, items: convertibleContent});
this.submitConvertibleCallback(convertible, convertibleContent);
}

async fetchItemNameToIdMap(): Promise<Record<string, number>> {
// async fetch of all items that are of the same classification of the rewards in spooky shuffle
const itemArray = await hgFuncs.getItemsByClass(['bait', 'stat', 'trinket', 'crafting_item'], true);

const itemMap = itemArray.reduce((map: Record<string, number>, item) => {
map[item.name] = parseHgInt(item.item_id);
return map;
}, {});

// Gold is never returned as an item so need to add manually
itemMap.Gold = 431;

return itemMap;
}

/**
* Validates that the given object is a JSON response from interacting with spooky shuffle board
* @param responseJSON
* @returns
*/
private isSpookyShuffleResponse(responseJSON: unknown): responseJSON is SpookyShuffleResponse {
const resultKey: keyof SpookyShuffleResponse = 'memory_game';
return responseJSON != null &&
typeof responseJSON === 'object' &&
resultKey in responseJSON;
}

static ShuffleConvertibleIds: Record<TitleRange, number> = {
novice_journeyman: CustomConvertibleIds.HalloweenSpookyShuffleNovice,
master_lord: CustomConvertibleIds.HalloweenSpookyShuffleMaster,
baron_duke: CustomConvertibleIds.HalloweenSpookyShuffleBaron,
grand_duke_plus: CustomConvertibleIds.HalloweenSpookyShuffleGrandDuke,
};

static UpgradedShuffleConvertibleIds: Record<TitleRange, number> = {
novice_journeyman: CustomConvertibleIds.HalloweenSpookyShuffleNoviceDusted,
master_lord: CustomConvertibleIds.HalloweenSpookyShuffleMasterDusted,
baron_duke: CustomConvertibleIds.HalloweenSpookyShuffleBaronDusted,
grand_duke_plus: CustomConvertibleIds.HalloweenSpookyShuffleGrandDukeDusted,
};
}
47 changes: 47 additions & 0 deletions src/scripts/modules/ajax-handlers/spookyShuffle.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {HgResponse} from "@scripts/types/hg";

export interface SpookyShuffleResponse extends HgResponse {
memory_game: SpookyShuffleStatus;
}

export interface SpookyShuffleStatus {
is_complete: true | null;
is_upgraded: true | null;
has_selected_testing_pair: boolean; // true on selection of the 2nd card
reward_tiers: RewardTier[];
title_range: TitleRange;
cards: Card[];
}

interface RewardTier {
type: TitleRange;
name: string; // A readable/nice english string of the title range
}

const TitleRanges = [
'novice_journeyman',
'master_lord',
'baron_duke',
'grand_duke_plus',
] as const;

export type TitleRange = typeof TitleRanges[number];

type Card = {
id: number;
quantity: number | null;
is_matched: true | null;
is_tested_pair: true | null;
} & (KnownCard | UnknownCard);

interface KnownCard {
name: string;
is_revealed: true;
quantity: number;
}

interface UnknownCard {
name: null;
is_revealed: false;
quantity: null;
}
11 changes: 11 additions & 0 deletions src/scripts/util/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@

export class CustomConvertibleIds {
// KGA
public static readonly KingsMiniPrizePack: number = 130008;
public static readonly KingsGiveawayVault: number = 130009;

// Halloween
public static readonly HalloweenSpookyShuffleNovice = 130010;
public static readonly HalloweenSpookyShuffleNoviceDusted = 130011;
public static readonly HalloweenSpookyShuffleMaster = 130012;
public static readonly HalloweenSpookyShuffleMasterDusted = 130013;
public static readonly HalloweenSpookyShuffleBaron = 130014;
public static readonly HalloweenSpookyShuffleBaronDusted = 130015;
public static readonly HalloweenSpookyShuffleGrandDuke = 130016;
public static readonly HalloweenSpookyShuffleGrandDukeDusted = 130017;
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/scripts/util/hgFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

export async function getItemsByClass(itemClassifications: string[], forceRefresh = false): Promise<{
name: string,
item_id: number
}[]> {
return await new Promise((resolve, reject) => {
// @ts-ignore
return hg.utils.UserInventory.getItemsByClass(itemClassifications, forceRefresh, resolve, reject);
});
}
Loading

0 comments on commit 087cc7c

Please sign in to comment.