coh-content-db 2.0.0-rc.1 → 2.0.0-rc.10

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 (100) hide show
  1. package/.editorconfig +10 -11
  2. package/.github/workflows/build.yml +1 -1
  3. package/.github/workflows/pull-request.yml +1 -1
  4. package/.github/workflows/release.yml +2 -2
  5. package/CHANGELOG.md +42 -0
  6. package/README.md +52 -24
  7. package/dist/coh-content-db.d.ts +683 -279
  8. package/dist/coh-content-db.js +834 -377
  9. package/dist/coh-content-db.js.map +1 -1
  10. package/dist/coh-content-db.mjs +809 -368
  11. package/dist/coh-content-db.mjs.map +1 -1
  12. package/eslint.config.mjs +1 -0
  13. package/package.json +1 -1
  14. package/src/main/api/alignment.ts +18 -2
  15. package/src/main/api/alternate-data.ts +2 -2
  16. package/src/main/api/badge-data.ts +20 -48
  17. package/src/main/api/badge-requirement-data.ts +64 -0
  18. package/src/main/api/badge-requirement-type.ts +32 -0
  19. package/src/main/api/badge-type.ts +15 -15
  20. package/src/main/api/bundle-data.ts +47 -0
  21. package/src/main/api/bundle-header-data.ts +37 -0
  22. package/src/main/api/contact-data.ts +48 -0
  23. package/src/main/api/enhancement-category.ts +26 -26
  24. package/src/main/api/location-data.ts +28 -0
  25. package/src/main/api/markdown-string.ts +4 -0
  26. package/src/main/api/mission-data.ts +83 -0
  27. package/src/main/api/mission-type.ts +2 -0
  28. package/src/main/api/morality.ts +31 -0
  29. package/src/main/api/sex.ts +8 -1
  30. package/src/main/api/zone-data.ts +20 -0
  31. package/src/main/db/abstract-index.ts +37 -0
  32. package/src/main/db/alignment-list.ts +54 -0
  33. package/src/main/db/alternates.ts +28 -42
  34. package/src/main/db/badge-index.ts +57 -0
  35. package/src/main/db/badge-requirement.ts +81 -0
  36. package/src/main/db/badge-search-options.ts +51 -0
  37. package/src/main/db/badge.ts +77 -71
  38. package/src/main/db/bundle-header.ts +44 -0
  39. package/src/main/db/coh-content-database.ts +123 -14
  40. package/src/main/db/contact.ts +62 -0
  41. package/src/main/db/location.ts +30 -0
  42. package/src/main/db/mission.ts +107 -0
  43. package/src/main/db/morality-list.ts +99 -0
  44. package/src/main/db/paged.ts +7 -0
  45. package/src/main/db/zone.ts +28 -0
  46. package/src/main/index.ts +23 -15
  47. package/src/main/util.ts +108 -7
  48. package/src/test/api/alignment.test.ts +38 -4
  49. package/src/test/api/badge-data.fixture.ts +1 -15
  50. package/src/test/api/badge-data.test.ts +4 -4
  51. package/src/test/api/badge-requirement-data.fixture.ts +7 -0
  52. package/src/test/api/badge-requirement-type.test.ts +31 -0
  53. package/src/test/api/badge-type.test.ts +5 -5
  54. package/src/test/api/bundle-data.fixture.ts +6 -0
  55. package/src/test/api/bundle-header-data.fixture.ts +6 -0
  56. package/src/test/api/contact-data.fixture.ts +7 -0
  57. package/src/test/api/enhancement-category.test.ts +5 -5
  58. package/src/test/api/mission-data.fixture.ts +12 -0
  59. package/src/test/api/sex.test.ts +33 -1
  60. package/src/test/api/zone-data.fixture.ts +8 -0
  61. package/src/test/db/abstract-index.test.ts +55 -0
  62. package/src/test/db/alignment-list.test.ts +200 -0
  63. package/src/test/db/alternates.test.ts +82 -117
  64. package/src/test/db/badge-index.test.ts +519 -0
  65. package/src/test/db/badge-requirement.test.ts +145 -0
  66. package/src/test/db/badge.test.ts +310 -14
  67. package/src/test/db/bundle-header.test.ts +76 -0
  68. package/src/test/db/coh-content-database.test.ts +264 -24
  69. package/src/test/db/contact.test.ts +97 -0
  70. package/src/test/db/location.test.ts +51 -0
  71. package/src/test/db/mission.test.ts +171 -0
  72. package/src/test/db/morality-list.test.ts +457 -0
  73. package/src/test/db/zone.test.ts +36 -0
  74. package/src/test/integration.test.ts +16 -0
  75. package/src/test/util.test.ts +144 -18
  76. package/src/main/api/badge-partial-data.ts +0 -65
  77. package/src/main/api/badge-partial-type.ts +0 -8
  78. package/src/main/api/change.ts +0 -14
  79. package/src/main/api/game-map-data.ts +0 -26
  80. package/src/main/api/plaque-type.ts +0 -6
  81. package/src/main/api/server-group-data.ts +0 -65
  82. package/src/main/api/vidiot-map-data.ts +0 -18
  83. package/src/main/api/vidiot-map-point-of-interest-data.ts +0 -30
  84. package/src/main/changelog.ts +0 -20
  85. package/src/main/db/badge-partial.ts +0 -35
  86. package/src/main/db/game-map.ts +0 -33
  87. package/src/main/db/server-group.ts +0 -112
  88. package/src/main/db/vidiot-map-point-of-interest.ts +0 -40
  89. package/src/main/db/vidiot-map.ts +0 -25
  90. package/src/test/api/badge-partial-data.fixture.ts +0 -17
  91. package/src/test/api/badge-partial-type.test.ts +0 -31
  92. package/src/test/api/game-map-data.fixture.ts +0 -10
  93. package/src/test/api/plaque-type.test.ts +0 -31
  94. package/src/test/api/server-group-data.fixture.ts +0 -23
  95. package/src/test/api/server-group-data.test.ts +0 -15
  96. package/src/test/api/vidiot-map-point-of-interest.fixture.ts +0 -10
  97. package/src/test/api/vidiot-map.fixture.ts +0 -9
  98. package/src/test/changelog.test.ts +0 -36
  99. package/src/test/db/server-group.test.ts +0 -124
  100. package/src/test/index.test.ts +0 -10
