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

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 (72) hide show
  1. package/.github/workflows/build.yml +1 -1
  2. package/.github/workflows/pull-request.yml +1 -1
  3. package/.github/workflows/release.yml +1 -1
  4. package/README.md +3 -3
  5. package/dist/coh-content-db.d.ts +444 -194
  6. package/dist/coh-content-db.js +720 -413
  7. package/dist/coh-content-db.js.map +1 -1
  8. package/dist/coh-content-db.mjs +708 -412
  9. package/dist/coh-content-db.mjs.map +1 -1
  10. package/eslint.config.mjs +1 -0
  11. package/package.json +1 -1
  12. package/src/main/api/alignment.ts +18 -2
  13. package/src/main/api/badge-data.ts +12 -42
  14. package/src/main/api/badge-requirement-data.ts +17 -35
  15. package/src/main/api/badge-requirement-type.ts +28 -7
  16. package/src/main/api/badge-type.ts +15 -15
  17. package/src/main/api/contact-data.ts +7 -5
  18. package/src/main/api/content-bundle.ts +6 -0
  19. package/src/main/api/enhancement-category.ts +26 -26
  20. package/src/main/api/location-data.ts +28 -0
  21. package/src/main/api/mission-data.ts +83 -0
  22. package/src/main/api/mission-type.ts +2 -0
  23. package/src/main/api/morality.ts +31 -0
  24. package/src/main/api/sex.ts +8 -1
  25. package/src/main/api/zone-data.ts +1 -1
  26. package/src/main/changelog.ts +5 -4
  27. package/src/main/db/abstract-index.ts +41 -0
  28. package/src/main/db/alignment-list.ts +54 -0
  29. package/src/main/db/alternates.ts +15 -32
  30. package/src/main/db/badge-index.ts +14 -50
  31. package/src/main/db/badge-requirement.ts +22 -43
  32. package/src/main/db/badge-search-options.ts +4 -4
  33. package/src/main/db/badge.ts +53 -54
  34. package/src/main/db/bundle-metadata.ts +8 -2
  35. package/src/main/db/coh-content-database.ts +80 -67
  36. package/src/main/db/contact.ts +17 -14
  37. package/src/main/db/location.ts +30 -0
  38. package/src/main/db/mission.ts +107 -0
  39. package/src/main/db/morality-list.ts +99 -0
  40. package/src/main/db/zone.ts +1 -1
  41. package/src/main/index.ts +8 -3
  42. package/src/main/util.ts +43 -3
  43. package/src/test/api/alignment.test.ts +38 -4
  44. package/src/test/api/badge-data.fixture.ts +1 -17
  45. package/src/test/api/badge-data.test.ts +3 -3
  46. package/src/test/api/badge-requirement-data.fixture.ts +1 -11
  47. package/src/test/api/badge-requirement-type.test.ts +3 -3
  48. package/src/test/api/badge-type.test.ts +5 -5
  49. package/src/test/api/contact-data.fixture.ts +0 -6
  50. package/src/test/api/content-bundle.fixture.ts +1 -17
  51. package/src/test/api/enhancement-category.test.ts +5 -5
  52. package/src/test/api/mission-data.fixture.ts +12 -0
  53. package/src/test/api/sex.test.ts +33 -1
  54. package/src/test/api/zone-data.fixture.ts +1 -1
  55. package/src/test/db/abstract-index.test.ts +86 -0
  56. package/src/test/db/alignment-list.test.ts +200 -0
  57. package/src/test/db/alternates.test.ts +60 -56
  58. package/src/test/db/badge-index.test.ts +220 -183
  59. package/src/test/db/badge-requirement.test.ts +35 -70
  60. package/src/test/db/badge.test.ts +185 -64
  61. package/src/test/db/bundle-metadata.test.ts +17 -0
  62. package/src/test/db/coh-content-database.test.ts +193 -119
  63. package/src/test/db/contact.test.ts +25 -24
  64. package/src/test/db/location.test.ts +51 -0
  65. package/src/test/db/mission.test.ts +171 -0
  66. package/src/test/db/morality-list.test.ts +457 -0
  67. package/src/test/db/zone.test.ts +4 -4
  68. package/src/test/util.test.ts +54 -1
  69. package/src/main/api/plaque-type.ts +0 -6
  70. package/src/main/db/alignments.ts +0 -17
  71. package/src/test/api/alignments.test.ts +0 -40
  72. package/src/test/api/plaque-type.test.ts +0 -31
