-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #553 from hymccord/school-of-sorcery
Add School of Sorcery stages
- Loading branch information
Showing
13 changed files
with
508 additions
and
5 deletions.
There are no files selected for viewing
67 changes: 67 additions & 0 deletions
67
src/scripts/hunt-filter/exemptions/environments/schoolOfSorcery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import type {IMessageExemption} from "@scripts/hunt-filter/interfaces"; | ||
import type {IntakeMessage} from "@scripts/types/mhct"; | ||
|
||
/** | ||
* Allow transitions from Boss stages to course stages or hallway | ||
*/ | ||
class CourseBossExemption implements IMessageExemption { | ||
readonly description = "School of Sorcery Boss Encounter"; | ||
readonly property = "stage"; | ||
|
||
readonly CourseBossStagesToMouse: Record<string, string | undefined> = { | ||
'Arcane Arts Boss': 'Arcane Master Sorcerer' , | ||
'Shadow Sciences Boss': 'Shadow Master Sorcerer' , | ||
'Final Exam Boss': 'Mythical Master Sorcerer', | ||
}; | ||
|
||
getExemptions( | ||
pre: IntakeMessage, | ||
post: IntakeMessage | ||
): (keyof IntakeMessage)[] | null { | ||
|
||
// Only allow transitions from boss to outside for now | ||
if (this.isTransitionFromBossToHallway(pre.stage, post.stage, pre.mouse)) { | ||
return ['stage', 'cheese']; | ||
} | ||
|
||
if (this.isTransitionFromBossToCourse(pre.stage, post.stage, pre.mouse)) { | ||
return ['stage']; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private isTransitionFromBossToHallway(preStage: unknown, postStage: unknown, mouse: unknown) { | ||
return this.isCourseStageAndBoss(preStage, mouse) | ||
&& this.isHallwayStage(postStage); | ||
} | ||
|
||
private isTransitionFromBossToCourse(preStage: unknown, postStage: unknown, mouse: unknown) { | ||
return this.isCourseStageAndBoss(preStage, mouse) | ||
&& this.isCourseStage(postStage); | ||
} | ||
|
||
/** Check if stage is a known boss stage that matches associated mouse */ | ||
private isCourseStageAndBoss(stage: unknown, mouse: unknown) { | ||
if (typeof stage === 'string') { | ||
return this.CourseBossStagesToMouse[stage] === mouse; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private isCourseStage(stage: unknown) { | ||
return stage === 'Arcane Arts' | ||
|| stage === 'Shadow Sciences' | ||
|| stage === 'Final Exam - Arcane' | ||
|| stage === 'Final Exam - Shadow'; | ||
} | ||
|
||
private isHallwayStage(stage: unknown) { | ||
return stage === 'Hallway'; | ||
} | ||
} | ||
|
||
export const schoolOfSorceryExemptions = [ | ||
new CourseBossExemption(), | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import type {IMessageExemption} from "@scripts/hunt-filter/interfaces"; | ||
import type {IntakeMessage} from "@scripts/types/mhct"; | ||
|
||
/** | ||
* Provides an exemption on the 'cheese' difference when cheese runs out. | ||
*/ | ||
class OutOfCheeseExemption implements IMessageExemption { | ||
readonly description = "Ran out of cheese"; | ||
readonly property = "cheese"; | ||
|
||
getExemptions(pre: IntakeMessage, post: IntakeMessage): (keyof IntakeMessage)[] | null { | ||
if ( | ||
pre.cheese.id > 0 && | ||
post.cheese.id == 0 | ||
) { | ||
return ["cheese"]; | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
|
||
/** | ||
* Provides an exemption on the 'charm' difference when charms run out. | ||
*/ | ||
class OutOfCharmsExemption implements IMessageExemption { | ||
readonly description = "Ran out of cheese"; | ||
readonly property = "charm"; | ||
|
||
getExemptions(pre: IntakeMessage, post: IntakeMessage): (keyof IntakeMessage)[] | null { | ||
if ( | ||
(pre.charm?.id ?? 0) > 0 && | ||
post.charm?.id === 0 | ||
) { | ||
return ["charm"]; | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
|
||
export const globalExemptions = [ | ||
new OutOfCheeseExemption(), | ||
new OutOfCharmsExemption(), | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,26 @@ | ||
import type {IMessageExemption} from '../interfaces'; | ||
|
||
import {globalExemptions} from './global'; | ||
import {acolyteRealmExemptions} from './environments/acolyteRealm'; | ||
import {bountifulBeanstalkExemptions} from './environments/bountifulBeanstalk'; | ||
import {clawShotCityExemptions} from './environments/clawShotCity'; | ||
import {floatingIslandsExemptions} from './environments/floatingIslands'; | ||
import {icebergExemptions} from './environments/iceberg'; | ||
import {iceFortressExemptions} from './environments/iceFortress'; | ||
import {schoolOfSorceryExemptions} from './environments/schoolOfSorcery'; | ||
import {superBrieFactoryExemptions} from './environments/superBrieFactory'; | ||
import {valourRiftExemptions} from './environments/valourRift'; | ||
|
||
export const MessageExemptions: IMessageExemption[] = [ | ||
...globalExemptions, | ||
|
||
...acolyteRealmExemptions, | ||
...bountifulBeanstalkExemptions, | ||
...clawShotCityExemptions, | ||
...icebergExemptions, | ||
...iceFortressExemptions, | ||
...floatingIslandsExemptions, | ||
...schoolOfSorceryExemptions, | ||
...superBrieFactoryExemptions, | ||
...valourRiftExemptions, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
src/scripts/modules/stages/environments/schoolOfSorcery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import type {User} from '@scripts/types/hg'; | ||
import type {IntakeMessage} from '@scripts/types/mhct'; | ||
import type {IStager} from '../stages.types'; | ||
import type {CourseSelection, CourseType} from '@scripts/types/quests/schoolOfSorcery'; | ||
|
||
|
||
export class SchoolOfSorceryStager implements IStager { | ||
readonly environment: string = 'School of Sorcery'; | ||
|
||
addStage(message: IntakeMessage, userPre: User, userPost: User, journal: unknown): void { | ||
const quest = userPre.quests.QuestSchoolOfSorcery; | ||
|
||
if (!quest) { | ||
throw new Error('QuestSchoolOfSorcery is undefined'); | ||
} | ||
|
||
if (!quest.in_course) { | ||
message.stage = 'Hallway'; | ||
} else { | ||
const currentCourse = quest.current_course; | ||
message.stage = this.getCourseName(quest.course_selections, currentCourse.course_type); | ||
|
||
if (currentCourse.is_boss_encounter) { | ||
message.stage += " Boss"; | ||
} else if (currentCourse.course_type === 'exam_course') { | ||
// Final Exam gets current powertype appended | ||
|
||
let powerType: string = currentCourse.power_type; | ||
// Capitalize first letter | ||
powerType = powerType.charAt(0).toUpperCase() + powerType.slice(1); | ||
message.stage += ` - ${powerType}`; | ||
} | ||
} | ||
} | ||
|
||
private getCourseName(courseSelections: CourseSelection[], currentCourseType: CourseType): string { | ||
const course = courseSelections.find(c => c.type === currentCourseType); | ||
if (!course) { | ||
throw new Error(`The course type '${currentCourseType}' was not found in the course_selections array`); | ||
} | ||
|
||
return course.name; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
export type QuestSchoolOfSorcery = { | ||
course_selections: CourseSelection[]; | ||
} & (RunningInHallway | TakingACourse); | ||
|
||
interface RunningInHallway { | ||
in_course: false; | ||
} | ||
|
||
interface TakingACourse { | ||
current_course: CurrentCourse; | ||
in_course: true; | ||
} | ||
|
||
export interface CourseSelection { | ||
type: CourseType; | ||
name: string; | ||
} | ||
|
||
export interface CurrentCourse { | ||
course_type: CourseType; | ||
power_type: 'shadow' | 'arcane' | ||
is_boss_encounter: boolean | null | ||
} | ||
|
||
export type CourseType = 'arcane_101_course' | 'shadow_101_course' | 'exam_course'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
tests/scripts/hunt-filter/exemptions/environments/schoolOfSorcery.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import {IntakeRejectionEngine} from "@scripts/hunt-filter/engine"; | ||
import {SchoolOfSorceryStager} from "@scripts/modules/stages/environments/schoolOfSorcery"; | ||
import {User} from "@scripts/types/hg"; | ||
import {ComponentEntry, IntakeMessage} from "@scripts/types/mhct"; | ||
import {CourseType} from "@scripts/types/quests/schoolOfSorcery"; | ||
import {LoggerService} from "@scripts/util/logger"; | ||
import { | ||
getDefaultIntakeMessage, | ||
getDefaultUser | ||
} from "@tests/scripts/hunt-filter/common"; | ||
import * as stageTest from "@tests/scripts/modules/stages/environments/schoolOfSorcery.spec"; | ||
|
||
describe("School of Sorcery exemptions", () => { | ||
let logger: LoggerService; | ||
let stager: SchoolOfSorceryStager; | ||
let target: IntakeRejectionEngine; | ||
|
||
beforeEach(() => { | ||
logger = {} as LoggerService; | ||
stager = new SchoolOfSorceryStager(); | ||
target = new IntakeRejectionEngine(logger); | ||
|
||
logger.debug = jest.fn(); | ||
}); | ||
|
||
describe("validateMessage", () => { | ||
let preUser: User; | ||
let postUser: User; | ||
let preMessage: IntakeMessage; | ||
let postMessage: IntakeMessage; | ||
|
||
beforeEach(() => { | ||
preUser = {...getDefaultUser(), ...getSchoolOfSorceryUser()}; | ||
postUser = {...getDefaultUser(), ...getSchoolOfSorceryUser()}; | ||
preMessage = {...getDefaultIntakeMessage()}; | ||
postMessage = {...getDefaultIntakeMessage()}; | ||
}); | ||
|
||
describe("In Course", () => { | ||
it("should reject on transition to boss", () => { | ||
preUser.quests.QuestSchoolOfSorcery = stageTest.createCourseAttributes({courseType: 'arcane_101_course', powerType: 'arcane'}, false); | ||
postUser.quests.QuestSchoolOfSorcery = stageTest.createCourseAttributes({courseType: 'arcane_101_course', powerType: 'arcane'}, true); | ||
preMessage.mouse = postMessage.mouse = "Some Random Mouse"; | ||
|
||
calculateStage(); | ||
|
||
const valid = target.validateMessage(preMessage, postMessage); | ||
|
||
expect(valid).toBe(false); | ||
}); | ||
|
||
describe.each<{courseType: CourseType, powerType: 'arcane' | 'shadow', boss: string}>([ | ||
{courseType: 'arcane_101_course', powerType: 'arcane', boss: 'Arcane Master Sorcerer'}, | ||
{courseType: 'shadow_101_course', powerType: 'shadow', boss: 'Shadow Master Sorcerer'}, | ||
{courseType: 'exam_course', powerType: 'arcane', boss: 'Mythical Master Sorcerer'}, | ||
{courseType: 'exam_course', powerType: 'shadow', boss: 'Mythical Master Sorcerer'}, | ||
])("Boss catching", ({courseType, powerType, boss}) => { | ||
it(`should accept on transition from catching ${boss} in ${courseType} to hallway`, () => { | ||
preUser.quests.QuestSchoolOfSorcery = stageTest.createCourseAttributes({courseType, powerType}, true); | ||
postUser.quests.QuestSchoolOfSorcery = stageTest.createHallwayAttributes(); | ||
preMessage.mouse = postMessage.mouse = boss; | ||
// Going back changes cheese | ||
preMessage.cheese = {id: 1, name: 'Some'}; | ||
postMessage.cheese = {id: 2, name: 'Another'}; | ||
|
||
calculateStage(); | ||
|
||
const valid = target.validateMessage(preMessage, postMessage); | ||
expect(valid).toBe(true); | ||
}); | ||
|
||
it(`should accept on transition from catching ${boss} in ${courseType} to hallway and cheese disarming`, () => { | ||
preUser.quests.QuestSchoolOfSorcery = stageTest.createCourseAttributes({courseType, powerType}, true); | ||
postUser.quests.QuestSchoolOfSorcery = stageTest.createHallwayAttributes(); | ||
preMessage.mouse = postMessage.mouse = boss; | ||
// Going back disarms cheese (sets user.bait_item_id: 0, user.bait_name: 0). See createMessageFromHunt | ||
preMessage.cheese = {id: 1, name: 'Some Cheese'}; | ||
postMessage.cheese = {} as ComponentEntry; | ||
|
||
calculateStage(); | ||
|
||
const valid = target.validateMessage(preMessage, postMessage); | ||
expect(valid).toBe(true); | ||
}); | ||
|
||
it(`should accept on transition from catching ${boss} in ${courseType} and continuing course`, () => { | ||
let postPowerType = powerType; | ||
if (courseType === 'exam_course') { | ||
postPowerType = postPowerType === 'arcane' ? 'shadow' : 'arcane'; | ||
} | ||
preUser.quests.QuestSchoolOfSorcery = stageTest.createCourseAttributes({courseType, powerType}, true); | ||
postUser.quests.QuestSchoolOfSorcery = stageTest.createCourseAttributes({courseType, powerType: postPowerType}, false); | ||
preMessage.mouse = postMessage.mouse = boss; | ||
|
||
calculateStage(); | ||
|
||
const valid = target.validateMessage(preMessage, postMessage); | ||
expect(valid).toBe(true); | ||
}); | ||
}); | ||
}); | ||
|
||
/** Sets the pre and post message stage based on current pre and post user */ | ||
function calculateStage() { | ||
stager.addStage(preMessage, preUser, {} as User, {}); | ||
stager.addStage(postMessage, postUser, {} as User, {}); | ||
} | ||
}); | ||
|
||
function getSchoolOfSorceryUser(): User { | ||
return { | ||
environment_name: "School of Sorcery", | ||
quests: { | ||
QuestSchoolOfSorcery: stageTest.getDefaultQuest(), | ||
}, | ||
} as User; | ||
} | ||
}); |
Oops, something went wrong.