Skip to content

Commit

Permalink
Merge branch 'darthmaim-feature/soto'
Browse files Browse the repository at this point in the history
  • Loading branch information
queicherius committed Sep 4, 2023
2 parents 0123bb5 + cdfd590 commit fabb9b0
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 56 deletions.
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
"url": "https://github.com/gw2efficiency/chat-codes"
},
"dependencies": {
"base64-js": "^1.1.2"
"base64-js": "^1.5.1"
},
"devDependencies": {
"@types/base64-js": "^1.2.5",
"@types/jest": "^24.9.0",
"jest": "^24.9.0",
"prettier": "^1.19.1",
"ts-jest": "^24.3.0",
"typescript": "^3.7.5"
"@types/base64-js": "^1.3.0",
"@types/jest": "^29.5.4",
"jest": "^29.6.4",
"prettier": "^3.0.3",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
}
}
49 changes: 46 additions & 3 deletions src/ChatCodeStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,25 @@ export class ChatCodeStruct {
this.bytes.push((value >> 0x10) & 0xff)
}

writeTraitSelection([trait1, trait2, trait3]: Array<number>) {
write4Bytes(value: number) {
this.bytes.push((value >> 0x00) & 0xff)
this.bytes.push((value >> 0x08) & 0xff)
this.bytes.push((value >> 0x10) & 0xff)
this.bytes.push((value >> 0x18) & 0xff)
}

writeTraitSelection([trait1, trait2, trait3]: [number, number, number]) {
const value = ((trait3 & 3) << 4) | ((trait2 & 3) << 2) | ((trait1 & 3) << 0)
this.write1Byte(value)
}

writeDynamicArray(values: number[], bytesPerValue: 2 | 4) {
this.write1Byte(values.length)
for (const value of values) {
bytesPerValue === 2 ? this.write2Bytes(value) : this.write4Bytes(value)
}
}

// -- DECODING --

decodeFromChatCode(chatCode: string) {
Expand All @@ -60,11 +74,40 @@ export class ChatCodeStruct {
)
}

readTraitSelection(): Array<number> {
read4Bytes() {
return (
this.bytes[this.offset++] |
(this.bytes[this.offset++] << 8) |
(this.bytes[this.offset++] << 16) |
(this.bytes[this.offset++] << 24)
)
}

readTraitSelection(): [number, number, number] {
return [
this.bytes[this.offset] & 3,
(this.bytes[this.offset] >> 2) & 3,
(this.bytes[this.offset++] >> 4) & 3
(this.bytes[this.offset++] >> 4) & 3,
]
}

readDynamicArray(bytesPerValue: 2 | 4): undefined | number[] {
const length = this.read1Byte()

if (length === 0) {
return undefined
}

const values: number[] = []

for (let i = 0; i < length; i++) {
values.push(bytesPerValue === 2 ? this.read2Bytes() : this.read4Bytes())
}

return values
}

atEnd(): boolean {
return this.offset >= this.bytes.length
}
}
2 changes: 1 addition & 1 deletion src/decode/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function decode(chatCode: string) {
// The header describing the type of the chat code is encoded as the first byte
const typeHeader = struct.read1Byte()
const codeType = Object.keys(TYPE_HEADERS).find(
(key) => TYPE_HEADERS[key as CodeType] === typeHeader
(key) => TYPE_HEADERS[key as CodeType] === typeHeader,
)

switch (codeType) {
Expand Down
27 changes: 22 additions & 5 deletions src/decode/decodeBuildLink.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { CodeType, PROFESSION_FLAGS } from '../static'
import { PROFESSION_FLAGS } from '../static'
import { ChatCodeStruct } from '../ChatCodeStruct'

export function decodeBuildLink(struct: ChatCodeStruct) {
const type = 'build' as CodeType

const profession = struct.read1Byte()

const specialization1 = struct.read1Byte()
Expand Down Expand Up @@ -48,8 +46,24 @@ export function decodeBuildLink(struct: ChatCodeStruct) {
aquaticLegend2 = struct.read1Byte()
}

// skip inactive legends
struct.read2Bytes()
struct.read2Bytes()
struct.read2Bytes()
struct.read2Bytes()
struct.read2Bytes()
struct.read2Bytes()

// check if we are at the end of the build code already,
// selectedWeapons and selectedSkillVariants were added with SotO,
// and old chat codes generated before SotO don't contain these yet.
const legacyChatCode = struct.atEnd()

const selectedWeapons = legacyChatCode ? undefined : struct.readDynamicArray(2)
const selectedSkillVariants = legacyChatCode ? undefined : struct.readDynamicArray(4)

return {
type,
type: 'build' as const,

profession,

Expand Down Expand Up @@ -80,6 +94,9 @@ export function decodeBuildLink(struct: ChatCodeStruct) {
terrestrialLegend1,
terrestrialLegend2,
aquaticLegend1,
aquaticLegend2
aquaticLegend2,

selectedWeapons,
selectedSkillVariants,
}
}
5 changes: 4 additions & 1 deletion src/decode/decodeIdLink.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { CodeType } from '../static'
import { ChatCodeStruct } from '../ChatCodeStruct'

export function decodeIdLink(type: CodeType, struct: ChatCodeStruct) {
export function decodeIdLink(
type: Exclude<CodeType, 'item' | 'objective' | 'build'>,
struct: ChatCodeStruct,
) {
const id = struct.read3Bytes()
return { type, id }
}
10 changes: 4 additions & 6 deletions src/decode/decodeItemLink.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { CodeType, ITEM_FLAGS } from '../static'
import { ITEM_FLAGS } from '../static'
import { ChatCodeStruct } from '../ChatCodeStruct'

export function decodeItemLink(struct: ChatCodeStruct) {
const type = 'item' as CodeType

const quantity = struct.read1Byte()
const id = struct.read3Bytes()
const flags = struct.read1Byte()
Expand All @@ -16,18 +14,18 @@ export function decodeItemLink(struct: ChatCodeStruct) {
}

// Upgrade slot 1
let upgrades
let upgrades: undefined | [number] | [number, number]
if (hasFlag(flags, ITEM_FLAGS.upgrade1)) {
upgrades = [struct.read3Bytes()]
struct.read1Byte() // Skip the next byte (always 0x00)
}

// Upgrade slot 2
if (hasFlag(flags, ITEM_FLAGS.upgrade2)) {
;(upgrades as Array<number>).push(struct.read3Bytes())
upgrades!.push(struct.read3Bytes())
}

return { type, quantity, id, skin, upgrades }
return { type: 'item' as const, quantity, id, skin, upgrades }
}

function hasFlag(flags: number, flag: number) {
Expand Down
5 changes: 1 addition & 4 deletions src/decode/decodeObjectiveLink.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { CodeType } from '../static'
import { ChatCodeStruct } from '../ChatCodeStruct'

export function decodeObjectiveLink(struct: ChatCodeStruct) {
const type = 'objective' as CodeType

const id = struct.read3Bytes()
struct.read1Byte() // Skip the next byte (always 0x00)
const map = struct.read3Bytes()

return { type, id: `${map}-${id}` }
return { type: 'objective' as const, id: `${map}-${id}` }
}
16 changes: 13 additions & 3 deletions src/encode/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ import { encodeObjectiveLink, ObjectiveLinkMeta } from './encodeObjectiveLink'
import { encodeItemLink, ItemLinkMeta } from './encodeItemLink'
import { BuildLinkMeta, encodeBuildLink } from './encodeBuildLink'

type MetaOrId = string | number | IdLinkMeta | ItemLinkMeta | ObjectiveLinkMeta | BuildLinkMeta
type TypeMeta = {
item: ItemLinkMeta | ItemLinkMeta['id']
map: IdLinkMeta | IdLinkMeta['id']
skill: IdLinkMeta | IdLinkMeta['id']
trait: IdLinkMeta | IdLinkMeta['id']
recipe: IdLinkMeta | IdLinkMeta['id']
skin: IdLinkMeta | IdLinkMeta['id']
outfit: IdLinkMeta | IdLinkMeta['id']
objective: ObjectiveLinkMeta | ObjectiveLinkMeta['id']
build: BuildLinkMeta
}

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

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

switch (codeType) {
case 'map':
Expand Down
12 changes: 9 additions & 3 deletions src/encode/encodeBuildLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export type BuildLinkMeta = {
profession: number

specialization1: number
traitChoices1: Array<number>
traitChoices1: [number, number, number]
specialization2: number
traitChoices2: Array<number>
traitChoices2: [number, number, number]
specialization3: number
traitChoices3: Array<number>
traitChoices3: [number, number, number]

terrestrialHealSkill: number
terrestrialUtilitySkill1: number
Expand All @@ -32,6 +32,9 @@ export type BuildLinkMeta = {
terrestrialLegend2?: number
aquaticLegend1?: number
aquaticLegend2?: number

selectedWeapons?: number[]
selectedSkillVariants?: number[]
}

export function encodeBuildLink(meta: BuildLinkMeta): string | false {
Expand Down Expand Up @@ -103,5 +106,8 @@ export function encodeBuildLink(meta: BuildLinkMeta): string | false {
struct.write2Bytes(0x00)
}

struct.writeDynamicArray(meta.selectedWeapons ?? [], 2)
struct.writeDynamicArray(meta.selectedSkillVariants ?? [], 4)

return struct.encodeToChatCode()
}
6 changes: 3 additions & 3 deletions src/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ export const TYPE_HEADERS: { [key in CodeType]: number } = {
skin: 0x0a,
outfit: 0x0b,
objective: 0x0c,
build: 0x0d
build: 0x0d,
}

export const ITEM_FLAGS = {
skin: 0x80,
upgrade1: 0x40,
upgrade2: 0x20
upgrade2: 0x20,
}

export const PROFESSION_FLAGS = {
ranger: 0x04,
revenant: 0x09
revenant: 0x09,
}
Loading

0 comments on commit fabb9b0

Please sign in to comment.