coh-content-db 2.0.0-rc.2 → 2.0.0-rc.21
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.
- package/.editorconfig +10 -11
- package/.github/workflows/build.yml +4 -2
- package/.github/workflows/pull-request.yml +1 -1
- package/.github/workflows/release.yml +1 -1
- package/CHANGELOG.md +47 -0
- package/README.md +77 -32
- package/dist/coh-content-db.d.ts +755 -290
- package/dist/coh-content-db.js +1028 -358
- package/dist/coh-content-db.js.map +1 -1
- package/dist/coh-content-db.mjs +998 -349
- package/dist/coh-content-db.mjs.map +1 -1
- package/eslint.config.mjs +1 -0
- package/jest.config.mjs +1 -0
- package/package.json +14 -14
- package/src/main/api/alignment.ts +18 -2
- package/src/main/api/badge-data.ts +29 -51
- package/src/main/api/badge-requirement-data.ts +64 -0
- package/src/main/api/badge-requirement-type.ts +32 -0
- package/src/main/api/badge-type.ts +15 -15
- package/src/main/api/bundle-data.ts +47 -0
- package/src/main/api/bundle-header-data.ts +44 -0
- package/src/main/api/contact-data.ts +49 -0
- package/src/main/api/enhancement-category.ts +26 -26
- package/src/main/api/level-range-data.ts +4 -0
- package/src/main/api/location-data.ts +28 -0
- package/src/main/api/markdown-string.ts +4 -0
- package/src/main/api/mission-data.ts +57 -0
- package/src/main/api/mission-flashback-data.ts +31 -0
- package/src/main/api/mission-type.ts +2 -0
- package/src/main/api/morality.ts +49 -0
- package/src/main/api/set-title-data.ts +4 -0
- package/src/main/api/sex.ts +8 -1
- package/src/main/api/variant-context.ts +11 -0
- package/src/main/api/variant-data.ts +22 -0
- package/src/main/api/zone-data.ts +44 -0
- package/src/main/api/zone-type.ts +59 -0
- package/src/main/db/abstract-index.ts +37 -0
- package/src/main/db/alignment-list.ts +54 -0
- package/src/main/db/badge-index.ts +83 -0
- package/src/main/db/badge-requirement.ts +81 -0
- package/src/main/db/badge-search-options.ts +52 -0
- package/src/main/db/badge.ts +97 -74
- package/src/main/db/bundle-header.ts +52 -0
- package/src/main/db/coh-content-database.ts +123 -14
- package/src/main/db/contact.ts +63 -0
- package/src/main/db/level-range.ts +15 -0
- package/src/main/db/location.ts +30 -0
- package/src/main/db/mission.ts +108 -0
- package/src/main/db/morality-list.ts +99 -0
- package/src/main/db/paged.ts +11 -0
- package/src/main/db/set-title-ids.ts +10 -0
- package/src/main/db/variants.ts +84 -0
- package/src/main/db/zone.ts +57 -0
- package/src/main/index.ts +33 -18
- package/src/main/util/coalesce-to-array.ts +13 -0
- package/src/main/util/links.ts +104 -0
- package/src/main/util/to-date.ts +9 -0
- package/src/test/api/alignment.test.ts +38 -4
- package/src/test/api/badge-data.fixture.ts +2 -15
- package/src/test/api/badge-data.test.ts +5 -4
- package/src/test/api/badge-requirement-data.fixture.ts +7 -0
- package/src/test/api/badge-requirement-type.test.ts +31 -0
- package/src/test/api/badge-type.test.ts +5 -5
- package/src/test/api/bundle-data.fixture.ts +7 -0
- package/src/test/api/bundle-header-data.fixture.ts +8 -0
- package/src/test/api/contact-data.fixture.ts +7 -0
- package/src/test/api/enhancement-category.test.ts +5 -5
- package/src/test/api/mission-data.fixture.ts +12 -0
- package/src/test/api/morality.test.ts +31 -0
- package/src/test/api/sex.test.ts +33 -1
- package/src/test/api/zone-data.fixture.ts +9 -0
- package/src/test/db/abstract-index.test.ts +55 -0
- package/src/test/db/alignment-list.test.ts +200 -0
- package/src/test/db/badge-index.test.ts +653 -0
- package/src/test/db/badge-requirement.test.ts +145 -0
- package/src/test/db/badge.test.ts +416 -14
- package/src/test/db/bundle-header.test.ts +89 -0
- package/src/test/db/coh-content-database.test.ts +265 -24
- package/src/test/db/contact.test.ts +98 -0
- package/src/test/db/level-range.test.ts +47 -0
- package/src/test/db/location.test.ts +51 -0
- package/src/test/db/mission.test.ts +173 -0
- package/src/test/db/morality-list.test.ts +457 -0
- package/src/test/db/set-title-ids.test.ts +19 -0
- package/src/test/db/variants.test.ts +188 -0
- package/src/test/db/zone.test.ts +81 -0
- package/src/test/integration.test.ts +16 -0
- package/src/test/util/coalese-to-array.test.ts +17 -0
- package/src/test/util/links.test.ts +149 -0
- package/src/test/util/to-date.test.ts +15 -0
- package/src/main/api/alternate-data.ts +0 -22
- package/src/main/api/badge-partial-data.ts +0 -65
- package/src/main/api/badge-partial-type.ts +0 -8
- package/src/main/api/change.ts +0 -14
- package/src/main/api/game-map-data.ts +0 -26
- package/src/main/api/plaque-type.ts +0 -6
- package/src/main/api/server-group-data.ts +0 -65
- package/src/main/api/vidiot-map-data.ts +0 -18
- package/src/main/api/vidiot-map-point-of-interest-data.ts +0 -30
- package/src/main/changelog.ts +0 -20
- package/src/main/db/alternates.ts +0 -81
- package/src/main/db/badge-partial.ts +0 -35
- package/src/main/db/game-map.ts +0 -33
- package/src/main/db/server-group.ts +0 -112
- package/src/main/db/vidiot-map-point-of-interest.ts +0 -40
- package/src/main/db/vidiot-map.ts +0 -25
- package/src/main/util.ts +0 -17
- package/src/test/api/badge-partial-data.fixture.ts +0 -17
- package/src/test/api/badge-partial-type.test.ts +0 -31
- package/src/test/api/game-map-data.fixture.ts +0 -10
- package/src/test/api/plaque-type.test.ts +0 -31
- package/src/test/api/server-group-data.fixture.ts +0 -23
- package/src/test/api/server-group-data.test.ts +0 -15
- package/src/test/api/vidiot-map-point-of-interest.fixture.ts +0 -10
- package/src/test/api/vidiot-map.fixture.ts +0 -9
- package/src/test/changelog.test.ts +0 -36
- package/src/test/db/alternates.test.ts +0 -223
- package/src/test/db/server-group.test.ts +0 -124
- package/src/test/index.test.ts +0 -10
- package/src/test/util.test.ts +0 -39
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Badge, compareByName, compareByReleaseDate, compareByZoneKey } from './badge'
|
|
2
|
+
import { BadgeSearchOptions } from './badge-search-options'
|
|
3
|
+
import { Paged } from './paged'
|
|
4
|
+
import { AbstractIndex } from './abstract-index'
|
|
5
|
+
|
|
6
|
+
export class BadgeIndex extends AbstractIndex<Badge> {
|
|
7
|
+
constructor(values: Badge[] | undefined) {
|
|
8
|
+
super('key', values)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
search(options?: BadgeSearchOptions): Paged<Badge> {
|
|
12
|
+
const matched = (options?.query || options?.filter)
|
|
13
|
+
? this._values.filter(badge => this.#satisfiesQueryPredicate(badge, options?.query) && this.#satisfiesFilterPredicate(badge, options?.filter))
|
|
14
|
+
: this._values
|
|
15
|
+
|
|
16
|
+
const sorted = this.#sort(matched, options)
|
|
17
|
+
|
|
18
|
+
const totalPages = options?.pageSize ? Math.ceil(matched.length / (options?.pageSize)) : 1
|
|
19
|
+
const pageNumber = Math.max(1, Math.min(totalPages, options?.page ?? 1))
|
|
20
|
+
const items = options?.pageSize ? sorted.slice((pageNumber - 1) * options.pageSize, pageNumber * options?.pageSize) : sorted
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
items: items,
|
|
24
|
+
pageIndex: pageNumber - 1,
|
|
25
|
+
pageNumber: pageNumber,
|
|
26
|
+
pageSize: options?.pageSize,
|
|
27
|
+
matchedItemCount: matched.length,
|
|
28
|
+
totalItemCount: this._values.length,
|
|
29
|
+
totalPageCount: totalPages,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#satisfiesQueryPredicate(badge: Badge, query?: BadgeSearchOptions['query']): boolean {
|
|
34
|
+
const queryString = query?.str?.toLowerCase() ?? ''
|
|
35
|
+
const fields = query?.fields ? new Set(query?.fields) : new Set(['name']) // Default to name if not provided
|
|
36
|
+
if (fields.size === 0) return true
|
|
37
|
+
|
|
38
|
+
return !!((fields.has('name') && badge.name.canonical.some(x => x.value.toLowerCase().includes(queryString)))
|
|
39
|
+
|| (fields.has('badge-text') && badge.badgeText.canonical.some(x => x.value.toLowerCase().includes(queryString)))
|
|
40
|
+
|| (fields.has('acquisition') && badge.acquisition?.toLowerCase().includes(queryString))
|
|
41
|
+
|| (fields.has('effect') && badge.effect?.toLowerCase().includes(queryString))
|
|
42
|
+
|| (fields.has('notes') && badge.notes?.toLowerCase().includes(queryString))
|
|
43
|
+
|| (fields.has('set-title-id') && (badge.setTitleId?.primal.toString() === queryString))
|
|
44
|
+
|| (fields.has('set-title-id') && (badge.setTitleId?.praetorian?.toString() === queryString))
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#satisfiesFilterPredicate(badge: Badge, filter?: BadgeSearchOptions['filter']): boolean {
|
|
49
|
+
return (!filter?.type || badge.type === filter.type)
|
|
50
|
+
&& (!filter?.zoneKey || badge.zoneKey === filter.zoneKey)
|
|
51
|
+
&& (!filter?.morality || badge.morality.has(filter.morality))
|
|
52
|
+
&& (!filter?.predicate || filter.predicate(badge))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#sort(badges: Badge[], options?: BadgeSearchOptions): Badge[] {
|
|
56
|
+
switch (options?.sort) {
|
|
57
|
+
case 'name.asc': {
|
|
58
|
+
return badges.toSorted((a, b) => compareByName(a, b, options.variantContext))
|
|
59
|
+
}
|
|
60
|
+
case 'name.desc': {
|
|
61
|
+
return badges.toSorted((a, b) => compareByName(b, a, options.variantContext))
|
|
62
|
+
}
|
|
63
|
+
case 'zone-key.asc': {
|
|
64
|
+
return badges.toSorted(compareByZoneKey)
|
|
65
|
+
}
|
|
66
|
+
case 'zone-key.desc': {
|
|
67
|
+
return badges.toSorted((a, b) => compareByZoneKey(b, a))
|
|
68
|
+
}
|
|
69
|
+
case 'release-date.asc': {
|
|
70
|
+
return badges.toSorted(compareByReleaseDate)
|
|
71
|
+
}
|
|
72
|
+
case 'release-date.desc': {
|
|
73
|
+
return badges.toSorted((a, b) => compareByReleaseDate(b, a))
|
|
74
|
+
}
|
|
75
|
+
case 'canonical.desc': {
|
|
76
|
+
return badges.toReversed()
|
|
77
|
+
}
|
|
78
|
+
default: {
|
|
79
|
+
return [...badges]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { BadgeRequirementData } from '../api/badge-requirement-data'
|
|
2
|
+
import { BadgeRequirementType } from '../api/badge-requirement-type'
|
|
3
|
+
import { EnhancementCategory } from '../api/enhancement-category'
|
|
4
|
+
import { Key } from './key'
|
|
5
|
+
import { MarkdownString } from '../api/markdown-string'
|
|
6
|
+
import { Link } from '../api/link'
|
|
7
|
+
import { Location } from './location'
|
|
8
|
+
import { coalesceToArray } from '../util/coalesce-to-array'
|
|
9
|
+
|
|
10
|
+
export class BadgeRequirement {
|
|
11
|
+
/**
|
|
12
|
+
* Unique key used to reference this badge requirement.
|
|
13
|
+
*
|
|
14
|
+
* Keys must be unique and can only contain lowercase letters, numbers and hyphens (`-`).
|
|
15
|
+
*/
|
|
16
|
+
readonly key: string
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The requirement type.
|
|
20
|
+
*/
|
|
21
|
+
readonly type: BadgeRequirementType
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* If the requirement involves a location, where it is.
|
|
25
|
+
*/
|
|
26
|
+
readonly location?: Location[]
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* If the requirement involves a badge, the badge key.
|
|
30
|
+
*/
|
|
31
|
+
readonly badgeKey?: string
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* If the requirement involves a mission, the mission key.
|
|
35
|
+
*/
|
|
36
|
+
readonly missionKey?: string
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* If the requirement involves a monument, the text that is displayed thereon.
|
|
40
|
+
*/
|
|
41
|
+
readonly monumentText?: string
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* If the requirement involves crafting an invention, the Level of the invention required.
|
|
45
|
+
*/
|
|
46
|
+
readonly inventionLevel?: number
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* If the requirement involves crafting an invention, the types of enhancements that will qualify.
|
|
50
|
+
*/
|
|
51
|
+
readonly inventionTypes?: EnhancementCategory[]
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Number of times the task needs to be repeated.
|
|
55
|
+
*/
|
|
56
|
+
readonly count?: number
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Additional information about the requirement.
|
|
60
|
+
*/
|
|
61
|
+
readonly notes?: MarkdownString
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* List of external links. Wiki, forums, etc.
|
|
65
|
+
*/
|
|
66
|
+
readonly links: Link[]
|
|
67
|
+
|
|
68
|
+
constructor(data: BadgeRequirementData) {
|
|
69
|
+
this.key = new Key(data.key).value
|
|
70
|
+
this.type = data.type
|
|
71
|
+
this.location = coalesceToArray(data.location)
|
|
72
|
+
this.badgeKey = data.badgeKey
|
|
73
|
+
this.missionKey = data.missionKey
|
|
74
|
+
this.monumentText = data.monumentText
|
|
75
|
+
this.inventionLevel = data.inventionLevel
|
|
76
|
+
this.inventionTypes = data.inventionTypes
|
|
77
|
+
this.count = data.count
|
|
78
|
+
this.notes = data.notes
|
|
79
|
+
this.links = data.links ?? []
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BadgeType } from '../api/badge-type'
|
|
2
|
+
import { Morality } from '../api/morality'
|
|
3
|
+
import { Badge } from './badge'
|
|
4
|
+
import { VariantContext } from '../api/variant-context'
|
|
5
|
+
|
|
6
|
+
export type BadgeQueryableField = 'name' | 'badge-text' | 'acquisition' | 'notes' | 'effect' | 'set-title-id'
|
|
7
|
+
export type BadgeSort = `${'canonical' | 'name' | 'zone-key' | 'release-date'}.${'asc' | 'desc'}`
|
|
8
|
+
|
|
9
|
+
export interface BadgeSearchOptions {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Text-based search.
|
|
13
|
+
*
|
|
14
|
+
* Case-insensitive. Defaults to searching on name only.
|
|
15
|
+
*/
|
|
16
|
+
query?: {
|
|
17
|
+
str?: string
|
|
18
|
+
fields?: BadgeQueryableField[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Filter results matching the given values.
|
|
23
|
+
*/
|
|
24
|
+
filter?: {
|
|
25
|
+
type?: BadgeType
|
|
26
|
+
zoneKey?: string
|
|
27
|
+
morality?: Morality
|
|
28
|
+
predicate?: (badge: Badge) => boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adjust search results based on a given variant context (morality or sex of a character).
|
|
33
|
+
*/
|
|
34
|
+
variantContext?: VariantContext
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sort results.
|
|
38
|
+
*
|
|
39
|
+
* Badges are assumed to be in canonical order in the content bundle, and should match the in-game display order.
|
|
40
|
+
*/
|
|
41
|
+
sort?: BadgeSort
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The page (1-based)
|
|
45
|
+
*/
|
|
46
|
+
page?: number
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* How many results per page
|
|
50
|
+
*/
|
|
51
|
+
pageSize?: number
|
|
52
|
+
}
|
package/src/main/db/badge.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
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 {
|
|
4
|
+
import { BadgeRequirement } from './badge-requirement'
|
|
6
5
|
import { Key } from './key'
|
|
7
|
-
import {
|
|
6
|
+
import { Variants } from './variants'
|
|
7
|
+
import { MarkdownString } from '../api/markdown-string'
|
|
8
|
+
import { MoralityList } from './morality-list'
|
|
9
|
+
import { AbstractIndex } from './abstract-index'
|
|
10
|
+
import { toDate } from '../util/to-date'
|
|
11
|
+
import { coalesceToArray } from '../util/coalesce-to-array'
|
|
12
|
+
import { SetTitleIds } from './set-title-ids'
|
|
13
|
+
import { VariantContext } from '../api/variant-context'
|
|
8
14
|
|
|
9
15
|
export class Badge {
|
|
10
|
-
readonly #
|
|
16
|
+
readonly #requirementsIndex: AbstractIndex<BadgeRequirement>
|
|
17
|
+
readonly #zoneKeys = new Set<string>()
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* The database key for this badge.
|
|
@@ -17,125 +24,141 @@ export class Badge {
|
|
|
17
24
|
/**
|
|
18
25
|
* The type of badge.
|
|
19
26
|
*/
|
|
20
|
-
readonly type: BadgeType
|
|
27
|
+
readonly type: BadgeType
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* The name of this badge.
|
|
24
31
|
*
|
|
25
32
|
* May vary by character sex or alignment.
|
|
26
33
|
*/
|
|
27
|
-
readonly name:
|
|
34
|
+
readonly name: Variants<string>
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
|
-
* The
|
|
37
|
+
* The date that the badge was added to the game.
|
|
31
38
|
*/
|
|
32
|
-
readonly
|
|
39
|
+
readonly releaseDate: Date
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The character moralities that this badge is available to.
|
|
43
|
+
*/
|
|
44
|
+
readonly morality: MoralityList
|
|
33
45
|
|
|
34
46
|
/**
|
|
35
47
|
* The badge text as it appears in-game. May vary by character sex or alignment.
|
|
36
48
|
*/
|
|
37
|
-
readonly badgeText:
|
|
49
|
+
readonly badgeText: Variants<MarkdownString>
|
|
38
50
|
|
|
39
51
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* Supports {@link https://www.markdownguide.org/|Markdown} format.
|
|
52
|
+
* Short description of how to acquire the badge. Detailed instructions will be in the notes field.
|
|
43
53
|
*/
|
|
44
|
-
readonly acquisition?:
|
|
54
|
+
readonly acquisition?: MarkdownString
|
|
45
55
|
|
|
46
56
|
/**
|
|
47
57
|
* Absolute URL to this badge's icon.
|
|
48
58
|
*
|
|
49
59
|
* May vary by character sex or alignment.
|
|
50
60
|
*/
|
|
51
|
-
readonly icon:
|
|
61
|
+
readonly icon: Variants<string>
|
|
52
62
|
|
|
53
63
|
/**
|
|
54
64
|
* Freeform notes or tips about the badge.
|
|
55
|
-
*
|
|
56
|
-
* Supports {@link https://www.markdownguide.org/|Markdown} format.
|
|
57
65
|
*/
|
|
58
|
-
readonly notes?:
|
|
66
|
+
readonly notes?: MarkdownString
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
|
-
* List of external links
|
|
69
|
+
* List of external links. Wiki, forums, etc.
|
|
62
70
|
*/
|
|
63
|
-
readonly links
|
|
71
|
+
readonly links: Link[]
|
|
64
72
|
|
|
65
73
|
/**
|
|
66
|
-
*
|
|
74
|
+
* The id used with the in-game `/settitle` command to apply the badge.
|
|
75
|
+
* The first value is the id for primal characters and the (optional) second number is the id for praetorian characters.
|
|
67
76
|
*/
|
|
68
|
-
readonly
|
|
77
|
+
readonly setTitleId?: SetTitleIds
|
|
69
78
|
|
|
70
79
|
/**
|
|
71
|
-
*
|
|
80
|
+
* A description of the effect the badge will have, such as a buff or granting a temporary power.
|
|
72
81
|
*/
|
|
73
|
-
readonly
|
|
82
|
+
readonly effect?: MarkdownString
|
|
74
83
|
|
|
75
84
|
/**
|
|
76
|
-
*
|
|
85
|
+
* 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.
|
|
77
86
|
*/
|
|
78
|
-
readonly
|
|
87
|
+
readonly ignoreInTotals: boolean
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
constructor(badgeData: BadgeData) {
|
|
90
|
+
this.key = new Key(badgeData.key).value
|
|
91
|
+
this.type = badgeData.type
|
|
92
|
+
this.name = new Variants(badgeData.name)
|
|
93
|
+
this.releaseDate = toDate(badgeData.releaseDate)
|
|
94
|
+
this.morality = new MoralityList(coalesceToArray(badgeData.morality))
|
|
95
|
+
this.badgeText = new Variants(badgeData.badgeText ?? [])
|
|
96
|
+
this.acquisition = badgeData.acquisition
|
|
97
|
+
this.icon = new Variants(badgeData.icon ?? [])
|
|
98
|
+
this.notes = badgeData.notes
|
|
99
|
+
this.links = badgeData.links ?? []
|
|
100
|
+
this.effect = badgeData.effect
|
|
101
|
+
this.setTitleId = badgeData.setTitleId ? new SetTitleIds(badgeData.setTitleId) : undefined
|
|
102
|
+
this.ignoreInTotals = badgeData.ignoreInTotals ?? false
|
|
103
|
+
|
|
104
|
+
this.#requirementsIndex = new AbstractIndex<BadgeRequirement>('key', badgeData.requirements?.map(x => new BadgeRequirement(x)))
|
|
105
|
+
|
|
106
|
+
for (const requirement of this.#requirementsIndex.values) {
|
|
107
|
+
if (requirement.location) for (const location of requirement.location) {
|
|
108
|
+
if (location.zoneKey) this.#zoneKeys.add(location.zoneKey)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* Supports {@link https://www.markdownguide.org/|Markdown} format.
|
|
114
|
+
* Represents the requirements for badges with multiple fulfillment steps, such as visiting monuments for history badges, completing missions, or collecting other badges.
|
|
98
115
|
*/
|
|
99
|
-
|
|
116
|
+
get requirements(): BadgeRequirement[] {
|
|
117
|
+
return this.#requirementsIndex.values
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getRequirement(key: string): BadgeRequirement | undefined {
|
|
121
|
+
return this.#requirementsIndex.get(key)
|
|
122
|
+
}
|
|
100
123
|
|
|
101
124
|
/**
|
|
102
|
-
*
|
|
125
|
+
* Return a list of all the zone keys referenced by this badge.
|
|
103
126
|
*/
|
|
104
|
-
|
|
127
|
+
get zoneKeys(): string[] {
|
|
128
|
+
return [...this.#zoneKeys]
|
|
129
|
+
}
|
|
105
130
|
|
|
106
131
|
/**
|
|
107
|
-
*
|
|
132
|
+
* The zone key if this badge relates to a single zone.
|
|
108
133
|
*/
|
|
109
|
-
|
|
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
|
-
})
|
|
134
|
+
get zoneKey(): string | undefined {
|
|
135
|
+
return this.#zoneKeys.size === 1 ? this.#zoneKeys.values().next().value : undefined
|
|
134
136
|
}
|
|
137
|
+
}
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
export function compareByName(a?: Badge, b?: Badge, context?: VariantContext): number {
|
|
140
|
+
const aName = a?.name?.getValue(context)
|
|
141
|
+
const bName = b?.name?.getValue(context)
|
|
142
|
+
if (!aName && !bName) return 0
|
|
143
|
+
if (!aName) return 1
|
|
144
|
+
if (!bName) return -1
|
|
145
|
+
return aName.localeCompare(bName)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function compareByZoneKey(a?: Badge, b?: Badge): number {
|
|
149
|
+
const aZone = a?.zoneKey
|
|
150
|
+
const bZone = b?.zoneKey
|
|
151
|
+
if (!aZone && !bZone) return 0
|
|
152
|
+
if (!aZone) return 1
|
|
153
|
+
if (!bZone) return -1
|
|
154
|
+
return aZone.localeCompare(bZone)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function compareByReleaseDate(a?: Badge, b?: Badge): number {
|
|
158
|
+
const aReleaseDate = a?.releaseDate?.getTime()
|
|
159
|
+
const bReleaseDate = b?.releaseDate?.getTime()
|
|
160
|
+
if (aReleaseDate === bReleaseDate) return 0
|
|
161
|
+
if (!aReleaseDate) return 1
|
|
162
|
+
if (!bReleaseDate) return -1
|
|
163
|
+
return aReleaseDate < bReleaseDate ? -1 : 1
|
|
141
164
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Link } from '../api/link'
|
|
2
|
+
import { MarkdownString } from '../api/markdown-string'
|
|
3
|
+
import { BundleHeaderData } from '../api/bundle-header-data'
|
|
4
|
+
import { toDate } from '../util/to-date'
|
|
5
|
+
|
|
6
|
+
export class BundleHeader {
|
|
7
|
+
/**
|
|
8
|
+
* Name of the fork this bundle contains data for.
|
|
9
|
+
*/
|
|
10
|
+
readonly name: string
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Version number for this data package.
|
|
14
|
+
*/
|
|
15
|
+
readonly version: string
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The time this bundle was last updated.
|
|
19
|
+
*/
|
|
20
|
+
readonly lastUpdateTime: Date
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Description of the fork.
|
|
24
|
+
*/
|
|
25
|
+
readonly description?: MarkdownString
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Url for the repository where the bundle is maintained.
|
|
29
|
+
*/
|
|
30
|
+
readonly repositoryUrl?: string
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Url for the location of the changelog.
|
|
34
|
+
*/
|
|
35
|
+
readonly changelogUrl?: string
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* List of external links. Wiki, forums, etc.
|
|
39
|
+
*/
|
|
40
|
+
readonly links?: Link[]
|
|
41
|
+
|
|
42
|
+
constructor(data: BundleHeaderData) {
|
|
43
|
+
if (!data) throw new Error('Missing header data')
|
|
44
|
+
this.name = data.name
|
|
45
|
+
this.version = data.version
|
|
46
|
+
this.lastUpdateTime = toDate(data.lastUpdateTime)
|
|
47
|
+
this.description = data?.description
|
|
48
|
+
this.repositoryUrl = data?.repositoryUrl
|
|
49
|
+
this.changelogUrl = data?.changelogUrl
|
|
50
|
+
this.links = data?.links ?? []
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,29 +1,138 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 #
|
|
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
|
-
*
|
|
9
|
-
* @param data The data to load.
|
|
115
|
+
* List of badges.
|
|
10
116
|
*/
|
|
11
|
-
|
|
12
|
-
this.#
|
|
117
|
+
get badges(): Badge[] {
|
|
118
|
+
return this.#badgeIndex.values
|
|
13
119
|
}
|
|
14
120
|
|
|
15
121
|
/**
|
|
16
|
-
* Get
|
|
122
|
+
* Get badge by key.
|
|
123
|
+
* @param key The key.
|
|
17
124
|
*/
|
|
18
|
-
|
|
19
|
-
return
|
|
125
|
+
getBadge(key: string | undefined): Badge | undefined {
|
|
126
|
+
return this.#badgeIndex.get(key)
|
|
20
127
|
}
|
|
21
128
|
|
|
22
129
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
|
|
27
|
-
return this.#
|
|
135
|
+
searchBadges(options?: BadgeSearchOptions): Paged<Badge> {
|
|
136
|
+
return this.#badgeIndex.search(options)
|
|
28
137
|
}
|
|
29
138
|
}
|