@@ -0,0 +1,51 @@
1
+ import { BadgeType } from '../api/badge-type'
2
+ import { MoralityExtended } from '../api/morality'
3
+
4
+ export interface BadgeSearchOptions {
5
+
6
+ /**
7
+ * Text-based search.
8
+ *
9
+ * Case-insensitive. Defaults to searching on name only.
10
+ */
11
+ query?: {
12
+ str?: string
13
+ on?: {
14
+ name?: boolean
15
+ badgeText?: boolean
16
+ acquisition?: boolean
17
+ notes?: boolean
18
+ effect?: boolean
19
+ setTitle?: boolean
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Filter results matching the given values.
25
+ */
26
+ filter?: {
27
+ type?: BadgeType
28
+ zoneKey?: string
29
+ morality?: MoralityExtended
30
+ }
31
+
32
+ /**
33
+ * Sort results.
34
+ *
35
+ * Badges are assumed to be in canonical order in the content bundle, and should match the in-game display order.
36
+ */
37
+ sort?: {
38
+ by?: 'canonical' | 'badge-name' | 'zone-key'
39
+ dir?: 'asc' | 'desc'
40
+ }
41
+
42
+ /**
43
+ * The page (1-based)
44
+ */
45
+ page?: number
46
+
47
+ /**
48
+ * How many results per page
49
+ */
50
+ pageSize?: number
51
+ }
@@ -1,13 +1,16 @@
1
1
  import { BadgeType } from '../api/badge-type'
2
- import { Alignment } from '../api/alignment'
3
2
  import { Link } from '../api/link'
4
3
  import { BadgeData } from '../api/badge-data'
5
- import { BadgePartial } from './badge-partial'
4
+ import { BadgeRequirement } from './badge-requirement'
6
5
  import { Key } from './key'
7
6
  import { Alternates } from './alternates'
7
+ import { MarkdownString } from '../api/markdown-string'
8
+ import { coalesceToArray } from '../util'
9
+ import { MoralityList } from './morality-list'
8
10
 
9
11
  export class Badge {
10
- readonly #partialsIndex: Record<string, BadgePartial> = {}
12
+ readonly #requirementsIndex: Record<string, BadgeRequirement> = {}
13
+ readonly #zoneKeys = new Set<string>()
11
14
 
12
15
  /**
13
16
  * The database key for this badge.
@@ -17,7 +20,7 @@ export class Badge {
17
20
  /**
18
21
  * The type of badge.
19
22
  */
20
- readonly type: BadgeType | string
23
+ readonly type: BadgeType
21
24
 
22
25
  /**
23
26
  * The name of this badge.
@@ -27,21 +30,19 @@ export class Badge {
27
30
  readonly name: Alternates<string>
28
31
 
29
32
  /**
30
- * The character alignments that this badge is available to.
33
+ * The character moralities that this badge is available to.
31
34
  */
32
- readonly alignment: Alignment[]
35
+ readonly morality: MoralityList
33
36
 
34
37
  /**
35
38
  * The badge text as it appears in-game. May vary by character sex or alignment.
36
39
  */
37
- readonly badgeText: Alternates<string>
40
+ readonly badgeText: Alternates<MarkdownString>
38
41
 
39
42
  /**
40
- * Description of how to acquire the badge.
41
- *
42
- * Supports {@link https://www.markdownguide.org/|Markdown} format.
43
+ * Short description of how to acquire the badge. Detailed instructions will be in the notes field.
43
44
  */
44
- readonly acquisition?: string
45
+ readonly acquisition?: MarkdownString
45
46
 
46
47
  /**
47
48
  * Absolute URL to this badge's icon.
@@ -52,90 +53,95 @@ export class Badge {
52
53
 
53
54
  /**
54
55
  * Freeform notes or tips about the badge.
55
- *
56
- * Supports {@link https://www.markdownguide.org/|Markdown} format.
57
56
  */
58
- readonly notes?: string
57
+ readonly notes?: MarkdownString
59
58
 
60
59
  /**
61
- * List of external links for this Badge. Wiki, forums, etc.
60
+ * List of external links. Wiki, forums, etc.
62
61
  */
63
- readonly links?: Link[]
62
+ readonly links: Link[]
64
63
 
65
64
  /**
66
- * For exploration badges, the key of the {@link GameMap} 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.
67
67
  */
68
- readonly mapKey?: string
68
+ readonly setTitleId?: [number, number?]
69
69
 
70
70
  /**
71
- * For exploration badges, the `/loc` coordinates of the badge on the in-game map.
71
+ * A description of the effect the badge will have, such as a buff or granting a temporary power.
72
72
  */
73
- readonly loc?: [number, number, number]
73
+ readonly effect?: MarkdownString
74
74
 
75
75
  /**
76
- * For badges that appear on a Vidiot Map, the number or letter the badge appears as.
76
+ * Represents the requirements for badges with multiple fulfillment steps, such as visiting monuments for history badges, completing missions, or collecting other badges.
77
77
  */
78
- readonly vidiotMapKey?: string
78
+ readonly requirements?: BadgeRequirement[]
79
79
 
80
80
  /**
81
- * ID used with the in-game `/settitle` command to apply the badge.
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.
82
82
  */
83
- readonly setTitle?: {
84
- /**
85
- * `/settitle` id.
86
- */
87
- id?: number
88
- /**
89
- * `/settitle` id if different for praetorian characters.
90
- */
91
- praetorianId?: number
83
+ readonly ignoreInTotals: boolean
84
+
85
+ constructor(badgeData: BadgeData) {
86
+ this.key = new Key(badgeData.key).value
87
+ this.type = badgeData.type
88
+ this.name = new Alternates(badgeData.name)
89
+ this.morality = new MoralityList(coalesceToArray(badgeData.morality))
90
+ this.badgeText = new Alternates(badgeData.badgeText ?? [])
91
+ this.acquisition = badgeData.acquisition
92
+ this.icon = new Alternates(badgeData.icon ?? [])
93
+ this.notes = badgeData.notes
94
+ this.links = badgeData.links ?? []
95
+ this.effect = badgeData.effect
96
+ this.setTitleId = badgeData.setTitleId
97
+ this.ignoreInTotals = badgeData.ignoreInTotals ?? false
98
+
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
107
+ })
92
108
  }
93
109
 
94
- /**
95
- * A description of the effect the badge will have, such as a buff or granting a temporary power.
96
- *
97
- * Supports {@link https://www.markdownguide.org/|Markdown} format.
98
- */
99
- readonly effect?: string
110
+ getRequirement(key: string): BadgeRequirement {
111
+ const result = this.#requirementsIndex[key]
112
+ if (result === undefined) throw new Error(`Unknown badge requirement key [${key}]`)
113
+ return result
114
+ }
100
115
 
101
116
  /**
102
- * A list of requirements for badges that have partial fulfilment steps, such as visiting plaques for history badges, or collecting other badges for meta-badges like accolades.
117
+ * Return a list of all the zone keys referenced by this badge.
103
118
  */
104
- readonly partials?: BadgePartial[]
119
+ get zoneKeys(): string[] {
120
+ return [...this.#zoneKeys]
121
+ }
105
122
 
106
123
  /**
107
- * 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.
124
+ * The zone key if this badge relates to a single zone.
108
125
  */
109
- readonly ignoreInTotals: boolean
110
-
111
- constructor(data: BadgeData) {
112
- this.key = new Key(data.key).value
113
- this.type = data.type
114
- this.name = new Alternates(data.name)
115
- this.alignment = data.alignment
116
- this.badgeText = new Alternates(data.badgeText ?? [])
117
- this.acquisition = data.acquisition
118
- this.icon = new Alternates(data.icon ?? [])
119
- this.notes = data.notes
120
- this.links = data.links
121
- this.mapKey = data.mapKey
122
- this.loc = data.loc
123
- this.effect = data.effect
124
- this.vidiotMapKey = data.vidiotMapKey
125
- this.setTitle = data.setTitle
126
- this.ignoreInTotals = data.ignoreInTotals ?? false
127
-
128
- this.partials = data.partials?.map((data) => {
129
- if (this.#partialsIndex[data.key] !== undefined) throw new Error(`Duplicate badge partial key [${data.key}]`)
130
- const badge = new BadgePartial(data)
131
- this.#partialsIndex[badge.key] = badge
132
- return badge
133
- })
126
+ get zoneKey(): string | undefined {
127
+ return this.#zoneKeys.size === 1 ? this.#zoneKeys.values().next().value : undefined
134
128
  }
129
+ }
135
130
 
136
- getPartial(key: string): BadgePartial {
137
- const result = this.#partialsIndex[key]
138
- if (result === undefined) throw new Error(`Unknown badge partial key [${key}]`)
139
- return result
140
- }
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)
141
147
  }
@@ -0,0 +1,44 @@
1
+ import { Link } from '../api/link'
2
+ import { MarkdownString } from '../api/markdown-string'
3
+ import { BundleHeaderData } from '../api/bundle-header-data'
4
+
5
+ export class BundleHeader {
6
+ /**
7
+ * Name of the content bundle.
8
+ */
9
+ readonly name?: string
10
+
11
+ /**
12
+ * Description of the fork.
13
+ */
14
+ readonly description?: MarkdownString
15
+
16
+ /**
17
+ * Url for the repository where the bundle is maintained.
18
+ */
19
+ readonly repositoryUrl?: string
20
+
21
+ /**
22
+ * Url for the location of the changelog.
23
+ */
24
+ readonly changelogUrl?: string
25
+
26
+ /**
27
+ * List of external links. Wiki, forums, etc.
28
+ */
29
+ readonly links?: Link[]
30
+
31
+ /**
32
+ * The current version of the data package.
33
+ */
34
+ readonly version?: string
35
+
36
+ constructor(data: BundleHeaderData | undefined) {
37
+ this.name = data?.name
38
+ this.description = data?.description
39
+ this.repositoryUrl = data?.repositoryUrl
40
+ this.changelogUrl = data?.changelogUrl
41
+ this.links = data?.links ?? []
42
+ this.version = data?.version
43
+ }
44
+ }
@@ -1,29 +1,138 @@
1
- import { ServerGroup } from './server-group'
2
- import { ServerGroupData } from '../api/server-group-data'
1
+ import { BundleData } from '../api/bundle-data'
2
+ import { Archetype } from './archetype'
3
+ import { Zone } from './zone'
4
+ import { Badge } from './badge'
5
+ import { BundleHeader } from './bundle-header'
6
+ import { BadgeSearchOptions } from './badge-search-options'
7
+ import { Paged } from './paged'
8
+ import { Contact } from './contact'
9
+ import { Mission } from './mission'
10
+ import { AbstractIndex } from './abstract-index'
11
+ import { BadgeIndex } from './badge-index'
3
12
 
4
13
  export class CohContentDatabase {
5
- readonly #serverGroups: Record<string, ServerGroup> = {}
14
+ readonly #archetypeIndex
15
+ readonly #zoneIndex
16
+ readonly #contactIndex
17
+ readonly #missionIndex
18
+ readonly #badgeIndex
19
+
20
+ readonly #header: BundleHeader
21
+ readonly #servers: string[]
22
+
23
+ /**
24
+ * Create a db instance from the given content bundle.
25
+ * @param bundle The bundle to load.
26
+ */
27
+ constructor(bundle: BundleData) {
28
+ this.#header = new BundleHeader(bundle.header)
29
+ this.#servers = bundle.servers ?? []
30
+
31
+ this.#archetypeIndex = new AbstractIndex<Archetype>('key', bundle.archetypes?.map(x => new Archetype(x)))
32
+ this.#zoneIndex = new AbstractIndex<Zone>('key', bundle.zones?.map(x => new Zone(x)))
33
+ this.#contactIndex = new AbstractIndex<Contact>('key', bundle.contacts?.map(x => new Contact(x)))
34
+ this.#missionIndex = new AbstractIndex<Mission>('key', bundle.missions?.map(x => new Mission(x)))
35
+ this.#badgeIndex = new BadgeIndex(bundle.badges?.map(x => new Badge(x)))
36
+ }
37
+
38
+ /**
39
+ * Header information about the content bundle.
40
+ */
41
+ get header(): BundleHeader {
42
+ return this.#header
43
+ }
44
+
45
+ /**
46
+ * List of the game server names.
47
+ *
48
+ * Torchbearer, Excelsior, etc.
49
+ */
50
+ get servers(): string[] {
51
+ return this.#servers
52
+ }
53
+
54
+ /**
55
+ * List of archetypes.
56
+ */
57
+ get archetypes(): Archetype[] {
58
+ return this.#archetypeIndex.values
59
+ }
60
+
61
+ /**
62
+ * Get archetype by key.
63
+ * @param key The key.
64
+ */
65
+ getArchetype(key: string | undefined): Archetype | undefined {
66
+ return this.#archetypeIndex.get(key)
67
+ }
68
+
69
+ /**
70
+ * List of game zones.
71
+ */
72
+ get zones(): Zone[] {
73
+ return this.#zoneIndex.values
74
+ }
75
+
76
+ /**
77
+ * Get zone by key.
78
+ * @param key The key.
79
+ */
80
+ getZone(key: string | undefined): Zone | undefined {
81
+ return this.#zoneIndex.get(key)
82
+ }
83
+
84
+ /**
85
+ * List of contacts.
86
+ */
87
+ get contacts(): Contact[] {
88
+ return this.#contactIndex.values
89
+ }
90
+
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)
97
+ }
98
+
99
+ /**
100
+ * List of missions.
101
+ */
102
+ get missions(): Mission[] {
103
+ return this.#missionIndex.values
104
+ }
105
+
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)
112
+ }
6
113
 