@@ -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
  /**
@@ -4,11 +4,13 @@ import { BadgeData } from '../api/badge-data'
4
4
  import { BadgeRequirement } from './badge-requirement'
5
5
  import { Key } from './key'
6
6
  import { Alternates } from './alternates'
7
- import { Alignments } from './alignments'
8
7
  import { MarkdownString } from '../api/markdown-string'
8
+ import { coalesceToArray } from '../util'
9
+ import { MoralityList } from './morality-list'
9
10
 
10
11
  export class Badge {
11
12
  readonly #requirementsIndex: Record<string, BadgeRequirement> = {}
13
+ readonly #zoneKeys = new Set<string>()
12
14
 
13
15
  /**
14
16
  * The database key for this badge.
@@ -28,14 +30,14 @@ export class Badge {
28
30
  readonly name: Alternates<string>
29
31
 
30
32
  /**
31
- * The character alignments that this badge is available to.
33
+ * The character moralities that this badge is available to.
32
34
  */
33
- readonly alignment: Alignments
35
+ readonly morality: MoralityList
34
36
 
35
37
  /**
36
38
  * The badge text as it appears in-game. May vary by character sex or alignment.
37
39
  */
38
- readonly badgeText: Alternates<string>
40
+ readonly badgeText: Alternates<MarkdownString>
39
41
 
40
42
  /**
41
43
  * Short description of how to acquire the badge. Detailed instructions will be in the notes field.
@@ -60,33 +62,10 @@ export class Badge {
60
62
  readonly links: Link[]
61
63
 
62
64
  /**
63
- * For exploration badges, the key of the {@link Zone} that this badge is found on.
65
+ * The id used with the in-game `/settitle` command to apply the badge.
66
+ * The first value is the id for primal characters and the (optional) second number is the id for praetorian characters.
64
67
  */
65
- readonly zoneKey?: string
66
-
67
- /**
68
- * For exploration badges, the `/loc` coordinates of the badge.
69
- */
70
- readonly loc?: [number, number, number]
71
-
72
- /**
73
- * For plaques that appear on a Vidiot Map, the number or letter the badge appears as.
74
- */
75
- readonly vidiotMapKey?: string
76
-
77
- /**
78
- * ID used with the in-game `/settitle` command to apply the badge.
79
- */
80
- readonly setTitle?: {
81
- /**
82
- * `/settitle` id.
83
- */
84
- id?: number
85
- /**
86
- * `/settitle` id if different for praetorian characters.
87
- */
88
- praetorianId?: number
89
- }
68
+ readonly setTitleId?: [number, number?]
90
69
 
91
70
  /**
92
71
  * A description of the effect the badge will have, such as a buff or granting a temporary power.
@@ -94,16 +73,9 @@ export class Badge {
94
73
  readonly effect?: MarkdownString
95
74
 
96
75
  /**
97
- * Represents the layered requirements for badges with multiple fulfillment steps,
98
- * such as visiting plaques for history badges or collecting other badges.
99
- *
100
- * The outer array represents groups of requirements evaluated with OR logic —
101
- * fulfilling any group satisfies the badge.
102
- *
103
- * Each inner array represents individual requirements evaluated with AND logic —
104
- * all conditions in the group must be met.
76
+ * Represents the requirements for badges with multiple fulfillment steps, such as visiting monuments for history badges, completing missions, or collecting other badges.
105
77
  */
