Skip to content

Commit 2bcdb3a

Browse files
committed
preliminary amalgam support, still waiting for the official api to get back online
closes #30
1 parent bb79048 commit 2bcdb3a

14 files changed

Lines changed: 242 additions & 21 deletions

hs_code_v2_spec.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Edits
44
2023-08-23: Added Relics after the release of the 'Secrets of the Obscure' expansion.
5+
2025-10-30: Added Amalgam toolbelt selected skills to profession specific section.
56

67
## Ethos
78
The following considerations were taken into account in descending priority:
@@ -117,6 +118,10 @@ V T P S.TS.TS.T WS..wS..WS..wS.. S..S..S..S..S.. R.. R.. A..n,,,, I..n,,,, F..U.
117118
- `_` (underscore): empty alternate legend utility skill slot
118119
- `1-3 characters`: alternate legend utility skill id resolved by `/v2/skills`, `encode(id, 3)`
119120
- omit whole block if second legend is empty
121+
- Amalgam F2-F4 Skills: [3-9 characters]
122+
- 3 times
123+
- `_` (underscore): if empty
124+
- `1-3 characters`: skill id resolved by `/v2/skills`, `encode(id, 3)`
120125

121126
### A note on underwater Codes:
122127
There are no special fields defined for handling underwater data. To define underwater data just make the following adjustments:
@@ -197,6 +202,9 @@ or
197202
198203
repeat 3. alternate legend skills. omit if legend 2 is empty
199204
24 : 0 if empty, Skill id otherwise
205+
or
206+
repeat 3.
207+
24 : 0 if empty, Skill id otherwise
200208
```
201-
(406 <-> 482) bits / 8 * 4/3 = (68 <-> 81) chars.
209+
(407 <-> 483) bits / 8 * 4/3 = (68 <-> 81) chars.
202210
Interestingly only about 12 chars (~ 20%) less than the textual representation, the encoding _does_ expand it a lot.

include/c#/10/BinaryLoader.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public static BuildCode LoadBuildCode(ReadOnlySpan<byte> raw)
176176
code.Food = (ItemId)rawSpan.DecodeNext(24);
177177
code.Utility = (ItemId)rawSpan.DecodeNext(24);
178178
}
179-
code.ProfessionSpecific = LoadProfessionSpecific(ref rawSpan, code.Profession);
179+
code.ProfessionSpecific = LoadProfessionSpecific(ref rawSpan, code.Profession, code.Specializations.Choice3.SpecializationId);
180180
code.Arbitrary = LoadArbitrary(ref rawSpan);
181181

182182
return code;
@@ -237,7 +237,7 @@ private static AllEquipmentInfusions LoadAllEquipmentInfusions(ref BitReader raw
237237
return allData;
238238
}
239239

240-
private static IProfessionSpecific LoadProfessionSpecific(ref BitReader rawSpan, Profession profession)
240+
private static IProfessionSpecific LoadProfessionSpecific(ref BitReader rawSpan, Profession profession, SpecializationId eliteSpec)
241241
{
242242
switch(profession)
243243
{
@@ -255,13 +255,22 @@ private static IProfessionSpecific LoadProfessionSpecific(ref BitReader rawSpan,
255255
data.Legend1 = (Legend)rawSpan.DecodeNext(4);
256256
if(!rawSpan.EatIfExpected(0, 4)){
257257
data.Legend2 = (Legend)rawSpan.DecodeNext(4);
258-
rawSpan.DecodeNext_WriteMinusMinIfAtLeast(ref data.AltUtilitySkill1, 1, 24);
259-
rawSpan.DecodeNext_WriteMinusMinIfAtLeast(ref data.AltUtilitySkill2, 1, 24);
260-
rawSpan.DecodeNext_WriteMinusMinIfAtLeast(ref data.AltUtilitySkill3, 1, 24);
258+
data.AltUtilitySkill1 = (SkillId)rawSpan.DecodeNext(24);
259+
data.AltUtilitySkill2 = (SkillId)rawSpan.DecodeNext(24);
260+
data.AltUtilitySkill3 = (SkillId)rawSpan.DecodeNext(24);
261261
}
262262
return data;
263263
}
264264

265+
case Profession.Engineer: if(eliteSpec == SpecializationId.Amalgam) {
266+
var data = new AmalgamData();
267+
data.ToolbeltSkill2 = (SkillId)rawSpan.DecodeNext(24);
268+
data.ToolbeltSkill3 = (SkillId)rawSpan.DecodeNext(24);
269+
data.ToolbeltSkill4 = (SkillId)rawSpan.DecodeNext(24);
270+
return data;
271+
}
272+
goto default;
273+
265274
default: return IProfessionSpecific.NONE.Instance;
266275
}
267276
}
@@ -417,6 +426,16 @@ public static int WriteBuildCode(BuildCode code, Span<byte> destination)
417426
rawBits.Write((int)revenantData.AltUtilitySkill3, 24);
418427
}
419428
break;
429+
430+
431+
case Profession.Engineer:
432+
if(code.Specializations.Choice3.SpecializationId == SpecializationId.Amalgam) {
433+
var amalgamData = (AmalgamData)code.ProfessionSpecific;
434+
rawBits.Write((int)amalgamData.ToolbeltSkill2, 24);
435+
rawBits.Write((int)amalgamData.ToolbeltSkill3, 24);
436+
rawBits.Write((int)amalgamData.ToolbeltSkill4, 24);
437+
}
438+
break;
420439
}
421440

422441
return (rawBits.BitPos + 7) / 8;

include/c#/10/Database/SpecializationIds.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,13 @@ public enum SpecializationId {
7474
Mechanist = 70,
7575
Specter = 71,
7676
Untamed = 72,
77+
Troubadour = 73,
78+
Paragon = 74,
79+
Amalgam = 75,
80+
Ritualist = 76,
81+
Antiquary = 77,
82+
Galeshot = 78,
83+
Conduit = 79,
84+
Evoker = 80,
85+
Luminary = 81,
7786
}

include/c#/10/Structures.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,20 @@ public class RevenantData : IProfessionSpecific {
111111
/// <remarks> Is <see cref="Legend._UNDEFINED"/> if the legend is not set. </remarks>
112112
public Legend Legend2;
113113

114-
/// <remarks> Is <see cref="Legend._UNDEFINED"/> if the second legend is not set. </remarks>
114+
/// <remarks> Is <see cref="SkillId._UNDEFINED"/> if the second legend is not set. </remarks>
115115
public SkillId AltUtilitySkill1;
116-
/// <remarks> Is <see cref="Legend._UNDEFINED"/> if the second legend is not set. </remarks>
116+
/// <remarks> Is <see cref="SkillId._UNDEFINED"/> if the second legend is not set. </remarks>
117117
public SkillId AltUtilitySkill2;
118-
/// <remarks> Is <see cref="Legend._UNDEFINED"/> if the second legend is not set. </remarks>
118+
/// <remarks> Is <see cref="SkillId._UNDEFINED"/> if the second legend is not set. </remarks>
119119
public SkillId AltUtilitySkill3;
120120
}
121121

122+
public class AmalgamData : IProfessionSpecific {
123+
public SkillId ToolbeltSkill2;
124+
public SkillId ToolbeltSkill3;
125+
public SkillId ToolbeltSkill4;
126+
}
127+
122128
public enum Legend {
123129
_UNDEFINED = 0,
124130
/// <summary> Assasin </summary>

include/c#/10/TextLoader.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public static BuildCode LoadBuildCode(ReadOnlySpan<char> text) {
116116
code.Utility = (ItemId)DecodeAndAdvance(ref text, 3);
117117
}
118118

119-
code.ProfessionSpecific = LoadProfessionSpecific(ref text, code.Profession);
119+
code.ProfessionSpecific = LoadProfessionSpecific(ref text, code.Profession, code.Specializations.Choice3.SpecializationId);
120120
code.Arbitrary = LoadArbitrary(ref text);
121121
return code;
122122
}
@@ -180,7 +180,7 @@ private static AllEquipmentInfusions LoadAllEquipmentInfusions(ref ReadOnlySpan<
180180
return allData;
181181
}
182182

183-
private static IProfessionSpecific LoadProfessionSpecific(ref ReadOnlySpan<char> text, Profession profession)
183+
private static IProfessionSpecific LoadProfessionSpecific(ref ReadOnlySpan<char> text, Profession profession, SpecializationId eliteSpec)
184184
{
185185
switch(profession)
186186
{
@@ -210,6 +210,18 @@ private static IProfessionSpecific LoadProfessionSpecific(ref ReadOnlySpan<char>
210210
return data;
211211
}
212212

213+
case Profession.Engineer: if(eliteSpec == SpecializationId.Amalgam) {
214+
var data = new AmalgamData();
215+
if(!EatToken(ref text, '_'))
216+
data.ToolbeltSkill2 = (SkillId)DecodeAndAdvance(ref text, 3);
217+
if(!EatToken(ref text, '_'))
218+
data.ToolbeltSkill3 = (SkillId)DecodeAndAdvance(ref text, 3);
219+
if(!EatToken(ref text, '_'))
220+
data.ToolbeltSkill4 = (SkillId)DecodeAndAdvance(ref text, 3);
221+
return data;
222+
}
223+
goto default;
224+
213225
default: return IProfessionSpecific.NONE.Instance;
214226
}
215227
}
@@ -430,6 +442,24 @@ private static void EncodeProfessionSpecific(ref Span<char> destination, BuildCo
430442
WriteAndAdvance(ref destination, '_');
431443
}
432444
break;
445+
446+
case Profession.Engineer:
447+
if(code.Specializations.Choice3.SpecializationId == SpecializationId.Amalgam)
448+
{
449+
if(code.ProfessionSpecific is AmalgamData amalgamData)
450+
{
451+
EncodeOrUnderscoreOnZeroAndAdvance(ref destination, (int)amalgamData.ToolbeltSkill2, 3);
452+
EncodeOrUnderscoreOnZeroAndAdvance(ref destination, (int)amalgamData.ToolbeltSkill3, 3);
453+
EncodeOrUnderscoreOnZeroAndAdvance(ref destination, (int)amalgamData.ToolbeltSkill4, 3);
454+
}
455+
else
456+
{
457+
WriteAndAdvance(ref destination, '_');
458+
WriteAndAdvance(ref destination, '_');
459+
WriteAndAdvance(ref destination, '_');
460+
}
461+
}
462+
break;
433463
}
434464
}
435465

include/php/8.0/BinaryLoader.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public static function LoadBuildCode(string $raw) : BuildCode
169169
$code->Food = $rawSpan->DecodeNext(24);
170170
$code->Utility = $rawSpan->DecodeNext(24);
171171
}
172-
$code->ProfessionSpecific = BinaryLoader::LoadProfessionSpecific($rawSpan, $code->Profession);
172+
$code->ProfessionSpecific = BinaryLoader::LoadProfessionSpecific($rawSpan, $code->Profession, $code->Specializations->Choice3->SpecializationId);
173173
$code->Arbitrary = BinaryLoader::LoadArbitrary($rawSpan);
174174

175175
return $code;
@@ -230,7 +230,7 @@ private static function LoadAllEquipmentInfusions(BitReader $rawSpan, BuildCode
230230
return $allData;
231231
}
232232

233-
private static function LoadProfessionSpecific(BitReader $rawSpan, int $profession) : IProfessionSpecific
233+
private static function LoadProfessionSpecific(BitReader $rawSpan, int $profession, int $eliteSpec) : IProfessionSpecific
234234
{
235235
switch($profession)
236236
{
@@ -255,6 +255,15 @@ private static function LoadProfessionSpecific(BitReader $rawSpan, int $professi
255255
return $data;
256256
}
257257

258+
case Profession::Engineer: if($eliteSpec === SpecializationId::Amalgam) {
259+
$data = new AmalgamData();
260+
$data->ToolbeltSkill2 = $rawSpan->DecodeNext(24);
261+
$data->ToolbeltSkill3 = $rawSpan->DecodeNext(24);
262+
$data->ToolbeltSkill4 = $rawSpan->DecodeNext(24);
263+
return $data;
264+
}
265+
// fallthrough
266+
258267
default: return ProfessionSpecific\NONE::GetInstance();
259268
}
260269
}
@@ -411,6 +420,16 @@ public static function WriteBuildCode(BuildCode $code) : string
411420
$rawBits->Write($revenantData->AltUtilitySkill3, 24);
412421
}
413422
break;
423+
424+
case Profession::Engineer:
425+
if($code->Specializations->Choice3->SpecializationId === SpecializationId::Amalgam) {
426+
/** @var AmalgamData */
427+
$amalgamData = $code->ProfessionSpecific;
428+
$rawBits->Write($amalgamData->ToolbeltSkill2, 24);
429+
$rawBits->Write($amalgamData->ToolbeltSkill3, 24);
430+
$rawBits->Write($amalgamData->ToolbeltSkill4, 24);
431+
}
432+
break;
414433
}
415434

416435
return pack('C*', ...$rawBits->Data);

include/php/8.0/Database/SpecializationIds.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,13 @@ class SpecializationId {
7777
public const Mechanist = 70;
7878
public const Specter = 71;
7979
public const Untamed = 72;
80+
public const Troubadour = 73;
81+
public const Paragon = 74;
82+
public const Amalgam = 75;
83+
public const Ritualist = 76;
84+
public const Antiquary = 77;
85+
public const Galeshot = 78;
86+
public const Conduit = 79;
87+
public const Evoker = 80;
88+
public const Luminary = 81;
8089
}

include/php/8.0/Structures.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ class RevenantData implements IProfessionSpecific {
194194
public int $AltUtilitySkill3 = SkillId::_UNDEFINED;
195195
}
196196

197+
class AmalgamData implements IProfessionSpecific {
198+
public int $ToolbeltSkill2 = SkillId::_UNDEFINED;
199+
public int $ToolbeltSkill3 = SkillId::_UNDEFINED;
200+
public int $ToolbeltSkill4 = SkillId::_UNDEFINED;
201+
}
202+
197203
class Legend {
198204
use Util\Enum;
199205
use Util\First;

include/php/8.0/TextLoader.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public static function LoadBuildCode(string $text) : BuildCode {
108108
$code->Utility = TextLoader::DecodeAndAdvance($view, 3);
109109
}
110110

111-
$code->ProfessionSpecific = TextLoader::LoadProfessionSpecific($view, $code->Profession);
111+
$code->ProfessionSpecific = TextLoader::LoadProfessionSpecific($view, $code->Profession, $code->Specializations->Choice3->SpecializationId);
112112
$code->Arbitrary = TextLoader::LoadArbitrary($view);
113113
return $code;
114114
}
@@ -172,7 +172,7 @@ private static function LoadAllEquipmentInfusions(StringView $text, BuildCode $w
172172
return $allData;
173173
}
174174

175-
private static function LoadProfessionSpecific(StringView $text, int $profession) : IProfessionSpecific
175+
private static function LoadProfessionSpecific(StringView $text, int $profession, int $eliteSpec) : IProfessionSpecific
176176
{
177177
switch($profession)
178178
{
@@ -202,6 +202,18 @@ private static function LoadProfessionSpecific(StringView $text, int $profession
202202
return $data;
203203
}
204204

205+
case Profession::Engineer: if($eliteSpec === SpecializationId::Amalgam) {
206+
$data = new AmalgamData();
207+
if(!TextLoader::EatToken($text, '_'))
208+
$data->ToolbeltSkill2 = TextLoader::DecodeAndAdvance($text, 3);
209+
if(!TextLoader::EatToken($text, '_'))
210+
$data->ToolbeltSkill3 = TextLoader::DecodeAndAdvance($text, 3);
211+
if(!TextLoader::EatToken($text, '_'))
212+
$data->ToolbeltSkill4 = TextLoader::DecodeAndAdvance($text, 3);
213+
return $data;
214+
}
215+
// fallthrough
216+
205217
default: return ProfessionSpecific\NONE::GetInstance();
206218
}
207219
}
@@ -413,6 +425,24 @@ private static function EncodeProfessionSpecific(string &$destination, BuildCode
413425
$destination .= '__';
414426
}
415427
break;
428+
429+
case Profession::Engineer:
430+
if($code->Specializations->Choice3->SpecializationId === SpecializationId::Amalgam)
431+
{
432+
if(get_class($code->ProfessionSpecific) === AmalgamData::class)
433+
{
434+
/** @var AmalgamData $amalgamData */
435+
$amalgamData = $code->ProfessionSpecific;
436+
TextLoader::EncodeOrUnderscoreOnZeroAndAdvance($destination, $amalgamData->ToolbeltSkill2, 3);
437+
TextLoader::EncodeOrUnderscoreOnZeroAndAdvance($destination, $amalgamData->ToolbeltSkill3, 3);
438+
TextLoader::EncodeOrUnderscoreOnZeroAndAdvance($destination, $amalgamData->ToolbeltSkill4, 3);
439+
}
440+
else
441+
{
442+
$destination .= '___';
443+
}
444+
}
445+
break;
416446
}
417447
}
418448

include/ts/es6/BinaryLoader.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import SpecializationId from "./Database/SpecializationIds";
55
import Overrides from "./Database/Overrides";
66
import { ALL_EQUIPMENT_COUNT, ALL_INFUSION_COUNT, CURRENT_VERSION, FIRST_VERSIONED_VERSION, HasAttributeSlot, HasInfusionSlot, IsTwoHanded } from "./Database/Static";
77
import StatId from "./Database/StatIds";
8-
import { Arbitrary, BuildCode, IArbitrary, IProfessionSpecific, Kind, Legend, PetId, Profession, ProfessionSpecific, RangerData, RevenantData, Specialization, TraitLineChoice, WeaponSet, WeaponType } from "./Structures";
8+
import { Arbitrary, BuildCode, IArbitrary, IProfessionSpecific, Kind, Legend, PetId, Profession, ProfessionSpecific, RangerData, RevenantData, AmalgamData, Specialization, TraitLineChoice, WeaponSet, WeaponType } from "./Structures";
99
import { AllEquipmentInfusions, AllEquipmentStats, TraitLineChoices } from "./Util/UtilStructs";
1010
import BinaryView from "./Util/BinaryView";
1111
import { Assert } from "./Util/Static";
@@ -171,7 +171,7 @@ class BinaryLoader {
171171
code.Food = rawSpan.DecodeNext(24);
172172
code.Utility = rawSpan.DecodeNext(24);
173173
}
174-
code.ProfessionSpecific = BinaryLoader.LoadProfessionSpecific(rawSpan, code.Profession);
174+
code.ProfessionSpecific = BinaryLoader.LoadProfessionSpecific(rawSpan, code.Profession, code.Specializations.Choice3.SpecializationId);
175175
code.Arbitrary = BinaryLoader.LoadArbitrary(rawSpan);
176176

177177
return code;
@@ -232,7 +232,7 @@ class BinaryLoader {
232232
return allData;
233233
}
234234

235-
private static LoadProfessionSpecific(rawSpan : BitReader, profession : Profession) : IProfessionSpecific
235+
private static LoadProfessionSpecific(rawSpan : BitReader, profession : Profession, eliteSpec : SpecializationId) : IProfessionSpecific
236236
{
237237
switch(profession)
238238
{
@@ -257,6 +257,15 @@ class BinaryLoader {
257257
return data;
258258
}
259259

260+
case Profession.Engineer: if(eliteSpec == SpecializationId.Amalgam) {
261+
var data = new AmalgamData();
262+
data.ToolbeltSkill2 = rawSpan.DecodeNext(24);
263+
data.ToolbeltSkill3 = rawSpan.DecodeNext(24);
264+
data.ToolbeltSkill4 = rawSpan.DecodeNext(24);
265+
return data;
266+
}
267+
// fallthrough
268+
260269
default: return ProfessionSpecific.NONE.Instance;
261270
}
262271
}
@@ -411,6 +420,15 @@ class BinaryLoader {
411420
rawBits.Write(revenantData.AltUtilitySkill3, 24);
412421
}
413422
break;
423+
424+
case Profession.Engineer:
425+
if(code.Specializations.Choice3.SpecializationId == SpecializationId.Amalgam) {
426+
const amalgamData = code.ProfessionSpecific as AmalgamData;
427+
rawBits.Write(amalgamData.ToolbeltSkill2, 24);
428+
rawBits.Write(amalgamData.ToolbeltSkill3, 24);
429+
rawBits.Write(amalgamData.ToolbeltSkill4, 24);
430+
}
431+
break;
414432
}
415433

416434
return new Uint8Array(rawBits.Data);

0 commit comments

Comments
 (0)