Skip to content

Commit fabb9b0

Browse files
committed
Merge branch 'darthmaim-feature/soto'
2 parents 0123bb5 + cdfd590 commit fabb9b0

File tree

11 files changed

+181
-56
lines changed

11 files changed

+181
-56
lines changed

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
"url": "https://github.com/gw2efficiency/chat-codes"
1818
},
1919
"dependencies": {
20-
"base64-js": "^1.1.2"
20+
"base64-js": "^1.5.1"
2121
},
2222
"devDependencies": {
23-
"@types/base64-js": "^1.2.5",
24-
"@types/jest": "^24.9.0",
25-
"jest": "^24.9.0",
26-
"prettier": "^1.19.1",
27-
"ts-jest": "^24.3.0",
28-
"typescript": "^3.7.5"
23+
"@types/base64-js": "^1.3.0",
24+
"@types/jest": "^29.5.4",
25+
"jest": "^29.6.4",
26+
"prettier": "^3.0.3",
27+
"ts-jest": "^29.1.1",
28+
"typescript": "^5.2.2"
2929
}
3030
}

src/ChatCodeStruct.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,25 @@ export class ChatCodeStruct {
2929
this.bytes.push((value >> 0x10) & 0xff)
3030
}
3131

32-
writeTraitSelection([trait1, trait2, trait3]: Array<number>) {
32+
write4Bytes(value: number) {
33+
this.bytes.push((value >> 0x00) & 0xff)
34+
this.bytes.push((value >> 0x08) & 0xff)
35+
this.bytes.push((value >> 0x10) & 0xff)
36+
this.bytes.push((value >> 0x18) & 0xff)
37+
}
38+
39+
writeTraitSelection([trait1, trait2, trait3]: [number, number, number]) {
3340
const value = ((trait3 & 3) << 4) | ((trait2 & 3) << 2) | ((trait1 & 3) << 0)
3441
this.write1Byte(value)
3542
}
3643

44+
writeDynamicArray(values: number[], bytesPerValue: 2 | 4) {
45+
this.write1Byte(values.length)
46+
for (const value of values) {
47+
bytesPerValue === 2 ? this.write2Bytes(value) : this.write4Bytes(value)
48+
}
49+
}
50+
3751
// -- DECODING --
3852

3953
decodeFromChatCode(chatCode: string) {
@@ -60,11 +74,40 @@ export class ChatCodeStruct {
6074
)
6175
}
6276

63-
readTraitSelection(): Array<number> {
77+
read4Bytes() {
78+
return (
79+
this.bytes[this.offset++] |
80+
(this.bytes[this.offset++] << 8) |
81+
(this.bytes[this.offset++] << 16) |
82+
(this.bytes[this.offset++] << 24)
83+
)
84+
}
85+
86+
readTraitSelection(): [number, number, number] {
6487
return [
6588
this.bytes[this.offset] & 3,
6689
(this.bytes[this.offset] >> 2) & 3,
67-
(this.bytes[this.offset++] >> 4) & 3
90+
(this.bytes[this.offset++] >> 4) & 3,
6891
]
6992
}
93+
94+
readDynamicArray(bytesPerValue: 2 | 4): undefined | number[] {
95+
const length = this.read1Byte()
96+
97+
if (length === 0) {
98+
return undefined
99+
}
100+
101+
const values: number[] = []
102+
103+
for (let i = 0; i < length; i++) {
104+
values.push(bytesPerValue === 2 ? this.read2Bytes() : this.read4Bytes())
105+
}
106+
107+
return values
108+
}
109+
110+
atEnd(): boolean {
111+
return this.offset >= this.bytes.length
112+
}
70113
}

src/decode/decode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function decode(chatCode: string) {
1818
// The header describing the type of the chat code is encoded as the first byte
1919
const typeHeader = struct.read1Byte()
2020
const codeType = Object.keys(TYPE_HEADERS).find(
21-
(key) => TYPE_HEADERS[key as CodeType] === typeHeader
21+
(key) => TYPE_HEADERS[key as CodeType] === typeHeader,
2222
)
2323

2424
switch (codeType) {

src/decode/decodeBuildLink.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { CodeType, PROFESSION_FLAGS } from '../static'
1+
import { PROFESSION_FLAGS } from '../static'
22
import { ChatCodeStruct } from '../ChatCodeStruct'
33

44
export function decodeBuildLink(struct: ChatCodeStruct) {
5-
const type = 'build' as CodeType
6-
75
const profession = struct.read1Byte()
86

97
const specialization1 = struct.read1Byte()
@@ -48,8 +46,24 @@ export function decodeBuildLink(struct: ChatCodeStruct) {
4846
aquaticLegend2 = struct.read1Byte()
4947
}
5048

49+
// skip inactive legends
50+
struct.read2Bytes()
51+
struct.read2Bytes()
52+
struct.read2Bytes()
53+
struct.read2Bytes()
54+
struct.read2Bytes()
55+
struct.read2Bytes()
56+
57+
// check if we are at the end of the build code already,
58+
// selectedWeapons and selectedSkillVariants were added with SotO,
59+
// and old chat codes generated before SotO don't contain these yet.
60+
const legacyChatCode = struct.atEnd()
61+
62+
const selectedWeapons = legacyChatCode ? undefined : struct.readDynamicArray(2)
63+
const selectedSkillVariants = legacyChatCode ? undefined : struct.readDynamicArray(4)
64+
5165
return {
52-
type,
66+
type: 'build' as const,
5367

5468
profession,
5569

@@ -80,6 +94,9 @@ export function decodeBuildLink(struct: ChatCodeStruct) {
8094
terrestrialLegend1,
8195
terrestrialLegend2,
8296
aquaticLegend1,
83-
aquaticLegend2
97+
aquaticLegend2,
98+
99+
selectedWeapons,
100+
selectedSkillVariants,
84101
}
85102
}

src/decode/decodeIdLink.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { CodeType } from '../static'
22
import { ChatCodeStruct } from '../ChatCodeStruct'
33

4-
export function decodeIdLink(type: CodeType, struct: ChatCodeStruct) {
4+
export function decodeIdLink(
5+
type: Exclude<CodeType, 'item' | 'objective' | 'build'>,
6+
struct: ChatCodeStruct,
7+
) {
58
const id = struct.read3Bytes()
69
return { type, id }
710
}

src/decode/decodeItemLink.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { CodeType, ITEM_FLAGS } from '../static'
1+
import { ITEM_FLAGS } from '../static'
22
import { ChatCodeStruct } from '../ChatCodeStruct'
33

44
export function decodeItemLink(struct: ChatCodeStruct) {
5-
const type = 'item' as CodeType
6-
75
const quantity = struct.read1Byte()
86
const id = struct.read3Bytes()
97
const flags = struct.read1Byte()
@@ -16,18 +14,18 @@ export function decodeItemLink(struct: ChatCodeStruct) {
1614
}
1715

1816
// Upgrade slot 1
19-
let upgrades
17+
let upgrades: undefined | [number] | [number, number]
2018
if (hasFlag(flags, ITEM_FLAGS.upgrade1)) {
2119
upgrades = [struct.read3Bytes()]
2220
struct.read1Byte() // Skip the next byte (always 0x00)
2321
}
2422

2523
// Upgrade slot 2
2624
if (hasFlag(flags, ITEM_FLAGS.upgrade2)) {
27-
;(upgrades as Array<number>).push(struct.read3Bytes())
25+
upgrades!.push(struct.read3Bytes())
2826
}
2927

30-
return { type, quantity, id, skin, upgrades }
28+
return { type: 'item' as const, quantity, id, skin, upgrades }
3129
}
3230

3331
function hasFlag(flags: number, flag: number) {

src/decode/decodeObjectiveLink.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { CodeType } from '../static'
21
import { ChatCodeStruct } from '../ChatCodeStruct'
32

43
export function decodeObjectiveLink(struct: ChatCodeStruct) {
5-
const type = 'objective' as CodeType
6-
74
const id = struct.read3Bytes()
85
struct.read1Byte() // Skip the next byte (always 0x00)
96
const map = struct.read3Bytes()
107

11-
return { type, id: `${map}-${id}` }
8+
return { type: 'objective' as const, id: `${map}-${id}` }
129
}

src/encode/encode.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,23 @@ import { encodeObjectiveLink, ObjectiveLinkMeta } from './encodeObjectiveLink'
44
import { encodeItemLink, ItemLinkMeta } from './encodeItemLink'
55
import { BuildLinkMeta, encodeBuildLink } from './encodeBuildLink'
66

7-
type MetaOrId = string | number | IdLinkMeta | ItemLinkMeta | ObjectiveLinkMeta | BuildLinkMeta
7+
type TypeMeta = {
8+
item: ItemLinkMeta | ItemLinkMeta['id']
9+
map: IdLinkMeta | IdLinkMeta['id']
10+
skill: IdLinkMeta | IdLinkMeta['id']
11+
trait: IdLinkMeta | IdLinkMeta['id']
12+
recipe: IdLinkMeta | IdLinkMeta['id']
13+
skin: IdLinkMeta | IdLinkMeta['id']
14+
outfit: IdLinkMeta | IdLinkMeta['id']
15+
objective: ObjectiveLinkMeta | ObjectiveLinkMeta['id']
16+
build: BuildLinkMeta
17+
}
818

9-
export function encode(type: CodeType, metaOrId: MetaOrId): string | false {
19+
export function encode<Type extends CodeType>(type: Type, data: TypeMeta[Type]): string | false {
1020
const codeType = type.trim().toLowerCase() as CodeType
1121

1222
// Normalize into a meta object if only the ID was passed
13-
const meta = typeof metaOrId !== 'object' ? { id: metaOrId } : metaOrId
23+
const meta = typeof data !== 'object' ? { id: data } : data
1424

1525
switch (codeType) {
1626
case 'map':

src/encode/encodeBuildLink.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ export type BuildLinkMeta = {
55
profession: number
66

77
specialization1: number
8-
traitChoices1: Array<number>
8+
traitChoices1: [number, number, number]
99
specialization2: number
10-
traitChoices2: Array<number>
10+
traitChoices2: [number, number, number]
1111
specialization3: number
12-
traitChoices3: Array<number>
12+
traitChoices3: [number, number, number]
1313

1414
terrestrialHealSkill: number
1515
terrestrialUtilitySkill1: number
@@ -32,6 +32,9 @@ export type BuildLinkMeta = {
3232
terrestrialLegend2?: number
3333
aquaticLegend1?: number
3434
aquaticLegend2?: number
35+
36+
selectedWeapons?: number[]
37+
selectedSkillVariants?: number[]
3538
}
3639

3740
export function encodeBuildLink(meta: BuildLinkMeta): string | false {
@@ -103,5 +106,8 @@ export function encodeBuildLink(meta: BuildLinkMeta): string | false {
103106
struct.write2Bytes(0x00)
104107
}
105108

109+
struct.writeDynamicArray(meta.selectedWeapons ?? [], 2)
110+
struct.writeDynamicArray(meta.selectedSkillVariants ?? [], 4)
111+
106112
return struct.encodeToChatCode()
107113
}

src/static.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ export const TYPE_HEADERS: { [key in CodeType]: number } = {
1818
skin: 0x0a,
1919
outfit: 0x0b,
2020
objective: 0x0c,
21-
build: 0x0d
21+
build: 0x0d,
2222
}
2323

2424
export const ITEM_FLAGS = {
2525
skin: 0x80,
2626
upgrade1: 0x40,
27-
upgrade2: 0x20
27+
upgrade2: 0x20,
2828
}
2929

3030
export const PROFESSION_FLAGS = {
3131
ranger: 0x04,
32-
revenant: 0x09
32+
revenant: 0x09,
3333
}

0 commit comments

Comments
 (0)