106
- readonly requirements?: BadgeRequirement[][]
78
+ readonly requirements?: BadgeRequirement[]
107
79
 
108
80
  /**
109
81
  * Some badges are not included in the badge total count... such as Flames of Prometheus, which can be removed by redeeming it for a Notice of the Well.
@@ -114,29 +86,24 @@ export class Badge {
114
86
  this.key = new Key(badgeData.key).value
115
87
  this.type = badgeData.type
116
88
  this.name = new Alternates(badgeData.name)
117
- this.alignment = new Alignments(badgeData.alignment)
89
+ this.morality = new MoralityList(coalesceToArray(badgeData.morality))
118
90
  this.badgeText = new Alternates(badgeData.badgeText ?? [])
119
91
  this.acquisition = badgeData.acquisition
120
92
  this.icon = new Alternates(badgeData.icon ?? [])
121
93
  this.notes = badgeData.notes
122
94
  this.links = badgeData.links ?? []
123
- this.zoneKey = badgeData.zoneKey
124
- this.loc = badgeData.loc
125
95
  this.effect = badgeData.effect
126
- this.vidiotMapKey = badgeData.vidiotMapKey
127
- this.setTitle = badgeData.setTitle
96
+ this.setTitleId = badgeData.setTitleId
128
97
  this.ignoreInTotals = badgeData.ignoreInTotals ?? false
129
98
 
130
- this.requirements = badgeData.requirements?.map((groups, index) => {
131
- const existingKeysInGroup = new Set<string>()
132
- return groups.map((requirementData) => {
133
- if (existingKeysInGroup.has(requirementData.key)) throw new Error(`Duplicate badge requirement key [${badgeData.key}:${requirementData.key}] in group [${index + 1}]`)
134
- existingKeysInGroup.add(requirementData.key)
135
-
136
- const badge = new BadgeRequirement(requirementData)
137
- this.#requirementsIndex[badge.key] = badge
138
- return badge
139
- })
99
+ this.requirements = badgeData.requirements?.map((requirementData) => {
100
+ if (this.#requirementsIndex[requirementData.key]) throw new Error(`Duplicate badge requirement key [${badgeData.key}:${requirementData.key}]`)
101
+ const requirement = new BadgeRequirement(requirementData)
102
+ this.#requirementsIndex[requirement.key] = requirement
103
+ if (requirement.location) for (const location of requirement.location) {
104
+ if (location.zoneKey) this.#zoneKeys.add(location.zoneKey)
105
+ }
106
+ return requirement
140
107
  })
141
108
  }
142
109
 
@@ -145,4 +112,36 @@ export class Badge {
145
112
  if (result === undefined) throw new Error(`Unknown badge requirement key [${key}]`)
146
113
  return result
147
114
  }
115
+
116
+ /**
117
+ * Return a list of all the zone keys referenced by this badge.
118
+ */
119
+ get zoneKeys(): string[] {
120
+ return [...this.#zoneKeys]
121
+ }
122
+
123
+ /**
124
+ * The zone key if this badge relates to a single zone.
125
+ */
126
+ get zoneKey(): string | undefined {
127
+ return this.#zoneKeys.size === 1 ? this.#zoneKeys.values().next().value : undefined
128
+ }
129
+ }
130
+
131
+ export function compareByDefaultName(a?: Badge, b?: Badge): number {
132
+ const aName = a?.name.default?.value
133
+ const bName = b?.name.default?.value
134
+ if (!aName && !bName) return 0
135
+ if (!aName) return 1
136
+ if (!bName) return -1
137
+ return aName.localeCompare(bName)
138
+ }
139
+
140
+ export function compareByZoneKey(a?: Badge, b?: Badge): number {
141
+ const aZone = a?.zoneKey
142
+ const bZone = b?.zoneKey
143
+ if (!aZone && !bZone) return 0
144
+ if (!aZone) return 1
145
+ if (!bZone) return -1
146
+ return aZone.localeCompare(bZone)
148
147
  }
