From 2902684c2f95999d30d42fe87857cab7cd4e3c43 Mon Sep 17 00:00:00 2001 From: Hank McCord Date: Fri, 26 Apr 2024 13:08:49 -0400 Subject: [PATCH 1/4] Add Gnawnian Express Station stage tests --- .../gnawnianExpressStation.spec.ts | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts diff --git a/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts b/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts new file mode 100644 index 00000000..fb91faaf --- /dev/null +++ b/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts @@ -0,0 +1,212 @@ +import {addTrainStage} from "@scripts/modules/stages/legacy"; +import {IStager} from "@scripts/modules/stages/stages.types"; +import {User} from "@scripts/types/hg"; +import {IntakeMessage} from "@scripts/types/mhct"; +import {BoardingPhase, JumpPhase, OffTrain, QuestTrainStation, SupplyPhase, TroubleArea} from "@scripts/types/quests"; + +describe('Gnawnian Express Station stages', () => { + let stager: IStager; + let message: IntakeMessage; + let preUser: User; + let postUser: User; + const journal = {}; + + beforeEach(() => { + stager = { + environment: 'Gnawnian Express Station', + addStage: addTrainStage, + }; + message = {} as IntakeMessage; + preUser = {quests: { + QuestTrainStation: getDefaultQuest(), + }} as User; + postUser = {quests: { + QuestTrainStation: getDefaultQuest(), + }} as User; + }); + + it('should be for the Gnawnian Express Station environment', () => { + expect(stager.environment).toBe('Gnawnian Express Station'); + }); + + it('should reject when pre and post on_train differ', () => { + message.location = {id: 0, name: "GES"}; // legacy rejects by setting location to null + preUser.quests.QuestTrainStation = createJumpPhaseAttributes(); + postUser.quests.QuestTrainStation = createOffTrainAttributes(); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.location).toBeNull(); + }); + + it('should reject when pre and post train phase differ', () => { + message.location = {id: 0, name: "GES"}; // legacy rejects by setting location to null + preUser.quests.QuestTrainStation = createSuppyPhaseAttributes(); + postUser.quests.QuestTrainStation = createBoardingPhaseAttributes(); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.location).toBeNull(); + }); + + it('should set stage to Station when not on train', () => { + preUser.quests.QuestTrainStation = createOffTrainAttributes(); + postUser.quests.QuestTrainStation = createOffTrainAttributes(); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe('Station'); + }); + + describe('Supply Depot', () => { + + it('should be Rush when more than zero supply hoarder turns', () => { + preUser.quests.QuestTrainStation = createSuppyPhaseAttributes(1); + postUser.quests.QuestTrainStation = createSuppyPhaseAttributes(1); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe('1. Supply Depot - Rush'); + }); + + it('should be No Rush when zero supply hoarder turns', () => { + preUser.quests.QuestTrainStation = createSuppyPhaseAttributes(0); + postUser.quests.QuestTrainStation = createSuppyPhaseAttributes(0); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe('1. Supply Depot - No Rush'); + }); + + it('should be No Rush + SS Charm when zero supply hoarder turns with Supply Schedule Charm equipped', () => { + preUser.trinket_name = "Supply Schedule Charm"; + preUser.quests.QuestTrainStation = createSuppyPhaseAttributes(0); + postUser.quests.QuestTrainStation = createSuppyPhaseAttributes(0); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe('1. Supply Depot - No Rush + SS Charm'); + }); + }); + + describe('Raider River', () => { + type DefendingCharm = 'Door Guard' | 'Greasy Glob' | 'Roof Rack'; + const charmToId: Record = { + 'Door Guard': 1210, + 'Greasy Glob': 1211, + 'Roof Rack': 1212, + }; + + it('should reject if pre and post trouble area differ', () => { + message.location = {id: 0, name: "GES"}; // legacy rejects by setting location to null + preUser.quests.QuestTrainStation = createBoardingPhaseAttributes('door'); + postUser.quests.QuestTrainStation = createBoardingPhaseAttributes('rails'); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.location).toBeNull(); + }); + + it.each<{troubleArea: TroubleArea, charm: DefendingCharm}>([ + {troubleArea: 'door', charm: 'Door Guard'}, + {troubleArea: 'rails', charm: 'Greasy Glob'}, + {troubleArea: 'roof', charm: 'Roof Rack'}, + ])('should append Defending Target when guarding appropriate area', ({troubleArea, charm}) => { + message.charm = { + id: charmToId[charm], + name: charm, + }; + preUser.quests.QuestTrainStation = createBoardingPhaseAttributes(troubleArea); + postUser.quests.QuestTrainStation = createBoardingPhaseAttributes(troubleArea); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe(`2. Raider River - Defending Target`); + }); + + it.each<{troubleArea: TroubleArea, charm: DefendingCharm}>([ + {troubleArea: 'door', charm: 'Roof Rack'}, + {troubleArea: 'rails', charm: 'Roof Rack'}, + {troubleArea: 'roof', charm: 'Greasy Glob'}, + ])('should append Defending Other when guarding other area', ({troubleArea, charm}) => { + message.charm = { + id: charmToId[charm], + name: charm, + }; + preUser.quests.QuestTrainStation = createBoardingPhaseAttributes(troubleArea); + postUser.quests.QuestTrainStation = createBoardingPhaseAttributes(troubleArea); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe(`2. Raider River - Defending Other`); + }); + + it('should append Not Defending with no area specific charm', () => { + message.charm = { + id: 42, + name: "The Answer", + }; + preUser.quests.QuestTrainStation = createBoardingPhaseAttributes('door'); + postUser.quests.QuestTrainStation = createBoardingPhaseAttributes('door'); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe(`2. Raider River - Not Defending`); + }); + }); + + describe('Daredevil Canyon', () => { + + it.each<{charm: string, expected: string}>([ + {charm: 'Magmatic Crystal Charm', expected: '- Magmatic Crystal'}, + {charm: 'Black Powder Charm', expected: '- Black Powder'}, + {charm: 'Dusty Coal Charm', expected: '- Dusty Coal'}, + {charm: 'No Charm', expected: '- No Fuelers'}, + ])('should append $expected when charm is $charm', ({charm, expected}) => { + preUser.trinket_name = charm; + preUser.quests.QuestTrainStation = createJumpPhaseAttributes(); + postUser.quests.QuestTrainStation = createJumpPhaseAttributes(); + + stager.addStage(message, preUser, postUser, journal); + + expect(message.stage).toBe(`3. Daredevil Canyon ${expected}`); + }); + }); +}); + +export function getDefaultQuest(): QuestTrainStation { + return createOffTrainAttributes(); +} + +export function createOffTrainAttributes(): OffTrain { + return { + on_train: false, + }; +} + +export function createSuppyPhaseAttributes(supplyHoarderTurns = 5): SupplyPhase { + return { + on_train: true, + current_phase: 'supplies', + minigame: { + supply_hoarder_turns: supplyHoarderTurns, + }, + }; +} + +export function createBoardingPhaseAttributes(troubleArea: TroubleArea = "roof"): BoardingPhase { + return { + on_train: true, + current_phase: 'boarding', + minigame: { + trouble_area: troubleArea, + }, + }; +} + +export function createJumpPhaseAttributes(): JumpPhase { + return { + on_train: true, + current_phase: 'bridge_jump', + }; +} From 158d5622786ab745ad39de49dc15db04e11cd3e5 Mon Sep 17 00:00:00 2001 From: Hank McCord Date: Fri, 26 Apr 2024 13:13:46 -0400 Subject: [PATCH 2/4] Add Gnawnian Express Station quest type --- src/scripts/types/hg.ts | 2 +- .../types/quests/gnawnianExpressStation.ts | 40 +++++++++++++++++++ src/scripts/types/quests/index.ts | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/scripts/types/quests/gnawnianExpressStation.ts diff --git a/src/scripts/types/hg.ts b/src/scripts/types/hg.ts index c7b22fe7..e1c7a370 100644 --- a/src/scripts/types/hg.ts +++ b/src/scripts/types/hg.ts @@ -69,7 +69,7 @@ export interface Quests { QuestSuperBrieFactory?: quests.QuestSuperBrieFactory QuestSpringHunt?: quests.QuestSpringHunt QuestTableOfContents?: quests.QuestTableOfContents - QuestTrainStation?: unknown + QuestTrainStation?: quests.QuestTrainStation // Gnawnian Express Station QuestWinterHunt2021?: unknown } diff --git a/src/scripts/types/quests/gnawnianExpressStation.ts b/src/scripts/types/quests/gnawnianExpressStation.ts new file mode 100644 index 00000000..4c57f0d4 --- /dev/null +++ b/src/scripts/types/quests/gnawnianExpressStation.ts @@ -0,0 +1,40 @@ +export type QuestTrainStation = + OffTrain | + SupplyPhase | + BoardingPhase | + JumpPhase; + +interface BaseQuestTrainStation { + on_train: boolean; +} + +export interface OffTrain extends BaseQuestTrainStation { + on_train: false +} + +export interface BaseTrainPhase extends BaseQuestTrainStation { + on_train: true + current_phase: TrainPhaseType +} + +export interface SupplyPhase extends BaseTrainPhase { + current_phase: 'supplies' + minigame: { + supply_hoarder_turns: number + } +} + +export interface BoardingPhase extends BaseTrainPhase { + current_phase: 'boarding' + minigame: { + trouble_area: TroubleArea + } +} + +export type TroubleArea = 'roof' | 'door' | 'rails'; + +export interface JumpPhase extends BaseTrainPhase { + current_phase: 'bridge_jump' +} + +export type TrainPhaseType = 'supplies' | 'boarding' | 'bridge_jump'; diff --git a/src/scripts/types/quests/index.ts b/src/scripts/types/quests/index.ts index dbd07885..a8456855 100644 --- a/src/scripts/types/quests/index.ts +++ b/src/scripts/types/quests/index.ts @@ -8,6 +8,7 @@ export * from '@scripts/types/quests/forbiddenGrove'; export * from '@scripts/types/quests/forewordFarm'; export * from '@scripts/types/quests/fortRox'; export * from '@scripts/types/quests/furomaRift'; +export * from '@scripts/types/quests/gnawnianExpressStation'; export * from '@scripts/types/quests/halloween'; export * from '@scripts/types/quests/harbour'; export * from '@scripts/types/quests/iceberg'; From 77d2235e1d21af2dafd90dcddbc0705c2ece05c4 Mon Sep 17 00:00:00 2001 From: Hank McCord Date: Fri, 26 Apr 2024 13:29:29 -0400 Subject: [PATCH 3/4] Convert GES to stager and remove obsolete legacy file --- src/scripts/main.js | 14 --- .../environments/gnawnianExpressStation.ts | 87 +++++++++++++++ src/scripts/modules/stages/index.ts | 2 + src/scripts/modules/stages/legacy.js | 104 ------------------ .../gnawnianExpressStation.spec.ts | 37 ++++--- 5 files changed, 110 insertions(+), 134 deletions(-) create mode 100644 src/scripts/modules/stages/environments/gnawnianExpressStation.ts delete mode 100644 src/scripts/modules/stages/legacy.js diff --git a/src/scripts/main.js b/src/scripts/main.js index 0e9e910e..7e63be78 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -6,7 +6,6 @@ import * as successHandlers from './modules/ajax-handlers'; import {HornHud} from './util/HornHud'; import * as detailers from './modules/details'; import * as stagers from './modules/stages'; -import * as stagingFuncs from './modules/stages/legacy'; import * as detailingFuncs from './modules/details/legacy'; (function () { @@ -1178,13 +1177,6 @@ import * as detailingFuncs from './modules/details/legacy'; } } - /** @type {Object } */ - const location_stage_lookup = { - "Festive Comet": stagingFuncs.addFestiveCometStage, - "Frozen Vacant Lot": stagingFuncs.addFestiveCometStage, - "Gnawnian Express Station": stagingFuncs.addTrainStage, - }; - /** @type {Object} */ const location_stager_lookup = {}; for (const stager of stagers.stageModules) { @@ -1199,12 +1191,6 @@ import * as detailingFuncs from './modules/details/legacy'; * @param {Object } hunt The journal entry corresponding to the active hunt */ function addStage(message, user, user_post, hunt) { - // legacy staging funcs - const stage_func = location_stage_lookup[user.environment_name]; - if (stage_func) { - stage_func(message, user, user_post, hunt); - } - // IStagers const stager = location_stager_lookup[user.environment_name]; if (stager) { diff --git a/src/scripts/modules/stages/environments/gnawnianExpressStation.ts b/src/scripts/modules/stages/environments/gnawnianExpressStation.ts new file mode 100644 index 00000000..0a495c87 --- /dev/null +++ b/src/scripts/modules/stages/environments/gnawnianExpressStation.ts @@ -0,0 +1,87 @@ +import {type User} from '@scripts/types/hg'; +import {type IntakeMessage} from '@scripts/types/mhct'; +import {TroubleArea, type BoardingPhase} from '@scripts/types/quests/gnawnianExpressStation'; +import {type IStager} from '../stages.types'; + +export class GnawnianExpressStationStager implements IStager { + readonly environment: string = 'Gnawnian Express Station'; + + /** + * Report on the unique minigames in each sub-location. Reject hunts for which the train + * moved / updated / departed, as the hunt stage is ambiguous. + */ + addStage(message: IntakeMessage, userPre: User, userPost: User, journal: unknown): void { + const quest = userPre.quests.QuestTrainStation; + const final_quest = userPost.quests.QuestTrainStation; + + if (!quest || !final_quest) { + throw new Error('QuestTrainStation is undefined'); + } + + if (quest.on_train !== final_quest.on_train) { + throw new Error('Skipping hunt due to server-side train stage change'); + } + + + // Pre- & post-hunt user object agree on train & phase statuses. + if (!quest.on_train || !final_quest.on_train) { + message.stage = 'Station'; + return; + } + + if (quest.current_phase !== final_quest.current_phase) { + throw new Error('Skipping hunt due to server-side train stage change'); + } + + if (quest.current_phase === 'supplies') { + let stage = '1. Supply Depot'; + if (quest.minigame && quest.minigame.supply_hoarder_turns > 0) { + // More than 0 (aka 1-5) Hoarder turns means a Supply Rush is active + stage += ' - Rush'; + } else { + stage += ' - No Rush'; + if (userPre.trinket_name === 'Supply Schedule Charm') { + stage += ' + SS Charm'; + } + } + message.stage = stage; + } else if (quest.current_phase === 'boarding') { + let stage = '2. Raider River'; + // Raider River has an additional server-side state change. + const area = quest.minigame.trouble_area; + const final_area = (final_quest as BoardingPhase).minigame.trouble_area; + + if (area !== final_area) { + throw new Error('Skipping hunt during server-side trouble area change'); + } + + const charm_id = message.charm?.id ?? -1; + const area_to_charm: Record = { + 'door': 1210, + 'rails': 1211, + 'roof': 1212, + }; + const has_correct_charm = area_to_charm[area] === charm_id; + if (has_correct_charm) { + stage += ' - Defending Target'; + } else if ([1210, 1211, 1212].includes(charm_id)) { + stage += ' - Defending Other'; + } else { + stage += ' - Not Defending'; + } + message.stage = stage; + } else if (quest.current_phase === 'bridge_jump') { + let stage = '3. Daredevil Canyon'; + if (userPre.trinket_name === 'Magmatic Crystal Charm') { + stage += ' - Magmatic Crystal'; + } else if (userPre.trinket_name === 'Black Powder Charm') { + stage += ' - Black Powder'; + } else if (userPre.trinket_name === 'Dusty Coal Charm') { + stage += ' - Dusty Coal'; + } else { + stage += ' - No Fuelers'; + } + message.stage = stage; + } + } +} diff --git a/src/scripts/modules/stages/index.ts b/src/scripts/modules/stages/index.ts index abbccb69..bf6a6685 100644 --- a/src/scripts/modules/stages/index.ts +++ b/src/scripts/modules/stages/index.ts @@ -12,6 +12,7 @@ import {ForewordFarmStager} from './environments/forewardFarm'; import {FortRoxStager} from './environments/fortRox'; import {FungalCavernStager} from './environments/fungalCavern'; import {FuromaRiftStager} from './environments/furomaRift'; +import {GnawnianExpressStationStager} from './environments/gnawnianExpressStation'; import {HarbourStager} from './environments/harbour'; import {IcebergStager} from './environments/iceberg'; import {IceFortressStager} from './environments/iceFortress'; @@ -49,6 +50,7 @@ const stageModules: IStager[] = [ new FortRoxStager(), new FungalCavernStager(), new FuromaRiftStager(), + new GnawnianExpressStationStager(), new HarbourStager(), new IcebergStager(), new IceFortressStager(), diff --git a/src/scripts/modules/stages/legacy.js b/src/scripts/modules/stages/legacy.js deleted file mode 100644 index 884e4c35..00000000 --- a/src/scripts/modules/stages/legacy.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Set the stage based on decoration and boss status. - * @param {Object } message The message to be sent. - * @param {Object } user The user state object, when the hunt was invoked (pre-hunt). - * @param {Object } user_post The user state object, after the hunt. - * @param {Object } hunt The journal entry corresponding to the active hunt. - */ -export function addFestiveCometStage(message, user, user_post, hunt) { - const quest = user.quests.QuestWinterHunt2021; - if (!quest) { - return; - } - - if (quest.comet.current_phase === 11) { - message.stage = "Boss"; - } - else if (/Pecan Pecorino/.test(user.bait_name)) { - let theme = quest.decorations.current_decoration || "none"; - if (theme == "none") { - theme = "No Decor"; - } else { - theme = theme.replace(/^([a-z_]+)_yule_log_stat_item/i, "$1").replace(/_/g, " "); - theme = theme.charAt(0).toUpperCase() + theme.slice(1); - } - message.stage = theme; - } else { - message.stage = 'N/A'; - } -} - -/** -/** - * Report on the unique minigames in each sub-location. Reject hunts for which the train - * moved / updated / departed, as the hunt stage is ambiguous. - * @param {Object } message The message to be sent. - * @param {Object } user The user state object, when the hunt was invoked (pre-hunt). - * @param {Object } user_post The user state object, after the hunt. - * @param {Object } hunt The journal entry corresponding to the active hunt. - */ -export function addTrainStage(message, user, user_post, hunt) { - const quest = user.quests.QuestTrainStation; - const final_quest = user_post.quests.QuestTrainStation; - // First check that the user is still in the same stage. - const changed_state = (quest.on_train !== final_quest.on_train - || quest.current_phase !== final_quest.current_phase); - if (changed_state) { - message.location = null; - } else { - // Pre- & post-hunt user object agree on train & phase statuses. - if (!quest.on_train || quest.on_train === "false") { - message.stage = "Station"; - } else if (quest.current_phase === "supplies") { - let stage = "1. Supply Depot"; - if (quest.minigame && quest.minigame.supply_hoarder_turns > 0) { - // More than 0 (aka 1-5) Hoarder turns means a Supply Rush is active - stage += " - Rush"; - } else { - stage += " - No Rush"; - if (user.trinket_name === "Supply Schedule Charm") { - stage += " + SS Charm"; - } - } - message.stage = stage; - } else if (quest.current_phase === "boarding") { - let stage = "2. Raider River"; - if (quest.minigame?.trouble_area) { - // Raider River has an additional server-side state change. - const area = quest.minigame.trouble_area; - const final_area = final_quest.minigame.trouble_area; - if (area !== final_area) { - message.location = null; - } else { - const charm_id = message.charm.id; - const has_correct_charm = (({ - "door": 1210, - "rails": 1211, - "roof": 1212, - })[area] === charm_id); - if (has_correct_charm) { - stage += " - Defending Target"; - } else if ([1210, 1211, 1212].includes(charm_id)) { - stage += " - Defending Other"; - } else { - stage += " - Not Defending"; - } - } - } - message.stage = stage; - } else if (quest.current_phase === "bridge_jump") { - let stage = "3. Daredevil Canyon"; - if (user.trinket_name === "Magmatic Crystal Charm") { - message.stage += " - Magmatic Crystal"; - } else if (user.trinket_name === "Black Powder Charm") { - stage += " - Black Powder"; - } else if (user.trinket_name === "Dusty Coal Charm") { - stage += " - Dusty Coal"; - } else { - stage += " - No Fuelers"; - } - message.stage = stage; - } - } -} - diff --git a/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts b/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts index fb91faaf..91d8e6f6 100644 --- a/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts +++ b/tests/scripts/modules/stages/environments/gnawnianExpressStation.spec.ts @@ -1,4 +1,4 @@ -import {addTrainStage} from "@scripts/modules/stages/legacy"; +import {GnawnianExpressStationStager} from "@scripts/modules/stages/environments/gnawnianExpressStation"; import {IStager} from "@scripts/modules/stages/stages.types"; import {User} from "@scripts/types/hg"; import {IntakeMessage} from "@scripts/types/mhct"; @@ -12,10 +12,7 @@ describe('Gnawnian Express Station stages', () => { const journal = {}; beforeEach(() => { - stager = { - environment: 'Gnawnian Express Station', - addStage: addTrainStage, - }; + stager = new GnawnianExpressStationStager(); message = {} as IntakeMessage; preUser = {quests: { QuestTrainStation: getDefaultQuest(), @@ -29,24 +26,34 @@ describe('Gnawnian Express Station stages', () => { expect(stager.environment).toBe('Gnawnian Express Station'); }); + it('it should throw when quest is undefined', () => { + preUser.quests.QuestTrainStation = undefined; + + expect(() => stager.addStage(message, preUser, postUser, journal)) + .toThrow('QuestTrainStation is undefined'); + + preUser.quests.QuestTrainStation = getDefaultQuest(); + postUser.quests.QuestTrainStation = undefined; + + expect(() => stager.addStage(message, preUser, postUser, journal)) + .toThrow('QuestTrainStation is undefined'); + }); + it('should reject when pre and post on_train differ', () => { message.location = {id: 0, name: "GES"}; // legacy rejects by setting location to null preUser.quests.QuestTrainStation = createJumpPhaseAttributes(); postUser.quests.QuestTrainStation = createOffTrainAttributes(); - stager.addStage(message, preUser, postUser, journal); - - expect(message.location).toBeNull(); + expect(() => stager.addStage(message, preUser, postUser, journal)) + .toThrow('Skipping hunt due to server-side train stage change'); }); it('should reject when pre and post train phase differ', () => { - message.location = {id: 0, name: "GES"}; // legacy rejects by setting location to null preUser.quests.QuestTrainStation = createSuppyPhaseAttributes(); postUser.quests.QuestTrainStation = createBoardingPhaseAttributes(); - stager.addStage(message, preUser, postUser, journal); - - expect(message.location).toBeNull(); + expect(() => stager.addStage(message, preUser, postUser, journal)) + .toThrow('Skipping hunt due to server-side train stage change'); }); it('should set stage to Station when not on train', () => { @@ -98,13 +105,11 @@ describe('Gnawnian Express Station stages', () => { }; it('should reject if pre and post trouble area differ', () => { - message.location = {id: 0, name: "GES"}; // legacy rejects by setting location to null preUser.quests.QuestTrainStation = createBoardingPhaseAttributes('door'); postUser.quests.QuestTrainStation = createBoardingPhaseAttributes('rails'); - stager.addStage(message, preUser, postUser, journal); - - expect(message.location).toBeNull(); + expect(() => stager.addStage(message, preUser, postUser, journal)) + .toThrow('Skipping hunt during server-side trouble area change'); }); it.each<{troubleArea: TroubleArea, charm: DefendingCharm}>([ From f38e347ef8767702c18203d10bb773b712f2237e Mon Sep 17 00:00:00 2001 From: Hank McCord Date: Mon, 13 May 2024 12:55:00 -0400 Subject: [PATCH 4/4] Convert string ids into numbers during hunt parse --- src/scripts/main.js | 63 ++++++++++------------------- src/scripts/types/hg.ts | 10 ++--- src/scripts/types/mhct.ts | 1 + tests/scripts/hunt-filter/common.ts | 1 + 4 files changed, 28 insertions(+), 47 deletions(-) diff --git a/src/scripts/main.js b/src/scripts/main.js index 7e63be78..58906b84 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -2,8 +2,9 @@ import {IntakeRejectionEngine} from "./hunt-filter/engine"; import {ConsoleLogger, LogLevel} from './util/logger'; import {getUnixTimestamp} from "./util/time"; -import * as successHandlers from './modules/ajax-handlers'; +import {parseHgInt} from "./util/number"; import {HornHud} from './util/HornHud'; +import * as successHandlers from './modules/ajax-handlers'; import * as detailers from './modules/details'; import * as stagers from './modules/stages'; import * as detailingFuncs from './modules/details/legacy'; @@ -455,8 +456,8 @@ import * as detailingFuncs from './modules/details/legacy'; } /** - * @param {Object } pre_response The object obtained prior to invoking `activeturn.php`. - * @param {Object } post_response Parsed JSON representation of the response from calling activeturn.php + * @param {import("./types/hg").HgResponse} pre_response The object obtained prior to invoking `activeturn.php`. + * @param {import("./types/hg").HgResponse} post_response Parsed JSON representation of the response from calling activeturn.php */ function recordHuntWithPrehuntUser(pre_response, post_response) { logger.debug("In recordHuntWithPrehuntUser pre and post:", pre_response, post_response); @@ -558,10 +559,10 @@ import * as detailingFuncs from './modules/details/legacy'; /** * - * @param {Object} user A the main user object (pre or post) used to populate the message - * @param {Object} user_post The post-hunt user object - * @param {Object } hunt Journal entry corresponding with the hunt - * @returns + * @param {import("./types/hg").User} user A the main user object (pre or post) used to populate the message + * @param {import("./types/hg").User} user_post The post-hunt user object + * @param {import("./types/hg").JournalMarkup} hunt Journal entry corresponding with the hunt + * @returns {import("./types/mhct").IntakeMessage | undefined} */ function createIntakeMessage(user, user_post, hunt) { // Obtain the main hunt information from the journal entry and user objects. @@ -738,10 +739,12 @@ import * as detailingFuncs from './modules/details/legacy'; /** * Find the active journal entry, and handle supported "bonus journals" such as the Relic Hunter attraction. - * @param {Object } hunt_response The JSON response returned from a horn sound. - * @returns {Object } The journal entry corresponding to the active hunt. + * @param {import("./types/hg").HgResponse} hunt_response The JSON response returned from a horn sound. + * @param {number} max_old_entry_id + * @returns {import("./types/hg").JournalMarkup | null} The journal entry corresponding to the active hunt. */ function parseJournalEntries(hunt_response, max_old_entry_id) { + /** @type {import("./types/hg").JournalMarkup & Object} */ let journal = {}; const more_details = {}; more_details.hunt_count = 0; @@ -1013,19 +1016,16 @@ import * as detailingFuncs from './modules/details/legacy'; /** * Initialize the message with main hunt details. - * @param {Object } journal The journal entry corresponding to the active hunt. - * @param {Object } user The user state object, when the hunt was invoked (pre-hunt). - * @param {Object } user_post The user state object, after the hunt. - * @returns {Object | null} The message object, or `null` if an error occurred. + * @param {import("./types/hg").JournalMarkup} journal The journal entry corresponding to the active hunt. + * @param {import("./types/hg").User} user The user state object, when the hunt was invoked (pre-hunt). + * @param {import("./types/hg").User} user_post The user state object, after the hunt. + * @returns {import("@scripts/types/mhct").IntakeMessage | null} The message object, or `null` if an error occurred. */ function createMessageFromHunt(journal, user, user_post) { + /** @type {import("./types/mhct").IntakeMessage} */ const message = {}; - const debug_logs = []; - // Entry ID message.entry_id = journal.render_data.entry_id; - - // Entry Timestamp message.entry_timestamp = journal.render_data.entry_timestamp; // Location @@ -1037,36 +1037,20 @@ import * as detailingFuncs from './modules/details/legacy'; name: user.environment_name, id: user.environment_id, }; - if (user_post.environment_id != user.environment_id) { - debug_logs.push(`User auto-traveled from ${user.environment_name} to ${user_post.environment_name}`); - } - // Shield (true / false) message.shield = user.has_shield; - - // Total Power, Luck, Attraction message.total_power = user.trap_power; - if (user_post.trap_power !== user.trap_power) { - debug_logs.push(`User setup power changed from ${user.trap_power} to ${user_post.trap_power}`); - } - message.total_luck = user.trap_luck; - if (user_post.trap_luck !== user.trap_luck) { - debug_logs.push(`User setup luck changed from ${user.trap_luck} to ${user_post.trap_luck}`); - } - message.attraction_bonus = Math.round(user.trap_attraction_bonus * 100); - if (user_post.trap_attraction_bonus !== user.trap_attraction_bonus) { - debug_logs.push(`User setup attraction bonus changed from ${user.trap_attraction_bonus} to ${user_post.trap_attraction_bonus}`); - } - // Setup components const components = [ {prop: 'weapon', message_field: 'trap', required: true, replacer: / trap$/i}, {prop: 'base', message_field: 'base', required: true, replacer: / base$/i}, {prop: 'bait', message_field: 'cheese', required: true, replacer: / cheese$/i}, {prop: 'trinket', message_field: 'charm', required: false, replacer: / charm$/i}, ]; + + // Setup components // All pre-hunt users must have a weapon, base, and cheese. const missing = components.filter(component => component.required === true && !Object.prototype.hasOwnProperty.call(user, `${component.prop}_name`) @@ -1086,13 +1070,10 @@ import * as detailingFuncs from './modules/details/legacy'; name: '', } : { - id: user[prop_id], + // Make sure any strumbers are converted to actual numbers + id: parseHgInt(user[prop_id]), name: item_name.replace(component.replacer, ''), }; - - if (item_name !== user_post[prop_name]) { - debug_logs.push(`User ${component.message_field} changed: Was '${item_name}' and is now '${user_post[prop_name] || "None"}'`); - } }); // Caught / Attracted / FTA'd @@ -1117,8 +1098,6 @@ import * as detailingFuncs from './modules/details/legacy'; .replace(/ mouse$/i, ''); // Remove " [Mm]ouse" if it is not a part of the name (e.g. Dread Pirate Mousert) } - debug_logs.forEach(log_message => logger.debug(log_message)); - return message; } diff --git a/src/scripts/types/hg.ts b/src/scripts/types/hg.ts index e1c7a370..5f617250 100644 --- a/src/scripts/types/hg.ts +++ b/src/scripts/types/hg.ts @@ -11,16 +11,16 @@ export interface HgResponse { export interface User { user_id: number; - sn_user_id: number; + sn_user_id: number | string; unique_hash: string; num_active_turns: number; next_activeturn_seconds: number; base_name: string; - base_item_id: number; + base_item_id: number | string; weapon_name: string; - weapon_item_id: number; + weapon_item_id: number | string; trinket_name: string | null; - trinket_item_id: number | null; + trinket_item_id: number | string | null; bait_name: string; bait_item_id: number; trap_power: number; @@ -100,7 +100,7 @@ export interface JournalMarkup { export interface RenderData { //image: Image | []; entry_id: number; - mouse_type: string; + mouse_type: string | boolean; css_class: string; entry_date: string; environment: string; diff --git a/src/scripts/types/mhct.ts b/src/scripts/types/mhct.ts index 2e368fa6..6d0be5fa 100644 --- a/src/scripts/types/mhct.ts +++ b/src/scripts/types/mhct.ts @@ -2,6 +2,7 @@ export interface IntakeMessage { extension_version: number; user_id: number; entry_id: number; + entry_timestamp: number; location: ComponentEntry | null; shield: boolean; total_power: number; diff --git a/tests/scripts/hunt-filter/common.ts b/tests/scripts/hunt-filter/common.ts index 265a8d20..72492077 100644 --- a/tests/scripts/hunt-filter/common.ts +++ b/tests/scripts/hunt-filter/common.ts @@ -47,6 +47,7 @@ export function getDefaultUser(): User { export function getDefaultIntakeMessage(): IntakeMessage { return { extension_version: 0, + entry_timestamp: 0, user_id: 0, entry_id: 0, location: {