7
114
  /**
8
- * Load a server group data package into the database.
9
- * @param data The data to load.
115
+ * List of badges.
10
116
  */
11
- loadServerGroupData(data: ServerGroupData): void {
12
- this.#serverGroups[data.key] = new ServerGroup(data)
117
+ get badges(): Badge[] {
118
+ return this.#badgeIndex.values
13
119
  }
14
120
 
15
121
  /**
16
- * Get all the server groups currently loaded in the database.
122
+ * Get badge by key.
123
+ * @param key The key.
17
124
  */
18
- listServerGroups(): ServerGroup[] {
19
- return Object.values(this.#serverGroups)
125
+ getBadge(key: string | undefined): Badge | undefined {
126
+ return this.#badgeIndex.get(key)
20
127
  }
21
128
 
22
129
  /**
23
- * get a server group by key.
24
- * @param serverGroupKey The key.
130
+ * Search, sort and filter the badge list.
131
+ * This is a fairly brute-forced approach and will not be as performant as loading the badge data into a traditional
132
+ * database engine, but is sufficient for most operations.
133
+ * @param options {@link BadgeSearchOptions}
25
134
  */
26
- getServerGroup(serverGroupKey: string): ServerGroup | null {
27
- return this.#serverGroups[serverGroupKey]
135
+ searchBadges(options?: BadgeSearchOptions): Paged<Badge> {
136
+ return this.#badgeIndex.search(options)
28
137
  }
29
138
  }
@@ -0,0 +1,62 @@
1
+ import { Link } from '../api/link'
2
+ import { Key } from './key'
3
+ import { MarkdownString } from '../api/markdown-string'
4
+ import { ContactData } from '../api/contact-data'
5
+ import { Location } from './location'
6
+ import { MoralityList } from './morality-list'
7
+ import { coalesceToArray } from '../util'
8
+
9
+ export class Contact {
10
+ /**
11
+ * Unique key used to reference this contact.
12
+ *
13
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
14
+ */
15
+ readonly key: string
16
+
17
+ /**
18
+ * The name of this contact.
19
+ */
20
+ readonly name: string
21
+
22
+ /**
23
+ * The contact's title.
24
+ */
25
+ readonly title?: string
26
+
27
+ /**
28
+ * The character moralities that this contact will interact with.
29
+ */
30
+ readonly morality?: MoralityList
31
+
32
+ /**
33
+ * The location of this contact.
34
+ */
35
+ readonly location?: Location
36
+
37
+ /**
38
+ * The level range this contact will offer missions for.
39
+ */
40
+ readonly levelRange?: [number, number?]
41
+
42
+ /**
43
+ * Freeform notes or tips about the contact.
44
+ */
45
+ readonly notes?: MarkdownString
46
+
47
+ /**
48
+ * List of external links. Wiki, forums, etc.
49
+ */
50
+ readonly links: Link[]
51
+
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 ?? []
61
+ }
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
+ }
@@ -0,0 +1,107 @@
1
+ import { MissionType } from '../api/mission-type'
2
+ import { MarkdownString } from '../api/markdown-string'
3
+ import { Link } from '../api/link'
4
+ import { MissionData } from '../api/mission-data'
5
+ import { Key } from './key'
6
+ import { coalesceToArray } from '../util'
7
+ import { MoralityList } from './morality-list'
8
+
9
+ export class Mission {
10
+ /**
11
+ * Unique key used to reference this mission.
12
+ *
13
+ * Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
14
+ */
15
+ readonly key: string
16
+
17
+ /**
18
+ * The name of the mission as it appears from the contact.
19
+ *
20
+ * The name may be different when viewed in Ouroboros as a Flashback.
21
+ */
22
+ readonly name: string
23
+
24
+ /**
25
+ * The type of mission... Story arc, task force, trial, etc.
26
+ */
27
+ readonly type: MissionType
28
+
29
+ /**
30
+ * The character moralities that may accept the mission.
31
+ */
32
+ readonly morality: MoralityList
33
+
34
+ /**
35
+ * The keys of any contacts that provide this mission.
36
+ */
37
+ readonly contactKeys?: string[]
38
+
39
+ /**
40
+ * The level range this mission is available for.
41
+ */
42
+ readonly levelRange?: [number, number?]
43
+
44
+ /**
45
+ * Freeform notes or tips about the mission.
46
+ */
47
+ readonly notes?: MarkdownString
48
+
49
+ /**
50
+ * List of external links. Wiki, forums, etc.
51
+ */
52
+ readonly links: Link[]
53
+
54
+ /**
55
+ * If the mission is available in Ouroboros as a Flashback.
56
+ */
57
+ readonly flashback?: {
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?: MoralityList
78
+
79
+ /**
80
+ * Freeform notes or tips about the Flashback version of the mission.
81
+ */
82
+ readonly notes?: MarkdownString
83
+ }
84
+
85
+ constructor(data: MissionData) {
86
+ this.key = new Key(data.key).value
87
+ this.name = data.name
88
+ this.type = data.type
89
+ this.morality = new MoralityList(coalesceToArray(data.morality))
90
+ this.contactKeys = coalesceToArray(data.contactKeys)
91
+ this.levelRange = data.levelRange
92
+ this.notes = data.notes
93
+ this.links = data.links ?? []
94
+ this.flashback = createFlashback(data)
95
+ }
96
+ }
97
+
98
+ function createFlashback(data: MissionData): Mission['flashback'] {
99
+ if (!data.flashback) return undefined
100
+ return {
101
+ id: data.flashback.id,
102
+ levelRange: data.flashback.levelRange ?? data.levelRange,
103
+ name: data.flashback.name ?? data.name,
104
+ morality: new MoralityList(coalesceToArray(data.flashback.morality ?? data.morality)),
105
+ notes: data.flashback.notes,
106
+ }
107
+ }