@@ -22,12 +22,17 @@ export class BundleMetadata {
22
22
  /**
23
23
  * List of external links. Wiki, forums, etc.
24
24
  */
25
- readonly links: Link[]
25
+ readonly links?: Link[]
26
26
 
27
27
  /**
28
28
  * Change log for this data package.
29
29
  */
30
- readonly changelog: Change[]
30
+ readonly changelog?: Change[]
31
+
32
+ /**
33
+ * The current version of the data package.
34
+ */
35
+ readonly version?: string
31
36
 
32
37
  constructor(bundle: ContentBundle) {
33
38
  this.name = bundle.name
@@ -35,5 +40,6 @@ export class BundleMetadata {
35
40
  this.repository = bundle.repository
36
41
  this.links = bundle.links ?? []
37
42
  this.changelog = bundle.changelog ?? []
43
+ this.version = this.changelog?.at(-1)?.version
38
44
  }
39
45
  }
@@ -7,110 +7,123 @@ import { BadgeIndex } from './badge-index'
7
7
  import { BadgeSearchOptions } from './badge-search-options'
8
8
  import { Paged } from './paged'
9
9
  import { Contact } from './contact'
10
+ import { Mission } from './mission'
11
+ import { AbstractIndex } from './abstract-index'
10
12
 
11
13
  export class CohContentDatabase {
12
- readonly #archetypeIndex: Record<string, Archetype> = {}
13
- readonly #zoneIndex: Record<string, Zone> = {}
14
- readonly #contactIndex: Record<string, Contact> = {}
15
- readonly #badgeIndex: BadgeIndex
14
+ #archetypeIndex = new AbstractIndex<Archetype>('key')
15
+ #zoneIndex = new AbstractIndex<Zone>('key')
16
+ #contactIndex = new AbstractIndex<Contact>('key')
17
+ #missionIndex = new AbstractIndex<Mission>('key')
18
+ #badgeIndex = new BadgeIndex()
19
+
20
+ #metadata?: BundleMetadata
21
+ #servers?: string[]
22
+
23
+ /**
24
+ * Load the given content bundle, resetting the db if a bundle is already loaded.
25
+ * @param bundle The bundle to load.
26
+ */
27
+ load(bundle: ContentBundle): void {
28
+ this.#metadata = new BundleMetadata(bundle)
29
+ this.#servers = bundle.servers ?? []
30
+
31
+ this.#archetypeIndex.load(bundle.archetypes?.map(x => new Archetype(x)))
32
+ this.#zoneIndex.load(bundle.zones?.map(x => new Zone(x)))
33
+ this.#contactIndex.load(bundle.contacts?.map(x => new Contact(x)))
34
+ this.#missionIndex.load(bundle.missions?.map(x => new Mission(x)))
35
+ this.#badgeIndex.load(bundle.badges?.map(x => new Badge(x)))
36
+ }
16
37
 
17
38
  /**
18
39
  * Metadata about the content bundle.
19
40
  */
20
- readonly metadata: BundleMetadata
41
+ get metadata(): BundleMetadata | undefined {
42
+ return this.#metadata
43
+ }
21
44
 
22
45
  /**
23
46
  * List of the game server names.
24
47
  *
25
48
  * Torchbearer, Excelsior, etc.
26
49
  */
27
- readonly servers: string[]
50
+ get servers(): string[] {
51
+ return this.#servers ?? []
52
+ }
28
53
 
29
54
  /**
30
55
  * List of archetypes.
31
56
  */
32
- readonly archetypes: Archetype[]
57
+ get archetypes(): Archetype[] {
58
+ return this.#archetypeIndex.values
59
+ }
33
60
 
34
61
  /**
35
- * List of game zones.
62
+ * Get archetype by key.
63
+ * @param key The key.
36
64
  */
37
- readonly zones: Zone[]
65
+ getArchetype(key: string | undefined): Archetype | undefined {
66
+ return this.#archetypeIndex.get(key)
67
+ }
38
68
 
39
69
  /**
40
- * List of contacts.
70
+ * List of game zones.
41
71
  */
42
- readonly contacts: Contact[]
72
+ get zones(): Zone[] {
73
+ return this.#zoneIndex.values
74
+ }
43
75
 
44
76
  /**
45
- * List of badges.
77
+ * Get zone by key.
78
+ * @param key The key.
46
79
  */
47
- readonly badges: Badge[]
80
+ getZone(key: string | undefined): Zone | undefined {
81
+ return this.#zoneIndex.get(key)
82
+ }
48
83
 
49
84
  /**
50
- * Initialize the database with a content bundle.
51
- * @param bundle The data to load.
85
+ * List of contacts.
52
86
  */
53
- constructor(bundle: ContentBundle) {
54
- this.metadata = new BundleMetadata(bundle)
55
- this.servers = bundle.servers ?? []
56
-
57
- this.archetypes = bundle.archetypes?.map((data) => {
58
- if (this.#archetypeIndex[data.key] !== undefined) throw new Error(`Duplicate archetype key '${data.key}'`)
59
- const archetype = new Archetype(data)
60
- this.#archetypeIndex[archetype.key] = archetype
61
- return archetype
62
- }) ?? []
63
-
64
- this.zones = bundle.zones?.map((data) => {
65
- if (this.#zoneIndex[data.key] !== undefined) throw new Error(`Duplicate zone key '${data.key}'`)
66
- const zone = new Zone(data)
67
- this.#zoneIndex[zone.key] = zone
68
- return zone
69
- }) ?? []
70
-
71
- this.contacts = bundle.contacts?.map((data) => {
72
- if (this.#contactIndex[data.key] !== undefined) throw new Error(`Duplicate contact key '${data.key}'`)
73
- const contact = new Contact(data)
74
- this.#contactIndex[contact.key] = contact
75
- return contact
76
- }) ?? []
77
-
78
- this.badges = bundle.badges?.map(data => new Badge(data)) ?? []
79
- this.#badgeIndex = new BadgeIndex(this.badges, this.zones)
87
+ get contacts(): Contact[] {
88
+ return this.#contactIndex.values
80
89
  }
81
90
 
82
- getArchetype(key: string): Archetype {
83
- const result = this.#archetypeIndex[key]
84
- if (result === undefined) throw new Error(`Unknown archetype key '${key}'`)
85
- return result
86
- }
87
-
88
- getZone(key: string): Zone {
89
- const result = this.#zoneIndex[key]
90
- if (result === undefined) throw new Error(`Unknown zone key '${key}'`)
91
- return result
92
- }
93
-
94
- getContact(key: string): Contact {
95
- const result = this.#contactIndex[key]
96
- if (result === undefined) throw new Error(`Unknown contact key '${key}'`)
97
- return result
91
+ /**
92
+ * Get contact by key.
93
+ * @param key The key.
94
+ */
95
+ getContact(key: string | undefined): Contact | undefined {
96
+ return this.#contactIndex.get(key)
98
97
  }
99
98
 
100
- zoneExists(key: string): boolean {
101
- return !!this.#zoneIndex[key]
99
+ /**
100
+ * List of missions.
101
+ */
102
+ get missions(): Mission[] {
103
+ return this.#missionIndex.values
102
104
  }
103
105
 
104
- contactExists(key: string): boolean {
105
- return !!this.#contactIndex[key]
106
+ /**
107
+ * Get mission by key.
108
+ * @param key The key.
109
+ */
110
+ getMission(key: string | undefined): Mission | undefined {
111
+ return this.#missionIndex.get(key)
106
112
  }
107
113
 
108
- getBadge(key: string): Badge {
109
- return this.#badgeIndex.getBadge(key)
114
+ /**
115
+ * List of badges.
116
+ */
117
+ get badges(): Badge[] {
118
+ return this.#badgeIndex.values
110
119
  }
111
120
 
112
- badgeExists(key: string): boolean {
113
- return this.#badgeIndex.badgeExists(key)
121
+ /**
122
+ * Get badge by key.
123
+ * @param key The key.
124
+ */
125
+ getBadge(key: string | undefined): Badge | undefined {
126
+ return this.#badgeIndex.get(key)
114
127
  }
115
128
 
116
129
  /**
@@ -120,6 +133,6 @@ export class CohContentDatabase {
120
133
  * @param options {@link BadgeSearchOptions}
121
134
  */
122
135
  searchBadges(options?: BadgeSearchOptions): Paged<Badge> {
123
- return this.#badgeIndex.searchBadges(options)
136
+ return this.#badgeIndex.search(options)
124
137
  }
125
138
  }
@@ -2,12 +2,15 @@ import { Link } from '../api/link'
2
2
  import { Key } from './key'
3
3
  import { MarkdownString } from '../api/markdown-string'
4
4
  import { ContactData } from '../api/contact-data'
5
+ import { Location } from './location'
6
+ import { MoralityList } from './morality-list'
7
+ import { coalesceToArray } from '../util'
5
8
 
6
9
  export class Contact {
7
10
  /**
8
11
  * Unique key used to reference this contact.
9
12
  *
10
- * Keys can only contain lowercase letters, numbers and hyphens (`-`).
13
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
11
14
  */
12
15
  readonly key: string
13
16
 
@@ -22,14 +25,14 @@ export class Contact {
22
25
  readonly title?: string
23
26
 
24
27
  /**
25
- * The zone this character is located in.
28
+ * The character moralities that this contact will interact with.
26
29
  */
27
- readonly zoneKey?: string
30
+ readonly morality?: MoralityList
28
31
 
29
32
  /**
30
- * The `/loc` coordinates of the contact.
33
+ * The location of this contact.
31
34
  */
32
- readonly loc?: [number, number, number]
35
+ readonly location?: Location
33
36
 
34
37
  /**
35
38
  * The level range this contact will offer missions for.
@@ -46,14 +49,14 @@ export class Contact {
46
49
  */
47
50
  readonly links: Link[]
48
51
 
49
- constructor(contactData: ContactData) {
50
- this.key = new Key(contactData.key).value
51
- this.name = contactData.name
52
- this.title = contactData.title
53
- this.zoneKey = contactData.zoneKey
54
- this.loc = contactData.loc
55
- this.levelRange = contactData.levelRange
56
- this.notes = contactData.notes
57
- this.links = contactData.links ?? []
52
+ constructor(data: ContactData) {
53
+ this.key = new Key(data.key).value
54
+ this.name = data.name
55
+ this.title = data.title
56
+ this.morality = new MoralityList(coalesceToArray(data.morality))
57
+ this.location = data.location
58
+ this.levelRange = data.levelRange
59
+ this.notes = data.notes
60
+ this.links = data.links ?? []
58
61
  }
59
62
  }
@@ -0,0 +1,30 @@
1
+ import { Coords, LocationData, LocationIcon } from '../api/location-data'
2
+
3
+ export class Location {
4
+ /**
5
+ * Key of the {@link Zone} that the location references.
6
+ */
7
+ readonly zoneKey?: string
8
+
9
+ /**
10
+ * In-game `/loc` coordinates of the location.
11
+ */
12
+ readonly coords?: Coords
13
+
14
+ /**
15
+ * The type of icon to use if the location appears on a map. (Typically the Vidiot map icon).
16
+ */
17
+ readonly icon?: LocationIcon
18
+
19
+ /**
20
+ * The text that should appear in the location icon. (Typically a number or symbol from the Vidiot map).
21
+ */
22
+ readonly iconText?: string
23
+
24
+ constructor(data: LocationData) {
25
+ this.zoneKey = data.zoneKey
26
+ this.coords = data.coords
27
+ this.icon = data.icon
28
+ this.iconText = data.iconText
29
+ }
30
+ }