coh-content-db 2.0.0-rc.6 → 2.0.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +3 -3
  2. package/dist/coh-content-db.d.ts +384 -181
  3. package/dist/coh-content-db.js +604 -348
  4. package/dist/coh-content-db.js.map +1 -1
  5. package/dist/coh-content-db.mjs +592 -347
  6. package/dist/coh-content-db.mjs.map +1 -1
  7. package/eslint.config.mjs +1 -0
  8. package/package.json +1 -1
  9. package/src/main/api/alignment.ts +18 -2
  10. package/src/main/api/badge-data.ts +12 -42
  11. package/src/main/api/badge-requirement-data.ts +17 -35
  12. package/src/main/api/badge-requirement-type.ts +28 -7
  13. package/src/main/api/badge-type.ts +15 -15
  14. package/src/main/api/contact-data.ts +7 -5
  15. package/src/main/api/content-bundle.ts +6 -0
  16. package/src/main/api/enhancement-category.ts +26 -26
  17. package/src/main/api/location-data.ts +28 -0
  18. package/src/main/api/mission-data.ts +83 -0
  19. package/src/main/api/mission-type.ts +2 -0
  20. package/src/main/api/morality.ts +31 -0
  21. package/src/main/api/sex.ts +8 -1
  22. package/src/main/api/zone-data.ts +1 -1
  23. package/src/main/changelog.ts +5 -4
  24. package/src/main/db/alignment-list.ts +54 -0
  25. package/src/main/db/alternates.ts +15 -32
  26. package/src/main/db/badge-index.ts +11 -36
  27. package/src/main/db/badge-requirement.ts +22 -43
  28. package/src/main/db/badge-search-options.ts +4 -4
  29. package/src/main/db/badge.ts +53 -54
  30. package/src/main/db/coh-content-database.ts +28 -24
  31. package/src/main/db/contact.ts +17 -14
  32. package/src/main/db/location.ts +30 -0
  33. package/src/main/db/mission.ts +107 -0
  34. package/src/main/db/morality-list.ts +99 -0
  35. package/src/main/db/zone.ts +1 -1
  36. package/src/main/index.ts +8 -3
  37. package/src/main/util.ts +43 -3
  38. package/src/test/api/alignment.test.ts +38 -4
  39. package/src/test/api/badge-data.fixture.ts +1 -17
  40. package/src/test/api/badge-data.test.ts +3 -3
  41. package/src/test/api/badge-requirement-data.fixture.ts +1 -11
  42. package/src/test/api/badge-requirement-type.test.ts +3 -3
  43. package/src/test/api/badge-type.test.ts +5 -5
  44. package/src/test/api/contact-data.fixture.ts +0 -6
  45. package/src/test/api/content-bundle.fixture.ts +1 -17
  46. package/src/test/api/enhancement-category.test.ts +5 -5
  47. package/src/test/api/mission-data.fixture.ts +12 -0
  48. package/src/test/api/sex.test.ts +33 -1
  49. package/src/test/api/zone-data.fixture.ts +1 -1
  50. package/src/test/db/alignment-list.test.ts +200 -0
  51. package/src/test/db/alternates.test.ts +60 -56
  52. package/src/test/db/badge-index.test.ts +82 -72
  53. package/src/test/db/badge-requirement.test.ts +35 -70
  54. package/src/test/db/badge.test.ts +185 -64
  55. package/src/test/db/coh-content-database.test.ts +58 -69
  56. package/src/test/db/contact.test.ts +25 -24
  57. package/src/test/db/location.test.ts +51 -0
  58. package/src/test/db/mission.test.ts +171 -0
  59. package/src/test/db/morality-list.test.ts +457 -0
  60. package/src/test/db/zone.test.ts +4 -4
  61. package/src/test/util.test.ts +54 -1
  62. package/src/main/api/plaque-type.ts +0 -6
  63. package/src/main/db/alignments.ts +0 -17
  64. package/src/test/api/alignments.test.ts +0 -40
  65. package/src/test/api/plaque-type.test.ts +0 -31
@@ -1,11 +1,13 @@
1
1
  import { Link } from './link'
2
2
  import { MarkdownString } from './markdown-string'
3
+ import { LocationData } from './location-data'
4
+ import { MoralityExtended } from './morality'
3
5
 
4
6
  export interface ContactData {
5
7
  /**
6
8
  * Unique key used to reference this contact.
7
9
  *
8
- * Keys can only contain lowercase letters, numbers and hyphens (`-`).
10
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
9
11
  */
10
12
  readonly key: string
11
13
 
@@ -20,14 +22,14 @@ export interface ContactData {
20
22
  readonly title?: string
21
23
 
22
24
  /**
23
- * The zone this character is located in.
25
+ * The character moralities that this contact will interact with.
24
26
  */
25
- readonly zoneKey?: string
27
+ readonly morality?: MoralityExtended | MoralityExtended[]
26
28
 
27
29
  /**
28
- * The `/loc` coordinates of the contact.
30
+ * The location of this contact.
29
31
  */
30
- readonly loc?: [number, number, number]
32
+ readonly location?: LocationData
31
33
 
32
34
  /**
33
35
  * The level range this contact will offer missions for.
@@ -5,6 +5,7 @@ import { Change } from './change'
5
5
  import { Link } from './link'
6
6
  import { MarkdownString } from './markdown-string'
7
7
  import { ContactData } from './contact-data'
8
+ import { MissionData } from './mission-data'
8
9
 
9
10
  /**
10
11
  * A content bundle holds the data that makes up one forked instance of the game since the original sunset, such as Homecoming (https://forums.homecomingservers.com/).
@@ -51,6 +52,11 @@ export interface ContentBundle {
51
52
  */
52
53
  readonly contacts?: ContactData[]
53
54
 
55
+ /**
56
+ * List of missions available in this fork.
57
+ */
58
+ readonly missions?: MissionData[]
59
+
54
60
  /**
55
61
  * List of badges available on this fork.
56
62
  */
@@ -1,30 +1,30 @@
1
1
  export const ENHANCEMENT_CATEGORY = [
2
- 'DEFENSE_DEBUFF',
3
- 'TO_HIT_DEBUFF',
4
- 'TAUNT',
5
- 'CONFUSE',
6
- 'HEALING',
7
- 'DEFENSE_BUFF',
8
- 'RESIST_DAMAGE',
9
- 'INTANGIBILITY',
10
- 'SLEEP',
11
- 'SLOW',
12
- 'HOLD',
13
- 'STUN',
14
- 'IMMOBILIZE',
15
- 'FEAR',
16
- 'ENDURANCE_MODIFICATION',
17
- 'ENDURANCE_REDUCTION',
18
- 'RECHARGE_REDUCTION',
19
- 'INTERRUPT_DURATION',
20
- 'ACCURACY',
21
- 'TO_HIT_BUFF',
22
- 'DAMAGE',
23
- 'KNOCKBACK',
24
- 'RUN_SPEED',
25
- 'JUMP',
26
- 'FLY_SPEED',
27
- 'RANGE',
2
+ 'defense-debuff',
3
+ 'to-hit-debuff',
4
+ 'taunt',
5
+ 'confuse',
6
+ 'healing',
7
+ 'defense-buff',
8
+ 'resist-damage',
9
+ 'intangibility',
10
+ 'sleep',
11
+ 'slow',
12
+ 'hold',
13
+ 'stun',
14
+ 'immobilize',
15
+ 'fear',
16
+ 'endurance-modification',
17
+ 'endurance-reduction',
18
+ 'recharge-reduction',
19
+ 'interrupt-duration',
20
+ 'accuracy',
21
+ 'to-hit-buff',
22
+ 'damage',
23
+ 'knockback',
24
+ 'run-speed',
25
+ 'jump',
26
+ 'fly-speed',
27
+ 'range',
28
28
  ] as const
29
29
 
30
30
  export type EnhancementCategory = typeof ENHANCEMENT_CATEGORY[number]
@@ -0,0 +1,28 @@
1
+ export interface LocationData {
2
+ /**
3
+ * Key of the {@link Zone} that the location references.
4
+ */
5
+ readonly zoneKey?: string
6
+
7
+ /**
8
+ * In-game `/loc` coordinates of the location.
9
+ */
10
+ readonly coords?: Coords
11
+
12
+ /**
13
+ * The type of icon to use if the location appears on a map. (Typically the Vidiot map icon).
14
+ */
15
+ readonly icon?: LocationIcon
16
+
17
+ /**
18
+ * The text that should appear in the location icon. (Typically a number or symbol from the Vidiot map).
19
+ */
20
+ readonly iconText?: string
21
+ }
22
+
23
+ /**
24
+ * Coordinates as they appear using the in-game `/loc` command.
25
+ */
26
+ export type Coords = [number, number, number]
27
+
28
+ export type LocationIcon = 'badge' | 'plaque' | 'pedestal' | 'object'
@@ -0,0 +1,83 @@
1
+ import { Link } from './link'
2
+ import { MarkdownString } from './markdown-string'
3
+ import { MissionType } from './mission-type'
4
+ import { MoralityExtended } from './morality'
5
+
6
+ export interface MissionData {
7
+ /**
8
+ * Unique key used to reference this mission.
9
+ *
10
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
11
+ */
12
+ readonly key: string
13
+
14
+ /**
15
+ * The name of the mission as it appears from the contact.
16
+ *
17
+ * The name may be different when viewed in Ouroboros as a Flashback.
18
+ */
19
+ readonly name: string
20
+
21
+ /**
22
+ * The type of mission... Story arc, task force, trial, etc.
23
+ */
24
+ readonly type: MissionType
25
+
26
+ /**
27
+ * The character moralities that may accept the mission.
28
+ */
29
+ readonly morality?: MoralityExtended | MoralityExtended[]
30
+
31
+ /**
32
+ * The keys of any contacts that provide this mission.
33
+ */
34
+ readonly contactKeys?: string | string[]
35
+
36
+ /**
37
+ * The level range this mission is available for.
38
+ */
39
+ readonly levelRange?: [number, number?]
40
+
41
+ /**
42
+ * Freeform notes or tips about the mission.
43
+ */
44
+ readonly notes?: MarkdownString
45
+
46
+ /**
47
+ * List of external links. Wiki, forums, etc.
48
+ */
49
+ readonly links?: Link[]
50
+
51
+ /**
52
+ * If the mission is available in Ouroboros as a Flashback.
53
+ */
54
+ readonly flashback?: MissionFlashbackData
55
+ }
56
+
57
+ export interface MissionFlashbackData {
58
+
59
+ /**
60
+ * The id of the mission as seen in the Flashback menu, i.e. '14.01'.
61
+ */
62
+ readonly id: string
63
+
64
+ /**
65
+ * The level range this mission appears under as a Flashback. Leave undefined if the same as the base mission.
66
+ */
67
+ readonly levelRange?: [number, number?]
68
+
69
+ /**
70
+ * The name as it appears in the Flashback list. Leave undefined if the same as the base mission.
71
+ */
72
+ readonly name?: string
73
+
74
+ /**
75
+ * The character moralities that the mission will appear for in the Flashback list. Leave undefined if the same as the base mission.
76
+ */
77
+ readonly morality?: MoralityExtended | MoralityExtended[]
78
+
79
+ /**
80
+ * Freeform notes or tips about the Flashback version of the mission.
81
+ */
82
+ readonly notes?: MarkdownString
83
+ }
@@ -0,0 +1,2 @@
1
+ export const MISSION_TYPE = ['story-arc', 'mission', 'task-force', 'strike-force', 'trial', 'personal-story'] as const
2
+ export type MissionType = typeof MISSION_TYPE[number]
@@ -0,0 +1,31 @@
1
+ export const MORALITY = ['hero', 'vigilante', 'villain', 'rogue', 'resistance', 'loyalist'] as const
2
+ export type Morality = typeof MORALITY[number]
3
+ export type MoralityExtended = Morality
4
+ /**
5
+ * Any of the Primal Earth moralities - Hero, Vigilante, Villain, Rogue.
6
+ */
7
+ | 'primal'
8
+ /**
9
+ * Either of the Praetorian Earth moralities - Resistance or Loyalist.
10
+ */
11
+ | 'praetorian'
12
+ /**
13
+ * The moralities that roll up to the Hero {@link Alignment} - Hero and Vigilante.
14
+ */
15
+ | 'heroic'
16
+ /**
17
+ * The moralities that roll up to the Villain {@link Alignment} - Villain and Rogue.
18
+ */
19
+ | 'villainous'
20
+ /**
21
+ * Moralities with access to Paragon City - Hero, Vigilante and Rogue.
22
+ */
23
+ | 'paragon-city-access'
24
+ /**
25
+ * Moralities with access to the Rogue Isles - Villain, Rogue and Vigilante.
26
+ */
27
+ | 'rogue-isles-access'
28
+ /**
29
+ * All the moralities.
30
+ */
31
+ | 'all'
@@ -1,3 +1,10 @@
1
1
  export const SEX = ['M', 'F'] as const
2
-
3
2
  export type Sex = typeof SEX[number]
3
+
4
+ const SEX_ORDER = Object.fromEntries(SEX.map((x, index) => [x, index]))
5
+
6
+ export function compareSex(a?: Sex, b?: Sex): number {
7
+ const orderA = a ? SEX_ORDER[a] : -1
8
+ const orderB = b ? SEX_ORDER[b] : -1
9
+ return orderA - orderB
10
+ }
@@ -4,7 +4,7 @@ export interface ZoneData {
4
4
  /**
5
5
  * Unique key used to reference this zone.
6
6
  *
7
- * Keys can only contain lowercase letters, numbers and hyphens (`-`).
7
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
8
8
  */
9
9
  readonly key: string
10
10
 
@@ -7,15 +7,16 @@ export const CHANGELOG: Change[] = [
7
7
  description: ''
8
8
  + '* Replaced redundant interfaces with their concrete equivalents.\n'
9
9
  + `* Server groups are now referred to as 'forks'.\n`
10
- + '* Replaced enums with union types.\n'
11
- + '* `IServerGroupData` is now `ContentBundle` and each database instance is now designed to accept only a single server group.\n'
10
+ + '* Replaced enums with union types and changed values to `kebab-case`.\n'
11
+ + '* `IServerGroupData` is now `ContentBundle` and each database instance is now designed to accept only a single bundle.\n'
12
12
  + '* `GameMap` is now `Zone`.\n'
13
13
  + '* Removed the `serverGroup` property from entities to simplify the object tree given that only a single context can exist per db now.\n'
14
14
  + '* Added a simple indexing and search function for badge names, text and acquisition info.\n'
15
15
  + '* Zone and badge references now follow a standard Markdown link format with a `badge://` or `map://` protocol.\n'
16
- + '* Badge partials are now known as badge requirements and support both AND and OR groups of requirements.\n'
16
+ + '* Badge partials are now known as badge requirements.\n'
17
17
  + '* Removed the `VidiotMap` API as it was never used or fleshed out properly.\n'
18
- + '* Added support for story arcs to badge requirements including a link to the contact.\n'
18
+ + '* Added formal support for Missions and Contacts in badge requirements.\n'
19
+ + '* Move exploration badge locations into badge requirement list.\n'
19
20
  + '* Standardized pluralization of some field names (name, icon).\n'
20
21
  + '* Combined `settitle` ids into a single tuple field.\n'
21
22
  + '* Change from GNU to The Unlicense.\n'
@@ -0,0 +1,54 @@
1
+ import { ALIGNMENT, Alignment, AlignmentExtended } from '../api/alignment'
2
+
3
+ export class AlignmentList {
4
+ readonly #items: Set<Alignment>
5
+
6
+ readonly hero: boolean
7
+ readonly villain: boolean
8
+ readonly praetorian: boolean
9
+
10
+ readonly primal: boolean
11
+ readonly all: boolean
12
+
13
+ constructor(items?: AlignmentExtended[]) {
14
+ const set = new Set(items ?? [...ALIGNMENT])
15
+ this.hero = set.has('hero') || set.has('primal') || set.has('all')
16
+ this.villain = set.has('villain') || set.has('primal') || set.has('all')
17
+ this.praetorian = set.has('praetorian') || set.has('all')
18
+
19
+ this.primal = this.hero && this.villain
20
+ this.all = this.hero && this.villain && this.praetorian
21
+
22
+ this.#items = new Set()
23
+ if (this.hero) this.#items.add('hero')
24
+ if (this.villain) this.#items.add('villain')
25
+ if (this.praetorian) this.#items.add('praetorian')
26
+ }
27
+
28
+ get items(): Alignment[] {
29
+ return [...this.#items]
30
+ }
31
+
32
+ has(alignment?: AlignmentExtended): boolean {
33
+ switch (alignment) {
34
+ case 'hero': {
35
+ return this.hero
36
+ }
37
+ case 'villain': {
38
+ return this.villain
39
+ }
40
+ case 'praetorian': {
41
+ return this.praetorian
42
+ }
43
+ case 'primal' : {
44
+ return this.primal
45
+ }
46
+ case 'all': {
47
+ return this.all
48
+ }
49
+ default: {
50
+ return false
51
+ }
52
+ }
53
+ }
54
+ }
@@ -1,16 +1,21 @@
1
1
  import { AlternateData } from '../api/alternate-data'
2
- import { Sex } from '../api/sex'
3
- import { Alignment } from '../api/alignment'
4
-
5
- const ALIGNMENT_SORT: Record<string, number> = { H: 2, V: 1, P: 0 }
6
- const SEX_SORT: Record<string, number> = { M: 1, F: 0 }
2
+ import { compareSex, Sex } from '../api/sex'
3
+ import { Alignment, compareAlignment } from '../api/alignment'
7
4
 
8
5
  export class Alternates<T> {
9
6
  readonly #sortedValues: AlternateData<T>[] = []
10
7
 
11
- constructor(values: AlternateData<T>[]) {
12
- this.#sortedValues = values.sort()
13
- this.#sortedValues.sort((a, b) => this.#compareAlternates(a, b))
8
+ /**
9
+ * Create an alternate set from either a list of categorized values, or a single value when there are no alternates.
10
+ * @param value List of alternates, or a single value.
11
+ */
12
+ constructor(value: AlternateData<T>[] | T) {
13
+ if (Array.isArray(value)) {
14
+ this.#sortedValues = value.sort()
15
+ this.#sortedValues.sort((a, b) => this.#compareAlternates(a, b))
16
+ } else {
17
+ this.#sortedValues = [{ value }]
18
+ }
14
19
  }
15
20
 
16
21
  getValue(alignment?: Alignment, sex?: Sex): T | undefined {
@@ -51,34 +56,12 @@ export class Alternates<T> {
51
56
  const bSpecificity = (b.alignment ? 2 : 0) + (b.sex ? 1 : 0)
52
57
  if (aSpecificity !== bSpecificity) return aSpecificity - bSpecificity // Order first by least-specific
53
58
 
54
- const alignmentComparison = this.#compareAlignment(a.alignment, b.alignment) // Next by alignment
59
+ const alignmentComparison = compareAlignment(a.alignment, b.alignment) // Next by alignment
55
60
  if (alignmentComparison !== 0) return alignmentComparison
56
61
 
57
- const sexComparison = this.#compareSex(a.sex, b.sex) // Last by sex
62
+ const sexComparison = compareSex(a.sex, b.sex) // Last by sex
58
63
  if (sexComparison !== 0) return sexComparison
59
64
 
60
65
  return String(a.value).localeCompare(String(b.value))
61
66
  }
62
-
63
- #compareAlignment(a?: Alignment, b?: Alignment): number {
64
- if (a === b) return 0
65
- if (a === undefined && b !== undefined) return -1
66
- if (b === undefined && a !== undefined) return 1
67
-
68
- const aSort = a === undefined ? -1 : ALIGNMENT_SORT[a] ?? -1 // Unknown values get -1 priority
69
- const bSort = b === undefined ? -1 : ALIGNMENT_SORT[b] ?? -1
70
-
71
- return bSort - aSort
72
- }
73
-
74
- #compareSex(a?: Sex, b?: Sex): number {
75
- if (a === b) return 0
76
- if (a === undefined && b !== undefined) return -1
77
- if (b === undefined && a !== undefined) return 1
78
-
79
- const aSort = SEX_SORT[a ?? -1] ?? -1 // Unknown values get -1 priority
80
- const bSort = SEX_SORT[b ?? -1] ?? -1
81
-
82
- return bSort - aSort
83
- }
84
67
  }
@@ -1,21 +1,12 @@
1
- import { Badge } from './badge'
1
+ import { Badge, compareByDefaultName, compareByZoneKey } from './badge'
2
2
  import { BadgeSearchOptions } from './badge-search-options'
3
- import { Zone } from './zone'
4
3
  import { Paged } from './paged'
5
4
 
6
5
  export class BadgeIndex {
7
6
  readonly #badges: Badge[] = []
8
7
  readonly #badgeIndex: Record<string, Badge> = {}
9
8
 
10
- readonly #zoneOrder: Record<string, number> = {}
11
-
12
- constructor(badges: Badge[], zones?: Zone[]) {
13
- this.#zoneOrder = Object.fromEntries(
14
- zones
15
- ?.sort((a, b) => a.name.localeCompare(b.name))
16
- ?.map((x, index) => [x.key, index]) ?? [],
17
- )
18
-
9
+ constructor(badges: Badge[]) {
19
10
  this.#badges = badges
20
11
  for (const badge of badges) {
21
12
  if (this.#badgeIndex[badge.key] !== undefined) throw new Error(`Duplicate badge key [${badge.key}]`)
@@ -23,14 +14,9 @@ export class BadgeIndex {
23
14
  }
24
15
  }
25
16
 
26
- getBadge(key: string): Badge {
27
- const result = this.#badgeIndex[key]
28
- if (result === undefined) throw new Error(`Unknown badge key [${key}]`)
29
- return result
30
- }
31
-
32
- badgeExists(key: string): boolean {
33
- return !!this.#badgeIndex[key]
17
+ getBadge(key?: string): Badge | undefined {
18
+ if (!key) return undefined
19
+ return this.#badgeIndex[key]
34
20
  }
35
21
 
36
22
  searchBadges(options?: BadgeSearchOptions): Paged<Badge> {
@@ -60,34 +46,23 @@ export class BadgeIndex {
60
46
  || (query?.on?.acquisition && badge.acquisition?.toLowerCase().includes(queryString))
61
47
  || (query?.on?.effect && badge.effect?.toLowerCase().includes(queryString))
62
48
  || (query?.on?.notes && badge.notes?.toLowerCase().includes(queryString))
63
- || (query?.on?.setTitle && (badge.setTitle?.id?.toString().includes(queryString) || badge.setTitle?.praetorianId?.toString().includes(queryString))))
49
+ || (query?.on?.setTitle && (badge.setTitleId?.some(x => x?.toString().includes(queryString)))))
64
50
  }
65
51
 
66
52
  #satisfiesFilterPredicate(badge: Badge, filter?: BadgeSearchOptions['filter']): boolean {
67
53
  return (!filter?.type || badge.type === filter.type)
68
54
  && (!filter?.zoneKey || badge.zoneKey === filter.zoneKey)
69
- && (!filter?.alignment || badge.alignment.items.includes(filter.alignment))
55
+ && (!filter?.morality || badge.morality.has(filter.morality))
70
56
  }
71
57
 
72
58
  #sort(badges: Badge[], sort?: BadgeSearchOptions['sort']): Badge[] {
73
59
  if (!sort) return badges
74
- const ascending = sort.dir !== 'DESC'
75
-
76
- if (!sort.by || sort.by === 'CANONICAL') return sort.dir === 'DESC' ? badges.reverse() : badges
77
-
78
- if (sort.by === 'BADGE_NAME') return ascending
79
- ? badges.sort((a, b) => a.name.default?.value.localeCompare(b.name.default?.value ?? '') ?? 0)
80
- : badges.sort((a, b) => b.name.default?.value.localeCompare(a.name.default?.value ?? '') ?? 0)
60
+ const ascending = sort.dir !== 'desc'
81
61
 
82
- return badges.sort((a, b) => {
83
- const aIndex = this.#zoneOrder[a.zoneKey ?? '']
84
- const bIndex = this.#zoneOrder[b.zoneKey ?? '']
62
+ if (sort.by === 'badge-name') return badges.sort((a, b) => ascending ? compareByDefaultName(a, b) : compareByDefaultName(b, a))
85
63
 
86
- if (aIndex === bIndex) return 0
87
- if (aIndex === undefined) return ascending ? 1 : -1
88
- if (bIndex === undefined) return ascending ? -1 : 1
64
+ if (sort.by === 'zone-key') return badges.sort((a, b) => ascending ? compareByZoneKey(a, b) : compareByZoneKey(b, a))
89
65
 
90
- return ascending ? aIndex - bIndex : bIndex - aIndex
91
- })
66
+ return sort.dir === 'desc' ? badges.reverse() : badges
92
67
  }
93
68
  }
@@ -1,79 +1,62 @@
1
1
  import { BadgeRequirementData } from '../api/badge-requirement-data'
2
- import { PlaqueType } from '../api/plaque-type'
3
2
  import { BadgeRequirementType } from '../api/badge-requirement-type'
4
3
  import { EnhancementCategory } from '../api/enhancement-category'
5
4
  import { Key } from './key'
6
5
  import { MarkdownString } from '../api/markdown-string'
7
6
  import { Link } from '../api/link'
7
+ import { Location } from './location'
8
+ import { coalesceToArray } from '../util'
8
9
 
9
10
  export class BadgeRequirement {
10
11
  /**
11
- * Key.
12
+ * Unique key used to reference this badge requirement.
13
+ *
14
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
12
15
  */
13
16
  readonly key: string
14
17
 
15
18
  /**
16
- * Type of requirement.
19
+ * The requirement type.
17
20
  */
18
21
  readonly type: BadgeRequirementType
19
22
 
20
23
  /**
21
- * Zone the requirement is located in.
24
+ * If the requirement involves a location, where it is.
22
25
  */
23
- readonly zoneKey?: string
26
+ readonly location?: Location[]
24
27
 
25
28
  /**
26
- * /loc coordinates.
27
- */
28
- readonly loc?: number[]
29
-
30
- /**
31
- * Is it a wall plaque or a physical monument?
32
- */
33
- readonly plaqueType?: PlaqueType
34
-
35
- /**
36
- * Plaque inscription.
37
- */
38
- readonly plaqueInscription?: string
39
-
40
- /**
41
- * The number or letter the plaque appears as on Vidiot Maps.
42
- */
43
- readonly vidiotMapKey?: string
44
-
45
- /**
46
- * The key of the badge for this requirement.
29
+ * If the requirement involves a badge, the badge key.
47
30
  */
48
31
  readonly badgeKey?: string
49
32
 
50
33
  /**
51
- * Mission name.
34
+ * If the requirement involves a mission, the mission key.
52
35
  */
53
- readonly missionName?: string
36
+ readonly missionKey?: string
54
37
 
55
38
  /**
56
- * {@link Contact} key for the story arc.
39
+ * If the requirement involves a monument, the text that is displayed thereon.
57
40
  */
58
- readonly contactKey?: string
41
+ readonly monumentText?: string
59
42
 
60
43
  /**
61
- * Level of the invention required.
44
+ * If the requirement involves crafting an invention, the Level of the invention required.
62
45
  */
63
46
  readonly inventionLevel?: number
64
47
 
65
48
  /**
66
- * The types of enhancements required to be crafted.
49
+ * If the requirement involves crafting an invention, the types of enhancements that will qualify.
67
50
  */
68
51
  readonly inventionTypes?: EnhancementCategory[]
69
52
 
70
53
  /**
71
- * Number of invention crafts required.
54
+ * Number of times the task needs to be repeated.
72
55
  */
73
- readonly inventionCount?: number
56
+ readonly count?: number
74
57
 
75
58
  /**
76
- * Any additional notes.
59
+ * Additional information about the requirement.
77
60
  */
78
61
  readonly notes?: MarkdownString
79
62
 
@@ -85,17 +68,13 @@ export class BadgeRequirement {
85
68
  constructor(data: BadgeRequirementData) {
86
69
  this.key = new Key(data.key).value
87
70
  this.type = data.type
88
- this.zoneKey = data.zoneKey
89
- this.loc = data.loc
90
- this.plaqueType = data.plaqueType
91
- this.plaqueInscription = data.plaqueInscription
92
- this.vidiotMapKey = data.vidiotMapKey
71
+ this.location = coalesceToArray(data.location)
93
72
  this.badgeKey = data.badgeKey
94
- this.missionName = data.missionName
95
- this.contactKey = data.contactKey
73
+ this.missionKey = data.missionKey
74
+ this.monumentText = data.monumentText
96
75
  this.inventionLevel = data.inventionLevel
97
76
  this.inventionTypes = data.inventionTypes
98
- this.inventionCount = data.inventionCount
77
+ this.count = data.count
99
78
  this.notes = data.notes
100
79
  this.links = data.links ?? []
101
80
  }
@@ -1,5 +1,5 @@
1
1
  import { BadgeType } from '../api/badge-type'
2
- import { Alignment } from '../api/alignment'
2
+ import { MoralityExtended } from '../api/morality'
3
3
 
4
4
  export interface BadgeSearchOptions {
5
5
 
@@ -26,7 +26,7 @@ export interface BadgeSearchOptions {
26
26
  filter?: {
27
27
  type?: BadgeType
28
28
  zoneKey?: string
29
- alignment?: Alignment
29
+ morality?: MoralityExtended
30
30
  }
31
31
 
32
32
  /**
@@ -35,8 +35,8 @@ export interface BadgeSearchOptions {
35
35
  * Badges are assumed to be in canonical order in the content bundle, and should match the in-game display order.
36
36
  */
37
37
  sort?: {
38
- by?: 'CANONICAL' | 'BADGE_NAME' | 'ZONE_NAME'
39
- dir?: 'ASC' | 'DESC'
38
+ by?: 'canonical' | 'badge-name' | 'zone-key'
39
+ dir?: 'asc' | 'desc'
40
40
  }
41
41
 
42
42
  /**