ultimatedarktower 4.1.0 → 5.0.1
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/CHANGELOG.md +51 -0
- package/dist/esm/index.mjs +1598 -556
- package/dist/src/UltimateDarkTower.d.ts +5 -13
- package/dist/src/UltimateDarkTower.js +33 -44
- package/dist/src/UltimateDarkTower.js.map +1 -1
- package/dist/src/adapters/NodeBluetoothAdapter.js +7 -9
- package/dist/src/adapters/NodeBluetoothAdapter.js.map +1 -1
- package/dist/src/adapters/WebBluetoothAdapter.js +1 -0
- package/dist/src/adapters/WebBluetoothAdapter.js.map +1 -1
- package/dist/src/data/board/index.d.ts +9 -0
- package/dist/src/data/board/index.js +26 -0
- package/dist/src/data/board/index.js.map +1 -0
- package/dist/src/data/board/udtBoardAdjacency.js.map +1 -0
- package/dist/src/data/board/udtBoardAnchors.js.map +1 -0
- package/dist/src/data/board/udtGameBoard.js.map +1 -0
- package/dist/src/data/index.d.ts +19 -0
- package/dist/src/data/index.js +56 -0
- package/dist/src/data/index.js.map +1 -0
- package/dist/src/data/udtBoxInventory.d.ts +47 -0
- package/dist/src/data/udtBoxInventory.js +679 -0
- package/dist/src/data/udtBoxInventory.js.map +1 -0
- package/dist/src/{udtFoes.d.ts → data/udtFoes.d.ts} +1 -1
- package/dist/src/data/udtFoes.js.map +1 -0
- package/dist/src/data/udtGameContent.d.ts +418 -0
- package/dist/src/data/udtGameContent.js +293 -0
- package/dist/src/data/udtGameContent.js.map +1 -0
- package/dist/src/data/udtHeroes.js.map +1 -0
- package/dist/src/data/udtMonuments.js.map +1 -0
- package/dist/src/index.d.ts +2 -15
- package/dist/src/index.js +30 -55
- package/dist/src/index.js.map +1 -1
- package/dist/src/seed/index.d.ts +7 -0
- package/dist/src/seed/index.js +24 -0
- package/dist/src/seed/index.js.map +1 -0
- package/dist/src/seed/udtSeedParser.js.map +1 -0
- package/dist/src/{udtSystemRandom.d.ts → seed/udtSystemRandom.d.ts} +6 -0
- package/dist/src/{udtSystemRandom.js → seed/udtSystemRandom.js} +21 -2
- package/dist/src/seed/udtSystemRandom.js.map +1 -0
- package/dist/src/udtBleConnection.js +3 -2
- package/dist/src/udtBleConnection.js.map +1 -1
- package/dist/src/udtCommandFactory.d.ts +1 -1
- package/dist/src/udtCommandFactory.js +4 -2
- package/dist/src/udtCommandFactory.js.map +1 -1
- package/dist/src/udtDiagnostics.js +2 -1
- package/dist/src/udtDiagnostics.js.map +1 -1
- package/dist/src/udtHelpers.js +1 -4
- package/dist/src/udtHelpers.js.map +1 -1
- package/dist/src/udtLogger.js +11 -6
- package/dist/src/udtLogger.js.map +1 -1
- package/dist/src/udtTowerCommands.d.ts +10 -0
- package/dist/src/udtTowerCommands.js +66 -37
- package/dist/src/udtTowerCommands.js.map +1 -1
- package/dist/src/udtTowerResponse.d.ts +9 -0
- package/dist/src/udtTowerResponse.js +12 -1
- package/dist/src/udtTowerResponse.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/udtBoardAdjacency.js.map +0 -1
- package/dist/src/udtBoardAnchors.js.map +0 -1
- package/dist/src/udtFoes.js.map +0 -1
- package/dist/src/udtGameBoard.js.map +0 -1
- package/dist/src/udtHeroes.js.map +0 -1
- package/dist/src/udtMonuments.js.map +0 -1
- package/dist/src/udtSeedParser.js.map +0 -1
- package/dist/src/udtSystemRandom.js.map +0 -1
- /package/dist/src/{udtBoardAdjacency.d.ts → data/board/udtBoardAdjacency.d.ts} +0 -0
- /package/dist/src/{udtBoardAdjacency.js → data/board/udtBoardAdjacency.js} +0 -0
- /package/dist/src/{udtBoardAnchors.d.ts → data/board/udtBoardAnchors.d.ts} +0 -0
- /package/dist/src/{udtBoardAnchors.js → data/board/udtBoardAnchors.js} +0 -0
- /package/dist/src/{udtGameBoard.d.ts → data/board/udtGameBoard.d.ts} +0 -0
- /package/dist/src/{udtGameBoard.js → data/board/udtGameBoard.js} +0 -0
- /package/dist/src/{udtFoes.js → data/udtFoes.js} +0 -0
- /package/dist/src/{udtHeroes.d.ts → data/udtHeroes.d.ts} +0 -0
- /package/dist/src/{udtHeroes.js → data/udtHeroes.js} +0 -0
- /package/dist/src/{udtMonuments.d.ts → data/udtMonuments.d.ts} +0 -0
- /package/dist/src/{udtMonuments.js → data/udtMonuments.js} +0 -0
- /package/dist/src/{udtSeedParser.d.ts → seed/udtSeedParser.d.ts} +0 -0
- /package/dist/src/{udtSeedParser.js → seed/udtSeedParser.js} +0 -0
package/dist/esm/index.mjs
CHANGED
|
@@ -473,6 +473,7 @@ var init_WebBluetoothAdapter = __esm({
|
|
|
473
473
|
this.boundOnCharacteristicValueChanged
|
|
474
474
|
);
|
|
475
475
|
} catch (error) {
|
|
476
|
+
await this.cleanup();
|
|
476
477
|
if (error instanceof BluetoothDeviceNotFoundError || error instanceof BluetoothUserCancelledError || error instanceof BluetoothConnectionError) {
|
|
477
478
|
throw error;
|
|
478
479
|
}
|
|
@@ -677,6 +678,10 @@ var init_NodeBluetoothAdapter = __esm({
|
|
|
677
678
|
}
|
|
678
679
|
}
|
|
679
680
|
async disconnect() {
|
|
681
|
+
if (noble && this.boundStateChangeHandler) {
|
|
682
|
+
noble.removeListener("stateChange", this.boundStateChangeHandler);
|
|
683
|
+
this.boundStateChangeHandler = void 0;
|
|
684
|
+
}
|
|
680
685
|
if (!this.peripheral) return;
|
|
681
686
|
try {
|
|
682
687
|
if (this.rxCharacteristic) {
|
|
@@ -798,14 +803,6 @@ var init_NodeBluetoothAdapter = __esm({
|
|
|
798
803
|
return info;
|
|
799
804
|
}
|
|
800
805
|
async cleanup() {
|
|
801
|
-
if (noble) {
|
|
802
|
-
if (this.boundStateChangeHandler) {
|
|
803
|
-
noble.removeListener(
|
|
804
|
-
"stateChange",
|
|
805
|
-
this.boundStateChangeHandler
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
806
|
if (this.peripheral && this.boundDisconnectHandler) {
|
|
810
807
|
this.peripheral.removeListener(
|
|
811
808
|
"disconnect",
|
|
@@ -813,9 +810,6 @@ var init_NodeBluetoothAdapter = __esm({
|
|
|
813
810
|
);
|
|
814
811
|
}
|
|
815
812
|
await this.disconnect();
|
|
816
|
-
this.characteristicCallback = void 0;
|
|
817
|
-
this.disconnectCallback = void 0;
|
|
818
|
-
this.availabilityCallback = void 0;
|
|
819
813
|
}
|
|
820
814
|
/**
|
|
821
815
|
* Scans for a BLE device by name using Noble's event-driven discovery
|
|
@@ -1075,10 +1069,7 @@ function commandToPacketString(command) {
|
|
|
1075
1069
|
if (command.length === 0) {
|
|
1076
1070
|
return "[]";
|
|
1077
1071
|
}
|
|
1078
|
-
|
|
1079
|
-
command.forEach((n) => cmdStr += n.toString(16) + ",");
|
|
1080
|
-
cmdStr = cmdStr.slice(0, -1) + "]";
|
|
1081
|
-
return cmdStr;
|
|
1072
|
+
return `[${Array.from(command).map((n) => n.toString(16).padStart(2, "0")).join(",")}]`;
|
|
1082
1073
|
}
|
|
1083
1074
|
function parseDifferentialReadings(response) {
|
|
1084
1075
|
if (response.length < 5 || response[0] !== 6) {
|
|
@@ -1222,15 +1213,18 @@ var DOMOutput = class {
|
|
|
1222
1213
|
this.updateBufferSizeDisplay();
|
|
1223
1214
|
}
|
|
1224
1215
|
getEnabledLevelsFromCheckboxes() {
|
|
1225
|
-
const
|
|
1216
|
+
const levelNames = ["debug", "info", "warn", "error"];
|
|
1226
1217
|
if (typeof document === "undefined") {
|
|
1227
|
-
return
|
|
1218
|
+
return new Set(levelNames);
|
|
1228
1219
|
}
|
|
1229
|
-
const checkboxes =
|
|
1230
|
-
checkboxes.
|
|
1231
|
-
|
|
1220
|
+
const checkboxes = levelNames.map((level) => document.getElementById(`logLevel-${level}`));
|
|
1221
|
+
if (checkboxes.every((checkbox) => !checkbox)) {
|
|
1222
|
+
return new Set(levelNames);
|
|
1223
|
+
}
|
|
1224
|
+
const enabledLevels = /* @__PURE__ */ new Set();
|
|
1225
|
+
checkboxes.forEach((checkbox, index) => {
|
|
1232
1226
|
if (checkbox && checkbox.checked) {
|
|
1233
|
-
enabledLevels.add(
|
|
1227
|
+
enabledLevels.add(levelNames[index]);
|
|
1234
1228
|
}
|
|
1235
1229
|
});
|
|
1236
1230
|
return enabledLevels;
|
|
@@ -1508,7 +1502,7 @@ var TowerResponseProcessor = class {
|
|
|
1508
1502
|
return [towerCommand.name, commandToPacketString(command)];
|
|
1509
1503
|
case TC.BATTERY: {
|
|
1510
1504
|
const millivolts = getMilliVoltsFromTowerResponse(command);
|
|
1511
|
-
const retval = [towerCommand.name, `${milliVoltsToPercentage(millivolts)} (${(millivolts / 1e3).toFixed(2)}
|
|
1505
|
+
const retval = [towerCommand.name, `${milliVoltsToPercentage(millivolts)} (${(millivolts / 1e3).toFixed(2)}v)`];
|
|
1512
1506
|
if (this.logDetail) {
|
|
1513
1507
|
retval.push(commandToPacketString(command));
|
|
1514
1508
|
}
|
|
@@ -1547,6 +1541,17 @@ var TowerResponseProcessor = class {
|
|
|
1547
1541
|
isTowerStateResponse(cmdKey) {
|
|
1548
1542
|
return cmdKey === TC.STATE;
|
|
1549
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Checks if a command is a spontaneous mechanical-sensor notification that
|
|
1546
|
+
* isn't tied to any specific in-flight command (jiggle detection,
|
|
1547
|
+
* unexpected trigger, differential sensor readings). These can arrive at
|
|
1548
|
+
* any time and should not be treated as the ack for a queued command.
|
|
1549
|
+
* @param {string} cmdKey - Command key from tower message
|
|
1550
|
+
* @returns {boolean} True if this is an unsolicited notification
|
|
1551
|
+
*/
|
|
1552
|
+
isUnsolicitedResponse(cmdKey) {
|
|
1553
|
+
return cmdKey === TC.JIGGLE || cmdKey === TC.UNEXPECTED || cmdKey === TC.DIFFERENTIAL;
|
|
1554
|
+
}
|
|
1550
1555
|
};
|
|
1551
1556
|
|
|
1552
1557
|
// src/udtBluetoothAdapterFactory.ts
|
|
@@ -1758,9 +1763,10 @@ var UdtBleConnection = class {
|
|
|
1758
1763
|
}
|
|
1759
1764
|
async disconnect() {
|
|
1760
1765
|
this.stopConnectionMonitoring();
|
|
1761
|
-
if (this.isConnected) {
|
|
1762
|
-
|
|
1766
|
+
if (!this.isConnected) {
|
|
1767
|
+
return;
|
|
1763
1768
|
}
|
|
1769
|
+
this.recordIncident("user_initiated");
|
|
1764
1770
|
const adapter = this.bluetoothAdapter;
|
|
1765
1771
|
if (adapter?.isConnected()) {
|
|
1766
1772
|
await adapter.disconnect();
|
|
@@ -2161,7 +2167,7 @@ var UdtCommandFactory = class {
|
|
|
2161
2167
|
audio: audioMods
|
|
2162
2168
|
};
|
|
2163
2169
|
const command = this.createStatefulCommand(currentState, modifications);
|
|
2164
|
-
const stateWithoutAudio = currentState ?
|
|
2170
|
+
const stateWithoutAudio = currentState ? this.deepCopyTowerState(currentState) : this.createEmptyTowerState();
|
|
2165
2171
|
if (otherModifications.drum) {
|
|
2166
2172
|
otherModifications.drum.forEach((drum, index) => {
|
|
2167
2173
|
if (drum && stateWithoutAudio.drum[index]) {
|
|
@@ -2273,6 +2279,7 @@ var UdtCommandFactory = class {
|
|
|
2273
2279
|
|
|
2274
2280
|
// src/udtTowerCommands.ts
|
|
2275
2281
|
init_udtConstants();
|
|
2282
|
+
init_udtBluetoothAdapter();
|
|
2276
2283
|
|
|
2277
2284
|
// src/udtCommandQueue.ts
|
|
2278
2285
|
var CommandQueue = class {
|
|
@@ -2446,13 +2453,13 @@ var UdtTowerCommands = class {
|
|
|
2446
2453
|
* @returns Promise that resolves when command is sent successfully
|
|
2447
2454
|
*/
|
|
2448
2455
|
async sendTowerCommandDirect(command) {
|
|
2456
|
+
const cmdStr = commandToPacketString(command);
|
|
2457
|
+
this.deps.logDetail && this.deps.logger.debug(`${cmdStr}`, "[UDT][CMD]");
|
|
2458
|
+
if (!this.deps.bleConnection.isConnected) {
|
|
2459
|
+
this.deps.logger.warn("Tower is not connected", "[UDT][CMD]");
|
|
2460
|
+
throw new Error("Cannot send command: tower is not connected");
|
|
2461
|
+
}
|
|
2449
2462
|
try {
|
|
2450
|
-
const cmdStr = commandToPacketString(command);
|
|
2451
|
-
this.deps.logDetail && this.deps.logger.debug(`${cmdStr}`, "[UDT][CMD]");
|
|
2452
|
-
if (!this.deps.bleConnection.isConnected) {
|
|
2453
|
-
this.deps.logger.warn("Tower is not connected", "[UDT][CMD]");
|
|
2454
|
-
return;
|
|
2455
|
-
}
|
|
2456
2463
|
await this.deps.bleConnection.writeCommand(command);
|
|
2457
2464
|
this.deps.retrySendCommandCount.value = 0;
|
|
2458
2465
|
this.deps.bleConnection.lastSuccessfulCommand = Date.now();
|
|
@@ -2461,11 +2468,12 @@ var UdtTowerCommands = class {
|
|
|
2461
2468
|
const errorMsg = error?.message ?? String(error);
|
|
2462
2469
|
const wasCancelled = errorMsg.includes("User cancelled");
|
|
2463
2470
|
const maxRetriesReached = this.deps.retrySendCommandCount.value >= this.deps.retrySendCommandMax;
|
|
2464
|
-
const
|
|
2471
|
+
const sendError = error instanceof Error ? error : new Error(errorMsg);
|
|
2472
|
+
const isDisconnected = error instanceof BluetoothConnectionError || errorMsg.includes("Cannot read properties of null") || errorMsg.includes("GATT Server is disconnected") || errorMsg.includes("Device is not connected") || !this.deps.bleConnection.isConnected;
|
|
2465
2473
|
if (isDisconnected) {
|
|
2466
2474
|
this.deps.logger.warn("Disconnect detected during command send", "[UDT][CMD]");
|
|
2467
2475
|
await this.deps.bleConnection.disconnect();
|
|
2468
|
-
|
|
2476
|
+
throw sendError;
|
|
2469
2477
|
}
|
|
2470
2478
|
if (!maxRetriesReached && this.deps.bleConnection.isConnected && !wasCancelled) {
|
|
2471
2479
|
this.deps.logger.info(`retrying tower command attempt ${this.deps.retrySendCommandCount.value + 1}`, "[UDT][CMD]");
|
|
@@ -2473,9 +2481,9 @@ var UdtTowerCommands = class {
|
|
|
2473
2481
|
const delay = 250 * this.deps.retrySendCommandCount.value;
|
|
2474
2482
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2475
2483
|
return await this.sendTowerCommandDirect(command);
|
|
2476
|
-
} else {
|
|
2477
|
-
this.deps.retrySendCommandCount.value = 0;
|
|
2478
2484
|
}
|
|
2485
|
+
this.deps.retrySendCommandCount.value = 0;
|
|
2486
|
+
throw sendError;
|
|
2479
2487
|
}
|
|
2480
2488
|
}
|
|
2481
2489
|
/**
|
|
@@ -2484,16 +2492,21 @@ var UdtTowerCommands = class {
|
|
|
2484
2492
|
* @returns Promise that resolves when calibration command is sent
|
|
2485
2493
|
*/
|
|
2486
2494
|
async calibrate() {
|
|
2487
|
-
if (
|
|
2488
|
-
this.deps.logger.
|
|
2489
|
-
this.deps.recorder?.recordEvent("calibration_started");
|
|
2490
|
-
await this.sendTowerCommand(new Uint8Array([TOWER_COMMANDS.calibration]), "calibrate");
|
|
2491
|
-
this.deps.bleConnection.performingCalibration = true;
|
|
2492
|
-
this.deps.bleConnection.performingLongCommand = true;
|
|
2495
|
+
if (this.deps.bleConnection.performingCalibration) {
|
|
2496
|
+
this.deps.logger.warn("Tower calibration requested when tower is already performing calibration", "[UDT][CMD]");
|
|
2493
2497
|
return;
|
|
2494
2498
|
}
|
|
2495
|
-
this.deps.logger.
|
|
2496
|
-
|
|
2499
|
+
this.deps.logger.info("Performing Tower Calibration", "[UDT][CMD]");
|
|
2500
|
+
this.deps.recorder?.recordEvent("calibration_started");
|
|
2501
|
+
this.deps.bleConnection.performingCalibration = true;
|
|
2502
|
+
this.deps.bleConnection.performingLongCommand = true;
|
|
2503
|
+
try {
|
|
2504
|
+
await this.sendTowerCommand(new Uint8Array([TOWER_COMMANDS.calibration]), "calibrate");
|
|
2505
|
+
} catch (error) {
|
|
2506
|
+
this.deps.bleConnection.performingCalibration = false;
|
|
2507
|
+
this.deps.bleConnection.performingLongCommand = false;
|
|
2508
|
+
throw error;
|
|
2509
|
+
}
|
|
2497
2510
|
}
|
|
2498
2511
|
/**
|
|
2499
2512
|
* Plays a sound from the tower's audio library using stateful commands that preserve existing tower state.
|
|
@@ -2502,7 +2515,7 @@ var UdtTowerCommands = class {
|
|
|
2502
2515
|
* @returns Promise that resolves when sound command is sent
|
|
2503
2516
|
*/
|
|
2504
2517
|
async playSound(soundIndex) {
|
|
2505
|
-
const invalidIndex = soundIndex
|
|
2518
|
+
const invalidIndex = !Number.isFinite(soundIndex) || soundIndex > Object.keys(TOWER_AUDIO_LIBRARY).length || soundIndex <= 0;
|
|
2506
2519
|
if (invalidIndex) {
|
|
2507
2520
|
this.deps.logger.error(`attempt to play invalid sound index ${soundIndex}`, "[UDT][CMD]");
|
|
2508
2521
|
return;
|
|
@@ -2691,8 +2704,13 @@ var UdtTowerCommands = class {
|
|
|
2691
2704
|
async rotate(top, middle, bottom, soundIndex) {
|
|
2692
2705
|
this.deps.logDetail && this.deps.logger.debug(`Rotate Parameter TMB[${JSON.stringify(top)}|${middle}|${bottom}] S[${soundIndex}]`, "[UDT][CMD]");
|
|
2693
2706
|
const rotateCommand = this.deps.commandFactory.createRotateCommand(top, middle, bottom);
|
|
2694
|
-
if (soundIndex) {
|
|
2695
|
-
|
|
2707
|
+
if (soundIndex !== void 0) {
|
|
2708
|
+
const validSoundIndex = Number.isFinite(soundIndex) && soundIndex > 0 && soundIndex <= Object.keys(TOWER_AUDIO_LIBRARY).length;
|
|
2709
|
+
if (validSoundIndex) {
|
|
2710
|
+
rotateCommand[AUDIO_COMMAND_POS] = soundIndex;
|
|
2711
|
+
} else {
|
|
2712
|
+
this.deps.logger.error(`attempt to play invalid sound index ${soundIndex} during rotate`, "[UDT][CMD]");
|
|
2713
|
+
}
|
|
2696
2714
|
}
|
|
2697
2715
|
this.deps.logger.info("Sending rotate command" + (soundIndex ? " with sound" : ""), "[UDT]");
|
|
2698
2716
|
this.deps.bleConnection.performingLongCommand = true;
|
|
@@ -2711,6 +2729,7 @@ var UdtTowerCommands = class {
|
|
|
2711
2729
|
towerState.drum[0].position = topPosition;
|
|
2712
2730
|
towerState.drum[1].position = middlePosition;
|
|
2713
2731
|
towerState.drum[2].position = bottomPosition;
|
|
2732
|
+
this.deps.setTowerState(towerState, "rotate");
|
|
2714
2733
|
}
|
|
2715
2734
|
}
|
|
2716
2735
|
/**
|
|
@@ -2743,12 +2762,6 @@ var UdtTowerCommands = class {
|
|
|
2743
2762
|
this.deps.bleConnection.performingLongCommand = false;
|
|
2744
2763
|
this.deps.bleConnection.lastBatteryHeartbeat = Date.now();
|
|
2745
2764
|
}, this.deps.bleConnection.longTowerCommandTimeout);
|
|
2746
|
-
const towerState = this.deps.getCurrentTowerState();
|
|
2747
|
-
if (towerState) {
|
|
2748
|
-
towerState.drum[0].position = positionMap[top];
|
|
2749
|
-
towerState.drum[1].position = positionMap[middle];
|
|
2750
|
-
towerState.drum[2].position = positionMap[bottom];
|
|
2751
|
-
}
|
|
2752
2765
|
}
|
|
2753
2766
|
}
|
|
2754
2767
|
/**
|
|
@@ -2773,9 +2786,9 @@ var UdtTowerCommands = class {
|
|
|
2773
2786
|
* @returns Promise that resolves when seal break sequence is complete
|
|
2774
2787
|
*/
|
|
2775
2788
|
async breakSeal(seal, volume) {
|
|
2776
|
-
const
|
|
2777
|
-
|
|
2778
|
-
|
|
2789
|
+
const currentState = this.deps.getCurrentTowerState();
|
|
2790
|
+
const actualVolume = volume !== void 0 ? volume : currentState.audio.volume;
|
|
2791
|
+
if (actualVolume !== currentState.audio.volume) {
|
|
2779
2792
|
const stateWithVolume = { ...currentState };
|
|
2780
2793
|
stateWithVolume.audio = { sample: 0, loop: false, volume: actualVolume };
|
|
2781
2794
|
await this.sendTowerStateStateful(stateWithVolume);
|
|
@@ -2906,7 +2919,7 @@ var UdtTowerCommands = class {
|
|
|
2906
2919
|
* @returns Promise that resolves when command is sent
|
|
2907
2920
|
*/
|
|
2908
2921
|
async playSoundStateful(soundIndex, loop = false, volume) {
|
|
2909
|
-
const invalidIndex = soundIndex
|
|
2922
|
+
const invalidIndex = !Number.isFinite(soundIndex) || soundIndex > Object.keys(TOWER_AUDIO_LIBRARY).length || soundIndex <= 0;
|
|
2910
2923
|
if (invalidIndex) {
|
|
2911
2924
|
this.deps.logger.error(`attempt to play invalid sound index ${soundIndex}`, "[UDT][CMD]");
|
|
2912
2925
|
return;
|
|
@@ -2956,6 +2969,20 @@ var UdtTowerCommands = class {
|
|
|
2956
2969
|
await this.sendTowerCommand(command, "sendTowerStateStateful");
|
|
2957
2970
|
}
|
|
2958
2971
|
//#endregion
|
|
2972
|
+
/**
|
|
2973
|
+
* Updates the detailed-logging flag without discarding the command queue.
|
|
2974
|
+
* @param value - Whether detailed logging is enabled
|
|
2975
|
+
*/
|
|
2976
|
+
updateLogDetail(value) {
|
|
2977
|
+
this.deps.logDetail = value;
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Updates the max retry count without discarding the command queue.
|
|
2981
|
+
* @param value - New max retry count
|
|
2982
|
+
*/
|
|
2983
|
+
updateRetrySendCommandMax(value) {
|
|
2984
|
+
this.deps.retrySendCommandMax = value;
|
|
2985
|
+
}
|
|
2959
2986
|
/**
|
|
2960
2987
|
* Public access to sendTowerCommandDirect for testing purposes.
|
|
2961
2988
|
* This bypasses the command queue and sends commands directly.
|
|
@@ -2991,7 +3018,7 @@ var RING_BUFFER_SIZE = 500;
|
|
|
2991
3018
|
var RING_BUFFER_DRAIN = 50;
|
|
2992
3019
|
var BATTERY_HISTORY_SIZE = 60;
|
|
2993
3020
|
var PAYLOAD_MAX_BYTES = 32;
|
|
2994
|
-
var LIBRARY_VERSION = "
|
|
3021
|
+
var LIBRARY_VERSION = "5.0.0";
|
|
2995
3022
|
function detectPlatform() {
|
|
2996
3023
|
if (typeof window !== "undefined" && typeof window.navigator !== "undefined") {
|
|
2997
3024
|
return "web";
|
|
@@ -3183,7 +3210,7 @@ var UltimateDarkTower = class {
|
|
|
3183
3210
|
this.beforeUnloadHandler = null;
|
|
3184
3211
|
// tower configuration
|
|
3185
3212
|
this.retrySendCommandCountRef = { value: 0 };
|
|
3186
|
-
this.
|
|
3213
|
+
this._retrySendCommandMax = DEFAULT_RETRY_SEND_COMMAND_MAX;
|
|
3187
3214
|
// tower state
|
|
3188
3215
|
this.currentBatteryValue = 0;
|
|
3189
3216
|
this.previousBatteryValue = 0;
|
|
@@ -3241,7 +3268,6 @@ var UltimateDarkTower = class {
|
|
|
3241
3268
|
*/
|
|
3242
3269
|
initializeLogger() {
|
|
3243
3270
|
this.logger = new Logger();
|
|
3244
|
-
this.logger.addOutput(new ConsoleOutput());
|
|
3245
3271
|
}
|
|
3246
3272
|
/**
|
|
3247
3273
|
* Initialize the diagnostics recorder. Always constructed; `enabled` defaults
|
|
@@ -3307,20 +3333,20 @@ var UltimateDarkTower = class {
|
|
|
3307
3333
|
*/
|
|
3308
3334
|
setupTowerResponseCallback() {
|
|
3309
3335
|
this.towerEventCallbacks.onTowerResponse = (response) => {
|
|
3310
|
-
this.
|
|
3311
|
-
if (
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3336
|
+
const { cmdKey } = this.responseProcessor.getTowerCommand(response[0]);
|
|
3337
|
+
if (!this.responseProcessor.isUnsolicitedResponse(cmdKey)) {
|
|
3338
|
+
this.towerCommands.onTowerResponse();
|
|
3339
|
+
}
|
|
3340
|
+
if (response.length >= TOWER_STATE_RESPONSE_MIN_LENGTH && this.responseProcessor.isTowerStateResponse(cmdKey)) {
|
|
3341
|
+
const stateData = response.slice(TOWER_STATE_DATA_OFFSET, TOWER_STATE_RESPONSE_MIN_LENGTH);
|
|
3342
|
+
this.updateTowerStateFromResponse(stateData);
|
|
3317
3343
|
}
|
|
3318
3344
|
this.onTowerResponse(response);
|
|
3319
3345
|
};
|
|
3320
3346
|
}
|
|
3321
3347
|
/**
|
|
3322
|
-
|
|
3323
|
-
|
|
3348
|
+
* Create tower event callbacks for BLE connection
|
|
3349
|
+
*/
|
|
3324
3350
|
createTowerEventCallbacks() {
|
|
3325
3351
|
return {
|
|
3326
3352
|
onTowerConnect: () => this.onTowerConnect(),
|
|
@@ -3355,8 +3381,12 @@ var UltimateDarkTower = class {
|
|
|
3355
3381
|
responseProcessor: this.responseProcessor,
|
|
3356
3382
|
logDetail: this.logDetail,
|
|
3357
3383
|
retrySendCommandCount: this.retrySendCommandCountRef,
|
|
3358
|
-
retrySendCommandMax: this.
|
|
3359
|
-
|
|
3384
|
+
retrySendCommandMax: this._retrySendCommandMax,
|
|
3385
|
+
// Return a deep copy so command builders can mutate it in place before
|
|
3386
|
+
// handing it to setTowerState() without aliasing the live state (the
|
|
3387
|
+
// old/new state passed to onTowerStateUpdate would otherwise be the
|
|
3388
|
+
// same object).
|
|
3389
|
+
getCurrentTowerState: () => this.commandFactory.deepCopyTowerState(this.currentTowerState),
|
|
3360
3390
|
setTowerState: (newState, source) => this.setTowerState(newState, source),
|
|
3361
3391
|
recorder: this.diagnosticsRecorder
|
|
3362
3392
|
};
|
|
@@ -3377,15 +3407,17 @@ var UltimateDarkTower = class {
|
|
|
3377
3407
|
this._logDetail = value;
|
|
3378
3408
|
this.responseProcessor.setDetailedLogging(value);
|
|
3379
3409
|
if (this.towerCommands) {
|
|
3380
|
-
this.
|
|
3410
|
+
this.towerCommands.updateLogDetail(value);
|
|
3381
3411
|
}
|
|
3382
3412
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
this.towerCommands
|
|
3413
|
+
get retrySendCommandMax() {
|
|
3414
|
+
return this._retrySendCommandMax;
|
|
3415
|
+
}
|
|
3416
|
+
set retrySendCommandMax(value) {
|
|
3417
|
+
this._retrySendCommandMax = value;
|
|
3418
|
+
if (this.towerCommands) {
|
|
3419
|
+
this.towerCommands.updateRetrySendCommandMax(value);
|
|
3420
|
+
}
|
|
3389
3421
|
}
|
|
3390
3422
|
// Getter methods for connection state
|
|
3391
3423
|
get isConnected() {
|
|
@@ -3608,7 +3640,7 @@ var UltimateDarkTower = class {
|
|
|
3608
3640
|
* @returns The current tower state object
|
|
3609
3641
|
*/
|
|
3610
3642
|
getCurrentTowerState() {
|
|
3611
|
-
return
|
|
3643
|
+
return this.commandFactory.deepCopyTowerState(this.currentTowerState);
|
|
3612
3644
|
}
|
|
3613
3645
|
/**
|
|
3614
3646
|
* Sends a complete tower state to the tower, preserving existing state.
|
|
@@ -3775,23 +3807,6 @@ var UltimateDarkTower = class {
|
|
|
3775
3807
|
this.updateGlyphPositionsAfterRotation(level, rotationSteps);
|
|
3776
3808
|
}
|
|
3777
3809
|
}
|
|
3778
|
-
/**
|
|
3779
|
-
* Updates glyph positions for a specific level rotation.
|
|
3780
|
-
* @param level - The drum level that was rotated
|
|
3781
|
-
* @param newPosition - The new position the drum was rotated to
|
|
3782
|
-
* @deprecated Use calculateAndUpdateGlyphPositions instead
|
|
3783
|
-
*/
|
|
3784
|
-
updateGlyphPositionsForRotation(level, newPosition) {
|
|
3785
|
-
const currentPosition = this.getCurrentDrumPosition(level);
|
|
3786
|
-
const sides = ["north", "east", "south", "west"];
|
|
3787
|
-
const currentIndex = sides.indexOf(currentPosition);
|
|
3788
|
-
const newIndex = sides.indexOf(newPosition);
|
|
3789
|
-
let rotationSteps = newIndex - currentIndex;
|
|
3790
|
-
if (rotationSteps < 0) {
|
|
3791
|
-
rotationSteps += TOWER_SIDES_COUNT;
|
|
3792
|
-
}
|
|
3793
|
-
this.updateGlyphPositionsAfterRotation(level, rotationSteps);
|
|
3794
|
-
}
|
|
3795
3810
|
/**
|
|
3796
3811
|
* Checks if a specific seal is broken.
|
|
3797
3812
|
* @param seal - The seal identifier to check
|
|
@@ -4170,316 +4185,65 @@ var IndexedDBSink = class {
|
|
|
4170
4185
|
}
|
|
4171
4186
|
};
|
|
4172
4187
|
|
|
4173
|
-
// src/
|
|
4174
|
-
var
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
var
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
"
|
|
4193
|
-
"
|
|
4194
|
-
"
|
|
4195
|
-
"
|
|
4196
|
-
|
|
4188
|
+
// src/data/index.ts
|
|
4189
|
+
var data_exports = {};
|
|
4190
|
+
__export(data_exports, {
|
|
4191
|
+
board: () => board_exports,
|
|
4192
|
+
content: () => udtGameContent_exports,
|
|
4193
|
+
foes: () => udtFoes_exports,
|
|
4194
|
+
heroes: () => udtHeroes_exports,
|
|
4195
|
+
inventory: () => udtBoxInventory_exports,
|
|
4196
|
+
monuments: () => udtMonuments_exports
|
|
4197
|
+
});
|
|
4198
|
+
|
|
4199
|
+
// src/data/udtHeroes.ts
|
|
4200
|
+
var udtHeroes_exports = {};
|
|
4201
|
+
__export(udtHeroes_exports, {
|
|
4202
|
+
HEROES: () => HEROES,
|
|
4203
|
+
HERO_BY_ID: () => HERO_BY_ID
|
|
4204
|
+
});
|
|
4205
|
+
var HEROES = [
|
|
4206
|
+
// Base (4)
|
|
4207
|
+
{ id: "brutal-warlord", name: "Brutal Warlord", source: "base" },
|
|
4208
|
+
{ id: "orphaned-scion", name: "Orphaned Scion", source: "base" },
|
|
4209
|
+
{ id: "relic-hunter", name: "Relic Hunter", source: "base" },
|
|
4210
|
+
{ id: "spymaster", name: "Spymaster", source: "base" },
|
|
4211
|
+
// Alliances (2)
|
|
4212
|
+
{ id: "archwright", name: "Archwright", source: "alliances" },
|
|
4213
|
+
{ id: "haunted-recluse", name: "Haunted Recluse", source: "alliances" },
|
|
4214
|
+
// Covenant (4)
|
|
4215
|
+
{ id: "devious-swindler", name: "Devious Swindler", source: "covenant" },
|
|
4216
|
+
{ id: "relentless-warden", name: "Relentless Warden", source: "covenant" },
|
|
4217
|
+
{ id: "reverent-astromancer", name: "Reverent Astromancer", source: "covenant" },
|
|
4218
|
+
{ id: "undaunted-aegis", name: "Undaunted Aegis", source: "covenant" },
|
|
4219
|
+
// Expeditions (4, unreleased — provisional)
|
|
4220
|
+
{ id: "jocular-druid", name: "Jocular Druid", source: "expeditions" },
|
|
4221
|
+
{ id: "grizzled-mariner", name: "Grizzled Mariner", source: "expeditions" },
|
|
4222
|
+
{ id: "clever-tinkerer", name: "Clever Tinkerer", source: "expeditions" },
|
|
4223
|
+
{ id: "enlightened-ascetic", name: "Enlightened Ascetic", source: "expeditions" }
|
|
4197
4224
|
];
|
|
4198
|
-
var
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
var
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
}
|
|
4217
|
-
|
|
4218
|
-
}
|
|
4219
|
-
|
|
4220
|
-
const c = VALUE_TO_CHAR.get(v);
|
|
4221
|
-
if (c === void 0) {
|
|
4222
|
-
throw new Error(`Invalid seed value: ${v} (must be 0\u2013${BASE - 1})`);
|
|
4223
|
-
}
|
|
4224
|
-
return c;
|
|
4225
|
-
}
|
|
4226
|
-
function validateSeed(seed) {
|
|
4227
|
-
const stripped = seed.replace(/[-\s]/g, "").toLowerCase();
|
|
4228
|
-
if (stripped.length !== SEED_LENGTH) {
|
|
4229
|
-
throw new Error(`Invalid seed length: expected ${SEED_LENGTH} characters, got ${stripped.length}`);
|
|
4230
|
-
}
|
|
4231
|
-
for (const c of stripped) {
|
|
4232
|
-
if (!CHAR_TO_VALUE.has(c)) {
|
|
4233
|
-
throw new Error(`Invalid seed character: '${c}'`);
|
|
4234
|
-
}
|
|
4235
|
-
}
|
|
4236
|
-
const upper = stripped.toUpperCase();
|
|
4237
|
-
return `${upper.slice(0, 4)}-${upper.slice(4, 8)}-${upper.slice(8, 12)}`;
|
|
4238
|
-
}
|
|
4239
|
-
function decodeSeed(seed) {
|
|
4240
|
-
const normalized = validateSeed(seed);
|
|
4241
|
-
const stripped = normalized.replace(/-/g, "").toLowerCase();
|
|
4242
|
-
const setup = [];
|
|
4243
|
-
for (let i = 0; i < SETUP_LENGTH; i++) {
|
|
4244
|
-
setup.push(charToValue(stripped[i]));
|
|
4245
|
-
}
|
|
4246
|
-
let rngSeed = 0;
|
|
4247
|
-
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
4248
|
-
const value = charToValue(stripped[SETUP_LENGTH + i]);
|
|
4249
|
-
rngSeed += value * Math.round(Math.pow(BASE, i));
|
|
4250
|
-
}
|
|
4251
|
-
const foeByteA = setup[0];
|
|
4252
|
-
const tier1 = foeByteA & 3;
|
|
4253
|
-
const tier2 = (foeByteA & 12) >> 2;
|
|
4254
|
-
const foeByteB = setup[1];
|
|
4255
|
-
const tier3 = (foeByteA & 16) >> 4 | (foeByteB & 16) >> 3;
|
|
4256
|
-
const adversaryIndex = foeByteB & 15;
|
|
4257
|
-
const allyIndex = setup[2];
|
|
4258
|
-
const extra = setup[3];
|
|
4259
|
-
const difficultyIndex = extra & 1;
|
|
4260
|
-
const expansionBits = (extra & 6) >> 1;
|
|
4261
|
-
const sourceBits = (extra & 8) >> 2;
|
|
4262
|
-
const playerCount = (setup[5] & 3) + 1;
|
|
4263
|
-
const expansions = [];
|
|
4264
|
-
if (expansionBits & 1) expansions.push("Monuments");
|
|
4265
|
-
if (expansionBits & 2) expansions.push("Alliances");
|
|
4266
|
-
let source;
|
|
4267
|
-
switch (sourceBits) {
|
|
4268
|
-
case 2:
|
|
4269
|
-
source = "Competitive";
|
|
4270
|
-
break;
|
|
4271
|
-
default:
|
|
4272
|
-
source = "Core";
|
|
4273
|
-
break;
|
|
4274
|
-
}
|
|
4275
|
-
const seedBank = {
|
|
4276
|
-
initializationSeed: rngSeed,
|
|
4277
|
-
questSeed: rngSeed - 1,
|
|
4278
|
-
seedString: normalized
|
|
4279
|
-
};
|
|
4280
|
-
return {
|
|
4281
|
-
seed: normalized,
|
|
4282
|
-
tier1Foe: TIER1_FOES[tier1],
|
|
4283
|
-
tier2Foe: TIER2_FOES[tier2],
|
|
4284
|
-
tier3Foe: TIER3_FOES[tier3],
|
|
4285
|
-
adversary: ADVERSARIES[adversaryIndex],
|
|
4286
|
-
ally: ALLIES[allyIndex],
|
|
4287
|
-
difficulty: DIFFICULTIES[difficultyIndex],
|
|
4288
|
-
source,
|
|
4289
|
-
expansions,
|
|
4290
|
-
playerCount,
|
|
4291
|
-
rngSeed,
|
|
4292
|
-
seedBank,
|
|
4293
|
-
setup
|
|
4294
|
-
};
|
|
4295
|
-
}
|
|
4296
|
-
function decodeRngSeed(seed) {
|
|
4297
|
-
const normalized = validateSeed(seed);
|
|
4298
|
-
const stripped = normalized.replace(/-/g, "").toLowerCase();
|
|
4299
|
-
let rngSeed = 0;
|
|
4300
|
-
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
4301
|
-
const value = charToValue(stripped[SETUP_LENGTH + i]);
|
|
4302
|
-
rngSeed += value * Math.round(Math.pow(BASE, i));
|
|
4303
|
-
}
|
|
4304
|
-
return rngSeed;
|
|
4305
|
-
}
|
|
4306
|
-
function createSeed(config) {
|
|
4307
|
-
let foeByteA = 0;
|
|
4308
|
-
let foeByteB = 0;
|
|
4309
|
-
const tier1Index = TIER1_FOES.indexOf(config.foes[0]);
|
|
4310
|
-
const tier2Index = TIER2_FOES.indexOf(config.foes[1]);
|
|
4311
|
-
const tier3Index = TIER3_FOES.indexOf(config.foes[2]);
|
|
4312
|
-
if (tier1Index < 0) throw new Error(`Invalid Tier 1 foe: ${config.foes[0]}`);
|
|
4313
|
-
if (tier2Index < 0) throw new Error(`Invalid Tier 2 foe: ${config.foes[1]}`);
|
|
4314
|
-
if (tier3Index < 0) throw new Error(`Invalid Tier 3 foe: ${config.foes[2]}`);
|
|
4315
|
-
foeByteA = tier1Index & 3;
|
|
4316
|
-
foeByteA |= (tier2Index & 3) << 2;
|
|
4317
|
-
foeByteA |= (tier3Index & 1) << 4;
|
|
4318
|
-
foeByteB |= (tier3Index >> 1 & 1) << 4;
|
|
4319
|
-
const adversaryIndex = ADVERSARIES.indexOf(config.adversary);
|
|
4320
|
-
if (adversaryIndex < 0) throw new Error(`Invalid adversary: ${config.adversary}`);
|
|
4321
|
-
foeByteB |= adversaryIndex & 15;
|
|
4322
|
-
const allyIndex = ALLIES.indexOf(config.ally);
|
|
4323
|
-
if (allyIndex < 0) throw new Error(`Invalid ally: ${config.ally}`);
|
|
4324
|
-
let extraByte = 0;
|
|
4325
|
-
if (config.difficulty === "Gritty") extraByte |= 1;
|
|
4326
|
-
for (const expansion of config.expansions) {
|
|
4327
|
-
switch (expansion) {
|
|
4328
|
-
case "Monuments":
|
|
4329
|
-
extraByte |= 2;
|
|
4330
|
-
break;
|
|
4331
|
-
case "Alliances":
|
|
4332
|
-
extraByte |= 4;
|
|
4333
|
-
break;
|
|
4334
|
-
}
|
|
4335
|
-
}
|
|
4336
|
-
if (config.source === "Competitive") extraByte |= 8;
|
|
4337
|
-
const versionByte = 0;
|
|
4338
|
-
const playerCountByte = Math.max(0, Math.min(3, config.playerCount - 1));
|
|
4339
|
-
let seedStr = valueToChar(foeByteA) + valueToChar(foeByteB) + valueToChar(allyIndex) + valueToChar(extraByte) + valueToChar(versionByte) + valueToChar(playerCountByte);
|
|
4340
|
-
let rngValue = 0;
|
|
4341
|
-
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
4342
|
-
const value = Math.floor(Math.random() * BASE);
|
|
4343
|
-
seedStr += valueToChar(value);
|
|
4344
|
-
rngValue += value * Math.round(Math.pow(BASE, i));
|
|
4345
|
-
}
|
|
4346
|
-
const upper = seedStr.toUpperCase();
|
|
4347
|
-
const formatted = `${upper.slice(0, 4)}-${upper.slice(4, 8)}-${upper.slice(8, 12)}`;
|
|
4348
|
-
return { seed: formatted, rngValue };
|
|
4349
|
-
}
|
|
4350
|
-
function encodeSeed(config, rngValue) {
|
|
4351
|
-
let foeByteA = 0;
|
|
4352
|
-
let foeByteB = 0;
|
|
4353
|
-
const tier1Index = TIER1_FOES.indexOf(config.foes[0]);
|
|
4354
|
-
const tier2Index = TIER2_FOES.indexOf(config.foes[1]);
|
|
4355
|
-
const tier3Index = TIER3_FOES.indexOf(config.foes[2]);
|
|
4356
|
-
if (tier1Index < 0) throw new Error(`Invalid Tier 1 foe: ${config.foes[0]}`);
|
|
4357
|
-
if (tier2Index < 0) throw new Error(`Invalid Tier 2 foe: ${config.foes[1]}`);
|
|
4358
|
-
if (tier3Index < 0) throw new Error(`Invalid Tier 3 foe: ${config.foes[2]}`);
|
|
4359
|
-
foeByteA = tier1Index & 3;
|
|
4360
|
-
foeByteA |= (tier2Index & 3) << 2;
|
|
4361
|
-
foeByteA |= (tier3Index & 1) << 4;
|
|
4362
|
-
foeByteB |= (tier3Index >> 1 & 1) << 4;
|
|
4363
|
-
const adversaryIndex = ADVERSARIES.indexOf(config.adversary);
|
|
4364
|
-
if (adversaryIndex < 0) throw new Error(`Invalid adversary: ${config.adversary}`);
|
|
4365
|
-
foeByteB |= adversaryIndex & 15;
|
|
4366
|
-
const allyIndex = ALLIES.indexOf(config.ally);
|
|
4367
|
-
if (allyIndex < 0) throw new Error(`Invalid ally: ${config.ally}`);
|
|
4368
|
-
let extraByte = 0;
|
|
4369
|
-
if (config.difficulty === "Gritty") extraByte |= 1;
|
|
4370
|
-
for (const expansion of config.expansions) {
|
|
4371
|
-
switch (expansion) {
|
|
4372
|
-
case "Monuments":
|
|
4373
|
-
extraByte |= 2;
|
|
4374
|
-
break;
|
|
4375
|
-
case "Alliances":
|
|
4376
|
-
extraByte |= 4;
|
|
4377
|
-
break;
|
|
4378
|
-
}
|
|
4379
|
-
}
|
|
4380
|
-
if (config.source === "Competitive") extraByte |= 8;
|
|
4381
|
-
const versionByte = 0;
|
|
4382
|
-
const playerCountByte = Math.max(0, Math.min(3, config.playerCount - 1));
|
|
4383
|
-
let seedStr = valueToChar(foeByteA) + valueToChar(foeByteB) + valueToChar(allyIndex) + valueToChar(extraByte) + valueToChar(versionByte) + valueToChar(playerCountByte);
|
|
4384
|
-
let remaining = rngValue;
|
|
4385
|
-
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
4386
|
-
const digit = remaining % BASE;
|
|
4387
|
-
seedStr += valueToChar(digit);
|
|
4388
|
-
remaining = Math.floor(remaining / BASE);
|
|
4389
|
-
}
|
|
4390
|
-
const upper = seedStr.toUpperCase();
|
|
4391
|
-
return `${upper.slice(0, 4)}-${upper.slice(4, 8)}-${upper.slice(8, 12)}`;
|
|
4392
|
-
}
|
|
4393
|
-
function compareSeedsRaw(seed1, seed2) {
|
|
4394
|
-
const n1 = validateSeed(seed1);
|
|
4395
|
-
const n2 = validateSeed(seed2);
|
|
4396
|
-
const s1 = n1.replace(/-/g, "").toLowerCase();
|
|
4397
|
-
const s2 = n2.replace(/-/g, "").toLowerCase();
|
|
4398
|
-
const diffs = [];
|
|
4399
|
-
for (let i = 0; i < SEED_LENGTH; i++) {
|
|
4400
|
-
const v1 = charToValue(s1[i]);
|
|
4401
|
-
const v2 = charToValue(s2[i]);
|
|
4402
|
-
if (v1 !== v2) {
|
|
4403
|
-
diffs.push({
|
|
4404
|
-
charIndex: i,
|
|
4405
|
-
value1: v1,
|
|
4406
|
-
value2: v2,
|
|
4407
|
-
char1: s1[i],
|
|
4408
|
-
char2: s2[i]
|
|
4409
|
-
});
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
return {
|
|
4413
|
-
seed1: n1,
|
|
4414
|
-
seed2: n2,
|
|
4415
|
-
diffs,
|
|
4416
|
-
setupDiffs: diffs.filter((d) => d.charIndex < SETUP_LENGTH),
|
|
4417
|
-
rngDiffs: diffs.filter((d) => d.charIndex >= SETUP_LENGTH)
|
|
4418
|
-
};
|
|
4419
|
-
}
|
|
4420
|
-
var SETUP_FIELD_LABELS = {
|
|
4421
|
-
0: "Tier1/Tier2/Tier3lo",
|
|
4422
|
-
1: "Adversary/Tier3hi",
|
|
4423
|
-
2: "Ally",
|
|
4424
|
-
3: "Difficulty/Expansions/Source",
|
|
4425
|
-
4: "Version",
|
|
4426
|
-
5: "PlayerCount"
|
|
4427
|
-
};
|
|
4428
|
-
function dumpSeedChars(seed) {
|
|
4429
|
-
const normalized = validateSeed(seed);
|
|
4430
|
-
const stripped = normalized.replace(/-/g, "").toLowerCase();
|
|
4431
|
-
const chars = [];
|
|
4432
|
-
for (let i = 0; i < SEED_LENGTH; i++) {
|
|
4433
|
-
const isSetup = i < SETUP_LENGTH;
|
|
4434
|
-
chars.push({
|
|
4435
|
-
index: i,
|
|
4436
|
-
char: stripped[i],
|
|
4437
|
-
value: charToValue(stripped[i]),
|
|
4438
|
-
section: isSetup ? "setup" : "rng",
|
|
4439
|
-
field: isSetup ? SETUP_FIELD_LABELS[i] : void 0
|
|
4440
|
-
});
|
|
4441
|
-
}
|
|
4442
|
-
return { seed: normalized, chars };
|
|
4443
|
-
}
|
|
4444
|
-
|
|
4445
|
-
// src/udtHeroes.ts
|
|
4446
|
-
var HEROES = [
|
|
4447
|
-
// Base (4)
|
|
4448
|
-
{ id: "brutal-warlord", name: "Brutal Warlord", source: "base" },
|
|
4449
|
-
{ id: "orphaned-scion", name: "Orphaned Scion", source: "base" },
|
|
4450
|
-
{ id: "relic-hunter", name: "Relic Hunter", source: "base" },
|
|
4451
|
-
{ id: "spymaster", name: "Spymaster", source: "base" },
|
|
4452
|
-
// Alliances (2)
|
|
4453
|
-
{ id: "archwright", name: "Archwright", source: "alliances" },
|
|
4454
|
-
{ id: "haunted-recluse", name: "Haunted Recluse", source: "alliances" },
|
|
4455
|
-
// Covenant (4)
|
|
4456
|
-
{ id: "devious-swindler", name: "Devious Swindler", source: "covenant" },
|
|
4457
|
-
{ id: "relentless-warden", name: "Relentless Warden", source: "covenant" },
|
|
4458
|
-
{ id: "reverent-astromancer", name: "Reverent Astromancer", source: "covenant" },
|
|
4459
|
-
{ id: "undaunted-aegis", name: "Undaunted Aegis", source: "covenant" },
|
|
4460
|
-
// Expeditions (4, unreleased — provisional)
|
|
4461
|
-
{ id: "jocular-druid", name: "Jocular Druid", source: "expeditions" },
|
|
4462
|
-
{ id: "grizzled-mariner", name: "Grizzled Mariner", source: "expeditions" },
|
|
4463
|
-
{ id: "clever-tinkerer", name: "Clever Tinkerer", source: "expeditions" },
|
|
4464
|
-
{ id: "enlightened-ascetic", name: "Enlightened Ascetic", source: "expeditions" }
|
|
4465
|
-
];
|
|
4466
|
-
var HERO_BY_ID = Object.freeze(
|
|
4467
|
-
HEROES.reduce((acc, hero) => {
|
|
4468
|
-
acc[hero.id] = hero;
|
|
4469
|
-
return acc;
|
|
4470
|
-
}, {})
|
|
4471
|
-
);
|
|
4472
|
-
|
|
4473
|
-
// src/udtMonuments.ts
|
|
4474
|
-
var MONUMENTS = [
|
|
4475
|
-
{ id: "arch-of-the-golden-sun", name: "Arch of the Golden Sun", source: "covenant" },
|
|
4476
|
-
{ id: "argent-oak", name: "Argent Oak", source: "covenant" },
|
|
4477
|
-
{ id: "cenotaph-of-the-first-prophet", name: "Cenotaph of the First Prophet", source: "covenant" },
|
|
4478
|
-
{ id: "colossus-of-bjorn", name: "Colossus of Bjorn", source: "covenant" },
|
|
4479
|
-
{ id: "endless-necropolis", name: "Endless Necropolis", source: "covenant" },
|
|
4480
|
-
{ id: "moonstone-temple", name: "Moonstone Temple", source: "covenant" },
|
|
4481
|
-
{ id: "nightmare-cage", name: "Nightmare Cage", source: "covenant" },
|
|
4482
|
-
{ id: "tower-shard", name: "Tower Shard", source: "covenant" }
|
|
4225
|
+
var HERO_BY_ID = Object.freeze(
|
|
4226
|
+
HEROES.reduce((acc, hero) => {
|
|
4227
|
+
acc[hero.id] = hero;
|
|
4228
|
+
return acc;
|
|
4229
|
+
}, {})
|
|
4230
|
+
);
|
|
4231
|
+
|
|
4232
|
+
// src/data/udtMonuments.ts
|
|
4233
|
+
var udtMonuments_exports = {};
|
|
4234
|
+
__export(udtMonuments_exports, {
|
|
4235
|
+
MONUMENTS: () => MONUMENTS,
|
|
4236
|
+
MONUMENT_BY_ID: () => MONUMENT_BY_ID
|
|
4237
|
+
});
|
|
4238
|
+
var MONUMENTS = [
|
|
4239
|
+
{ id: "arch-of-the-golden-sun", name: "Arch of the Golden Sun", source: "covenant" },
|
|
4240
|
+
{ id: "argent-oak", name: "Argent Oak", source: "covenant" },
|
|
4241
|
+
{ id: "cenotaph-of-the-first-prophet", name: "Cenotaph of the First Prophet", source: "covenant" },
|
|
4242
|
+
{ id: "colossus-of-bjorn", name: "Colossus of Bjorn", source: "covenant" },
|
|
4243
|
+
{ id: "endless-necropolis", name: "Endless Necropolis", source: "covenant" },
|
|
4244
|
+
{ id: "moonstone-temple", name: "Moonstone Temple", source: "covenant" },
|
|
4245
|
+
{ id: "nightmare-cage", name: "Nightmare Cage", source: "covenant" },
|
|
4246
|
+
{ id: "tower-shard", name: "Tower Shard", source: "covenant" }
|
|
4483
4247
|
];
|
|
4484
4248
|
var MONUMENT_BY_ID = Object.freeze(
|
|
4485
4249
|
MONUMENTS.reduce((acc, monument) => {
|
|
@@ -4488,7 +4252,16 @@ var MONUMENT_BY_ID = Object.freeze(
|
|
|
4488
4252
|
}, {})
|
|
4489
4253
|
);
|
|
4490
4254
|
|
|
4491
|
-
// src/udtFoes.ts
|
|
4255
|
+
// src/data/udtFoes.ts
|
|
4256
|
+
var udtFoes_exports = {};
|
|
4257
|
+
__export(udtFoes_exports, {
|
|
4258
|
+
ADVERSARY_ROSTER: () => ADVERSARY_ROSTER,
|
|
4259
|
+
ALL_FOES: () => ALL_FOES,
|
|
4260
|
+
FOES: () => FOES,
|
|
4261
|
+
FOE_BY_ID: () => FOE_BY_ID,
|
|
4262
|
+
FOE_BY_NAME: () => FOE_BY_NAME,
|
|
4263
|
+
FOE_STATUSES: () => FOE_STATUSES
|
|
4264
|
+
});
|
|
4492
4265
|
var FOE_STATUSES = ["panicked", "unsteady", "ready", "savage", "lethal"];
|
|
4493
4266
|
var FOES = [
|
|
4494
4267
|
// Tier 1 — level 2
|
|
@@ -4531,120 +4304,21 @@ var FOE_BY_NAME = Object.freeze(
|
|
|
4531
4304
|
}, {})
|
|
4532
4305
|
);
|
|
4533
4306
|
|
|
4534
|
-
// src/
|
|
4535
|
-
var
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
this.inext = 0;
|
|
4548
|
-
this.inextp = 0;
|
|
4549
|
-
this.initialize(seed);
|
|
4550
|
-
}
|
|
4551
|
-
/**
|
|
4552
|
-
* Replicate .NET's System.Random constructor seeding algorithm.
|
|
4553
|
-
*/
|
|
4554
|
-
initialize(seed) {
|
|
4555
|
-
let subtraction;
|
|
4556
|
-
if (seed === -2147483648) {
|
|
4557
|
-
subtraction = INT32_MAX;
|
|
4558
|
-
} else {
|
|
4559
|
-
subtraction = Math.abs(seed);
|
|
4560
|
-
}
|
|
4561
|
-
let mj = toInt32(MSEED - subtraction);
|
|
4562
|
-
this.seedArray[55] = mj;
|
|
4563
|
-
let mk = 1;
|
|
4564
|
-
for (let i = 1; i < 55; i++) {
|
|
4565
|
-
const ii = 21 * i % 55;
|
|
4566
|
-
this.seedArray[ii] = mk;
|
|
4567
|
-
mk = toInt32(mj - mk);
|
|
4568
|
-
if (mk < 0) mk = toInt32(mk + INT32_MAX);
|
|
4569
|
-
mj = this.seedArray[ii];
|
|
4570
|
-
}
|
|
4571
|
-
for (let k = 1; k < 5; k++) {
|
|
4572
|
-
for (let i = 1; i < 56; i++) {
|
|
4573
|
-
this.seedArray[i] = toInt32(this.seedArray[i] - this.seedArray[1 + (i + 30) % 55]);
|
|
4574
|
-
if (this.seedArray[i] < 0) {
|
|
4575
|
-
this.seedArray[i] = toInt32(this.seedArray[i] + INT32_MAX);
|
|
4576
|
-
}
|
|
4577
|
-
}
|
|
4578
|
-
}
|
|
4579
|
-
this.inext = 0;
|
|
4580
|
-
this.inextp = 21;
|
|
4581
|
-
}
|
|
4582
|
-
/**
|
|
4583
|
-
* Internal sample — returns value in range [0, Int32.MaxValue).
|
|
4584
|
-
* Matches C#'s InternalSample().
|
|
4585
|
-
*/
|
|
4586
|
-
internalSample() {
|
|
4587
|
-
let retVal;
|
|
4588
|
-
let locINext = this.inext;
|
|
4589
|
-
let locINextp = this.inextp;
|
|
4590
|
-
if (++locINext >= 56) locINext = 1;
|
|
4591
|
-
if (++locINextp >= 56) locINextp = 1;
|
|
4592
|
-
retVal = toInt32(this.seedArray[locINext] - this.seedArray[locINextp]);
|
|
4593
|
-
if (retVal === INT32_MAX) retVal--;
|
|
4594
|
-
if (retVal < 0) retVal = toInt32(retVal + INT32_MAX);
|
|
4595
|
-
this.seedArray[locINext] = retVal;
|
|
4596
|
-
this.inext = locINext;
|
|
4597
|
-
this.inextp = locINextp;
|
|
4598
|
-
return retVal;
|
|
4599
|
-
}
|
|
4600
|
-
/**
|
|
4601
|
-
* Sample — returns a double in range [0.0, 1.0).
|
|
4602
|
-
* Matches C#'s Sample().
|
|
4603
|
-
*/
|
|
4604
|
-
sample() {
|
|
4605
|
-
return this.internalSample() * (1 / INT32_MAX);
|
|
4606
|
-
}
|
|
4607
|
-
/**
|
|
4608
|
-
* Returns a non-negative random integer less than Int32.MaxValue.
|
|
4609
|
-
* Matches C# `Random.Next()`.
|
|
4610
|
-
*/
|
|
4611
|
-
next() {
|
|
4612
|
-
return this.internalSample();
|
|
4613
|
-
}
|
|
4614
|
-
/**
|
|
4615
|
-
* Returns a non-negative random integer less than maxValue.
|
|
4616
|
-
* Matches C# `Random.Next(maxValue)`.
|
|
4617
|
-
*/
|
|
4618
|
-
nextMax(maxValue) {
|
|
4619
|
-
if (maxValue < 0) {
|
|
4620
|
-
throw new Error("maxValue must be non-negative");
|
|
4621
|
-
}
|
|
4622
|
-
return toInt32(this.sample() * maxValue);
|
|
4623
|
-
}
|
|
4624
|
-
/**
|
|
4625
|
-
* Returns a random integer in range [minValue, maxValue).
|
|
4626
|
-
* Matches C# `Random.Next(minValue, maxValue)`.
|
|
4627
|
-
*/
|
|
4628
|
-
nextRange(minValue, maxValue) {
|
|
4629
|
-
if (minValue > maxValue) {
|
|
4630
|
-
throw new Error("minValue must be less than or equal to maxValue");
|
|
4631
|
-
}
|
|
4632
|
-
const range = maxValue - minValue;
|
|
4633
|
-
if (range <= INT32_MAX) {
|
|
4634
|
-
return toInt32(this.sample() * range) + minValue;
|
|
4635
|
-
}
|
|
4636
|
-
return toInt32(this.internalSample() * (1 / INT32_MAX) * range) + minValue;
|
|
4637
|
-
}
|
|
4638
|
-
/**
|
|
4639
|
-
* Returns a random double in range [0.0, 1.0).
|
|
4640
|
-
* Matches C# `Random.NextDouble()`.
|
|
4641
|
-
*/
|
|
4642
|
-
nextDouble() {
|
|
4643
|
-
return this.sample();
|
|
4644
|
-
}
|
|
4645
|
-
};
|
|
4307
|
+
// src/data/board/index.ts
|
|
4308
|
+
var board_exports = {};
|
|
4309
|
+
__export(board_exports, {
|
|
4310
|
+
BOARD_ADJACENCY: () => BOARD_ADJACENCY,
|
|
4311
|
+
BOARD_ANCHORS: () => BOARD_ANCHORS,
|
|
4312
|
+
BOARD_GROUPINGS: () => BOARD_GROUPINGS,
|
|
4313
|
+
BOARD_IMAGE_INFO: () => BOARD_IMAGE_INFO,
|
|
4314
|
+
BOARD_LOCATIONS: () => BOARD_LOCATIONS,
|
|
4315
|
+
BOARD_LOCATION_BY_NAME: () => BOARD_LOCATION_BY_NAME,
|
|
4316
|
+
neighborsOf: () => neighborsOf,
|
|
4317
|
+
shortestPath: () => shortestPath,
|
|
4318
|
+
stepDistance: () => stepDistance
|
|
4319
|
+
});
|
|
4646
4320
|
|
|
4647
|
-
// src/udtGameBoard.ts
|
|
4321
|
+
// src/data/board/udtGameBoard.ts
|
|
4648
4322
|
var BOARD_GROUPINGS = {
|
|
4649
4323
|
/** Dayside and Fivepint (North kingdom lakes). */
|
|
4650
4324
|
LONG_WATER: "Long Water",
|
|
@@ -4721,7 +4395,7 @@ var BOARD_LOCATIONS = [
|
|
|
4721
4395
|
];
|
|
4722
4396
|
var BOARD_LOCATION_BY_NAME = Object.fromEntries(BOARD_LOCATIONS.map((loc) => [loc.name, loc]));
|
|
4723
4397
|
|
|
4724
|
-
// src/udtBoardAnchors.ts
|
|
4398
|
+
// src/data/board/udtBoardAnchors.ts
|
|
4725
4399
|
var BOARD_IMAGE_INFO = {
|
|
4726
4400
|
width: 4096,
|
|
4727
4401
|
height: 4096,
|
|
@@ -5065,7 +4739,7 @@ var BOARD_ANCHORS = {
|
|
|
5065
4739
|
}
|
|
5066
4740
|
};
|
|
5067
4741
|
|
|
5068
|
-
// src/udtBoardAdjacency.ts
|
|
4742
|
+
// src/data/board/udtBoardAdjacency.ts
|
|
5069
4743
|
var BOARD_ADJACENCY = {
|
|
5070
4744
|
"Howling Desert": [
|
|
5071
4745
|
"Azkol's Bane",
|
|
@@ -5431,21 +5105,1413 @@ function shortestPath(a, b) {
|
|
|
5431
5105
|
return [];
|
|
5432
5106
|
}
|
|
5433
5107
|
|
|
5108
|
+
// src/data/udtGameContent.ts
|
|
5109
|
+
var udtGameContent_exports = {};
|
|
5110
|
+
__export(udtGameContent_exports, {
|
|
5111
|
+
ADVERSARIES: () => ADVERSARIES,
|
|
5112
|
+
COMPANIONS: () => COMPANIONS,
|
|
5113
|
+
FOES: () => FOES2,
|
|
5114
|
+
HEROES: () => HEROES2,
|
|
5115
|
+
KINGDOM_VIRTUES: () => KINGDOM_VIRTUES,
|
|
5116
|
+
adversaries: () => adversaries,
|
|
5117
|
+
companions: () => companions,
|
|
5118
|
+
foes: () => foes,
|
|
5119
|
+
heroes: () => heroes,
|
|
5120
|
+
kingdomVirtues: () => kingdomVirtues
|
|
5121
|
+
});
|
|
5122
|
+
var HEROES2 = {
|
|
5123
|
+
Spymaster: {
|
|
5124
|
+
name: "Spymaster",
|
|
5125
|
+
expansion: "Base Game",
|
|
5126
|
+
bannerAction: "Place your hero on any space in your current kingdom.",
|
|
5127
|
+
defaultVirtues: [
|
|
5128
|
+
{ name: "Alert", ability: "+1 Stealth Advantage." },
|
|
5129
|
+
{ name: "Swift", ability: "Your base move is 4." }
|
|
5130
|
+
],
|
|
5131
|
+
unlockableVirtues: [
|
|
5132
|
+
{ name: "Resourceful", ability: "At the end of each month, gain 15 warriors." },
|
|
5133
|
+
{
|
|
5134
|
+
name: "Fortunate",
|
|
5135
|
+
ability: "You may Reinforce twice per turn at the same building."
|
|
5136
|
+
},
|
|
5137
|
+
{
|
|
5138
|
+
name: "Unseen",
|
|
5139
|
+
ability: "When you complete a monthly quest, you may remove a foe instead of gaining spirit."
|
|
5140
|
+
}
|
|
5141
|
+
]
|
|
5142
|
+
},
|
|
5143
|
+
"Brutal Warlord": {
|
|
5144
|
+
name: "Brutal Warlord",
|
|
5145
|
+
expansion: "Base Game",
|
|
5146
|
+
bannerAction: "Gain 5 warriors.",
|
|
5147
|
+
defaultVirtues: [
|
|
5148
|
+
{ name: "Baleful", ability: "+1 Melee Advantage." },
|
|
5149
|
+
{ name: "Veteran", ability: "+1 Wild Advantage when you Battle." }
|
|
5150
|
+
],
|
|
5151
|
+
unlockableVirtues: [
|
|
5152
|
+
{ name: "Inspiring", ability: "After you Reinforce, also gain 6 warriors." },
|
|
5153
|
+
{
|
|
5154
|
+
name: "Callous",
|
|
5155
|
+
ability: "After you Battle, if you lost at least 10 warriors, gain a treasure from the market."
|
|
5156
|
+
},
|
|
5157
|
+
{ name: "Relentless", ability: "If you double your move, gain +1 Wild Advantage." }
|
|
5158
|
+
]
|
|
5159
|
+
},
|
|
5160
|
+
"Orphaned Scion": {
|
|
5161
|
+
name: "Orphaned Scion",
|
|
5162
|
+
expansion: "Base Game",
|
|
5163
|
+
bannerAction: "Gain 1 spirit.",
|
|
5164
|
+
defaultVirtues: [
|
|
5165
|
+
{ name: "Arcane", ability: "+1 Magic Advantage." },
|
|
5166
|
+
{ name: "Generous", ability: "After you Cleanse, remove 1 skull from any building." }
|
|
5167
|
+
],
|
|
5168
|
+
unlockableVirtues: [
|
|
5169
|
+
{
|
|
5170
|
+
name: "Infused",
|
|
5171
|
+
ability: "At the start of your turn, remove 1 skull from a building in your home kingdom."
|
|
5172
|
+
},
|
|
5173
|
+
{
|
|
5174
|
+
name: "Blessed",
|
|
5175
|
+
ability: "Spend 1 spirit to prevent up to 6 warrior losses from a battle card or dungeon room."
|
|
5176
|
+
},
|
|
5177
|
+
{
|
|
5178
|
+
name: "Anointed",
|
|
5179
|
+
ability: "+1 Wild Advantage for each building with no skulls on or adjacent to your space."
|
|
5180
|
+
}
|
|
5181
|
+
]
|
|
5182
|
+
},
|
|
5183
|
+
"Relic Hunter": {
|
|
5184
|
+
name: "Relic Hunter",
|
|
5185
|
+
expansion: "Base Game",
|
|
5186
|
+
bannerAction: "Gain 1 potion.",
|
|
5187
|
+
defaultVirtues: [
|
|
5188
|
+
{ name: "Precise", ability: "+1 Humanoid Advantage." },
|
|
5189
|
+
{
|
|
5190
|
+
name: "Prepared",
|
|
5191
|
+
ability: "When you Reinforce at a bazaar, spend 1 less spirit to gain a treasure."
|
|
5192
|
+
}
|
|
5193
|
+
],
|
|
5194
|
+
unlockableVirtues: [
|
|
5195
|
+
{ name: "Crafty", ability: "When you spend a potion, double the number on it." },
|
|
5196
|
+
{
|
|
5197
|
+
name: "Lucky",
|
|
5198
|
+
ability: "When you spend (not lose) a treasure, gain the top card of the treasure deck."
|
|
5199
|
+
},
|
|
5200
|
+
{ name: "Inventive", ability: "Spend 4 potions to remove a foe from your space." }
|
|
5201
|
+
]
|
|
5202
|
+
},
|
|
5203
|
+
"Haunted Recluse": {
|
|
5204
|
+
name: "Haunted Recluse",
|
|
5205
|
+
expansion: "Alliances",
|
|
5206
|
+
bannerAction: "Move 1 skull from any building to any other building with 2 or fewer skulls.",
|
|
5207
|
+
defaultVirtues: [
|
|
5208
|
+
{ name: "Spiritreaver", ability: "+1 Undead Advantage." },
|
|
5209
|
+
{
|
|
5210
|
+
name: "Skullweaver",
|
|
5211
|
+
ability: "When a skull emerges in your home kingdom, you can place it on any building with 2 or fewer skulls in any kingdom."
|
|
5212
|
+
}
|
|
5213
|
+
],
|
|
5214
|
+
unlockableVirtues: [
|
|
5215
|
+
{
|
|
5216
|
+
name: "Shadowspinner",
|
|
5217
|
+
ability: "+1 Wild Advantage for each building with skulls on or adjacent to your space."
|
|
5218
|
+
},
|
|
5219
|
+
{
|
|
5220
|
+
name: "Soulreaper",
|
|
5221
|
+
ability: "Prevent up to 2 warrior losses per battle card for each skull on or adjacent to your space."
|
|
5222
|
+
},
|
|
5223
|
+
{
|
|
5224
|
+
name: "Sinbearer",
|
|
5225
|
+
ability: "At the end of the month you can spend up to 12 warriors to remove all skulls from your current kingdom."
|
|
5226
|
+
}
|
|
5227
|
+
]
|
|
5228
|
+
},
|
|
5229
|
+
Archwright: {
|
|
5230
|
+
name: "Archwright",
|
|
5231
|
+
expansion: "Alliances",
|
|
5232
|
+
bannerAction: "Place a battlement on any space or move a battlement up to 2 spaces.",
|
|
5233
|
+
defaultVirtues: [
|
|
5234
|
+
{ name: "Innovative", ability: "+1 Beast Advantage." },
|
|
5235
|
+
{ name: "Clever", ability: "Battlements give you +2 Wild Advantages (instead of +1)." }
|
|
5236
|
+
],
|
|
5237
|
+
unlockableVirtues: [
|
|
5238
|
+
{
|
|
5239
|
+
name: "Tactical",
|
|
5240
|
+
ability: "While on a battlement, you can Battle a foe on an adjacent space. (Terrain advantages use the space you are on.)"
|
|
5241
|
+
},
|
|
5242
|
+
{
|
|
5243
|
+
name: "Wily",
|
|
5244
|
+
ability: "Battlements give you advantages when you Quest (in addition to when you Battle)."
|
|
5245
|
+
},
|
|
5246
|
+
{
|
|
5247
|
+
name: "Exalted",
|
|
5248
|
+
ability: "While on a battlement, you may Cleanse to remove skulls from all adjacent buildings."
|
|
5249
|
+
}
|
|
5250
|
+
]
|
|
5251
|
+
},
|
|
5252
|
+
"Relentless Warden": {
|
|
5253
|
+
name: "Relentless Warden",
|
|
5254
|
+
expansion: "Covenant",
|
|
5255
|
+
bannerAction: "Place quarry token on a foe if it is not already, else move quarry token up to 2 spaces.",
|
|
5256
|
+
defaultVirtues: [
|
|
5257
|
+
{ name: "Perceptive", ability: "+1 Wild Advantage vs. your quarry." },
|
|
5258
|
+
{
|
|
5259
|
+
name: "Guarded",
|
|
5260
|
+
ability: "Prevent up to 3 warrior losses per battle card when you Battle your quarry."
|
|
5261
|
+
}
|
|
5262
|
+
],
|
|
5263
|
+
unlockableVirtues: [
|
|
5264
|
+
{ name: "Keen-Eyed", ability: "+2 Wild Advantages vs. your quarry." },
|
|
5265
|
+
{
|
|
5266
|
+
name: "Instinctive",
|
|
5267
|
+
ability: "You may remove your quarry token to ignore your quarry during its strike event."
|
|
5268
|
+
},
|
|
5269
|
+
{
|
|
5270
|
+
name: "Inspiring",
|
|
5271
|
+
ability: "When you defeat your quarry, remove all skulls on or adjacent to your space."
|
|
5272
|
+
}
|
|
5273
|
+
]
|
|
5274
|
+
},
|
|
5275
|
+
"Undaunted Aegis": {
|
|
5276
|
+
name: "Undaunted Aegis",
|
|
5277
|
+
expansion: "Covenant",
|
|
5278
|
+
bannerAction: "For each corruption you have, gain 3 warriors. You may spend 10 warriors to remove one of your corruptions.",
|
|
5279
|
+
defaultVirtues: [
|
|
5280
|
+
{
|
|
5281
|
+
name: "Ascetic",
|
|
5282
|
+
ability: "Gain 1 spirit for each battle card you spend no advantages on."
|
|
5283
|
+
},
|
|
5284
|
+
{
|
|
5285
|
+
name: "Iron-Willed",
|
|
5286
|
+
ability: "You can have an additional corruption. Start the game with 1 random corruption."
|
|
5287
|
+
}
|
|
5288
|
+
],
|
|
5289
|
+
unlockableVirtues: [
|
|
5290
|
+
{ name: "Emboldened", ability: "+1 Wild Advantage for each corruption you have." },
|
|
5291
|
+
{
|
|
5292
|
+
name: "Resolute",
|
|
5293
|
+
ability: "When you Reinforce, spend 1 less spirit for each corruption you have."
|
|
5294
|
+
},
|
|
5295
|
+
{
|
|
5296
|
+
name: "Steeled",
|
|
5297
|
+
ability: "Once per turn, if another hero would gain a corruption, you may gain it instead and gain 2 spirit."
|
|
5298
|
+
}
|
|
5299
|
+
]
|
|
5300
|
+
},
|
|
5301
|
+
"Devious Swindler": {
|
|
5302
|
+
name: "Devious Swindler",
|
|
5303
|
+
expansion: "Covenant",
|
|
5304
|
+
bannerAction: "Roll the haggle die and gain the result.",
|
|
5305
|
+
defaultVirtues: [
|
|
5306
|
+
{
|
|
5307
|
+
name: "Inventive",
|
|
5308
|
+
ability: "When you Battle, gain all advantages in the treasure market."
|
|
5309
|
+
},
|
|
5310
|
+
{
|
|
5311
|
+
name: "Joyful",
|
|
5312
|
+
ability: "When you roll the haggle die, ignore the Cancelled symbol."
|
|
5313
|
+
}
|
|
5314
|
+
],
|
|
5315
|
+
unlockableVirtues: [
|
|
5316
|
+
{
|
|
5317
|
+
name: "Fortuitous",
|
|
5318
|
+
ability: "After you roll the haggle die, you may reroll once and take either result."
|
|
5319
|
+
},
|
|
5320
|
+
{
|
|
5321
|
+
name: "Opportunistic",
|
|
5322
|
+
ability: "When any player gains a treasure from the treasure market, you gain a blessing."
|
|
5323
|
+
},
|
|
5324
|
+
{
|
|
5325
|
+
name: "Calculating",
|
|
5326
|
+
ability: "You may ignore warrior and spirit losses on critical hit battle cards."
|
|
5327
|
+
}
|
|
5328
|
+
]
|
|
5329
|
+
},
|
|
5330
|
+
"Reverent Astromancer": {
|
|
5331
|
+
name: "Reverent Astromancer",
|
|
5332
|
+
expansion: "Covenant",
|
|
5333
|
+
bannerAction: "Remove a skull on or adjacent to your space.",
|
|
5334
|
+
defaultVirtues: [
|
|
5335
|
+
{
|
|
5336
|
+
name: "Well Versed",
|
|
5337
|
+
ability: "If you remove a skull with your Banner action, gain a blessing."
|
|
5338
|
+
},
|
|
5339
|
+
{
|
|
5340
|
+
name: "Pious",
|
|
5341
|
+
ability: "At the start of each month, prepare spells equal to the month number."
|
|
5342
|
+
}
|
|
5343
|
+
],
|
|
5344
|
+
unlockableVirtues: [
|
|
5345
|
+
{ name: "Exalted", ability: "You can prepare invocations." },
|
|
5346
|
+
{ name: "Zealous", ability: "Whenever you cast a spell, gain a blessing." },
|
|
5347
|
+
{
|
|
5348
|
+
name: "Bounteous",
|
|
5349
|
+
ability: "Once per turn, when you cast a spell, gain the top card of the treasure deck."
|
|
5350
|
+
}
|
|
5351
|
+
]
|
|
5352
|
+
}
|
|
5353
|
+
};
|
|
5354
|
+
var heroes = Object.values(HEROES2);
|
|
5355
|
+
var FOES2 = {
|
|
5356
|
+
Brigands: { name: "Brigands", level: 2 },
|
|
5357
|
+
Oreks: { name: "Oreks", level: 2 },
|
|
5358
|
+
"Shadow Wolves": { name: "Shadow Wolves", level: 2 },
|
|
5359
|
+
"Spine Fiend": { name: "Spine Fiend", level: 2 },
|
|
5360
|
+
"Clan Of Neuri": { name: "Clan Of Neuri", level: 3 },
|
|
5361
|
+
"Frost Troll": { name: "Frost Troll", level: 3 },
|
|
5362
|
+
Lemure: { name: "Lemure", level: 3 },
|
|
5363
|
+
"Widowmade Spider": { name: "Widowmade Spider", level: 3 },
|
|
5364
|
+
Dragon: { name: "Dragon", level: 4 },
|
|
5365
|
+
Mormo: { name: "Mormo", level: 4 },
|
|
5366
|
+
Striga: { name: "Striga", level: 4 },
|
|
5367
|
+
Titan: { name: "Titan", level: 4 }
|
|
5368
|
+
};
|
|
5369
|
+
var foes = Object.values(FOES2);
|
|
5370
|
+
var ADVERSARIES = {
|
|
5371
|
+
Ashstrider: { name: "Ashstrider", level: 5 },
|
|
5372
|
+
"Bane Of Omens": { name: "Bane Of Omens", level: 5 },
|
|
5373
|
+
"Empress Of Shades": { name: "Empress Of Shades", level: 5 },
|
|
5374
|
+
"Gaze Eternal": { name: "Gaze Eternal", level: 5 },
|
|
5375
|
+
Gravemaw: { name: "Gravemaw", level: 5 },
|
|
5376
|
+
"Isa The Exile": { name: "Isa The Exile", level: 5 },
|
|
5377
|
+
"Lingering Rot": { name: "Lingering Rot", level: 5 },
|
|
5378
|
+
"U'tuk-Ku The Ice Herald": { name: "U'tuk-Ku The Ice Herald", level: 5 }
|
|
5379
|
+
};
|
|
5380
|
+
var adversaries = Object.values(ADVERSARIES);
|
|
5381
|
+
var COMPANIONS = {
|
|
5382
|
+
Gleb: { name: "Gleb", title: "The Outlaw King" },
|
|
5383
|
+
Grigor: { name: "Grigor", title: "The Unbreakable" },
|
|
5384
|
+
Hakan: { name: "Hakan", title: "The Artificer" },
|
|
5385
|
+
Letha: { name: "Letha", title: "The Dryad" },
|
|
5386
|
+
Miras: { name: "Miras", title: "The Horselord" },
|
|
5387
|
+
Nimet: { name: "Nimet", title: "The Fathomless" },
|
|
5388
|
+
Tomas: { name: "Tomas", title: "The Scout" },
|
|
5389
|
+
Vasa: { name: "Vasa", title: "The Devine" },
|
|
5390
|
+
Yana: { name: "Yana", title: "The Assassin" },
|
|
5391
|
+
Zaida: { name: "Zaida", title: "The Efreet" }
|
|
5392
|
+
};
|
|
5393
|
+
var companions = Object.values(COMPANIONS);
|
|
5394
|
+
var KINGDOM_VIRTUES = {
|
|
5395
|
+
East: { name: "Champion of the East", ability: "+2 Wild Advantages in hills." },
|
|
5396
|
+
North: { name: "Champion of the North", ability: "+2 Wild Advantages in mountains." },
|
|
5397
|
+
South: { name: "Champion of the South", ability: "+2 Wild Advantages in deserts." },
|
|
5398
|
+
West: { name: "Champion of the West", ability: "+2 Wild Advantages in forests." }
|
|
5399
|
+
};
|
|
5400
|
+
var kingdomVirtues = Object.values(KINGDOM_VIRTUES);
|
|
5401
|
+
|
|
5402
|
+
// src/data/udtBoxInventory.ts
|
|
5403
|
+
var udtBoxInventory_exports = {};
|
|
5404
|
+
__export(udtBoxInventory_exports, {
|
|
5405
|
+
EXPANSIONS: () => EXPANSIONS,
|
|
5406
|
+
coffers: () => coffers,
|
|
5407
|
+
coffers2: () => coffers2,
|
|
5408
|
+
expansions: () => expansions,
|
|
5409
|
+
skullsPack: () => skullsPack,
|
|
5410
|
+
sleeves: () => sleeves
|
|
5411
|
+
});
|
|
5412
|
+
var expansions = [
|
|
5413
|
+
{
|
|
5414
|
+
name: "Base Game",
|
|
5415
|
+
categories: [
|
|
5416
|
+
{
|
|
5417
|
+
name: "Manuals / Sheets",
|
|
5418
|
+
section: "Misc",
|
|
5419
|
+
components: [
|
|
5420
|
+
{ name: "Are You Ready To Take On The Tower", count: 1 },
|
|
5421
|
+
{ name: "Re-Pack Sheet", count: 1 },
|
|
5422
|
+
{ name: "Rulebook", count: 1 }
|
|
5423
|
+
]
|
|
5424
|
+
},
|
|
5425
|
+
{
|
|
5426
|
+
name: "Tower",
|
|
5427
|
+
section: "Misc",
|
|
5428
|
+
components: [
|
|
5429
|
+
{ name: "Seals", count: 12 },
|
|
5430
|
+
{ name: "Tower", count: 1 }
|
|
5431
|
+
]
|
|
5432
|
+
},
|
|
5433
|
+
{
|
|
5434
|
+
name: "Boards",
|
|
5435
|
+
section: "Misc",
|
|
5436
|
+
components: [
|
|
5437
|
+
{ name: "Brutal Warlord", count: 1 },
|
|
5438
|
+
{ name: "Game Board", count: 1 },
|
|
5439
|
+
{ name: "Orphaned Scion", count: 1 },
|
|
5440
|
+
{ name: "Relic Hunter", count: 1 },
|
|
5441
|
+
{ name: "Spymaster", count: 1 }
|
|
5442
|
+
]
|
|
5443
|
+
},
|
|
5444
|
+
{
|
|
5445
|
+
name: "Minis",
|
|
5446
|
+
section: "Misc",
|
|
5447
|
+
components: [
|
|
5448
|
+
{ name: "Bazaars", count: 4 },
|
|
5449
|
+
{ name: "Citadels", count: 4 },
|
|
5450
|
+
{ name: "Hero Figures", count: 4 },
|
|
5451
|
+
{ name: "Sanctuaries", count: 4 },
|
|
5452
|
+
{ name: "Villages", count: 4 }
|
|
5453
|
+
]
|
|
5454
|
+
},
|
|
5455
|
+
{
|
|
5456
|
+
name: "Mini Bases",
|
|
5457
|
+
section: "Misc",
|
|
5458
|
+
components: [
|
|
5459
|
+
{ color: "Blue", count: 1 },
|
|
5460
|
+
{ color: "Brown", count: 1 },
|
|
5461
|
+
{ color: "Green", count: 1 },
|
|
5462
|
+
{ color: "White", count: 1 }
|
|
5463
|
+
]
|
|
5464
|
+
},
|
|
5465
|
+
{
|
|
5466
|
+
name: "Dungeons",
|
|
5467
|
+
section: "Tokens",
|
|
5468
|
+
components: [
|
|
5469
|
+
{ name: "Cave", count: 1 },
|
|
5470
|
+
{ name: "Encampment", count: 1 },
|
|
5471
|
+
{ name: "Fortress", count: 1 },
|
|
5472
|
+
{ name: "Ruins", count: 1 },
|
|
5473
|
+
{ name: "Shrine", count: 1 },
|
|
5474
|
+
{ name: "Tomb", count: 1 }
|
|
5475
|
+
]
|
|
5476
|
+
},
|
|
5477
|
+
{
|
|
5478
|
+
name: "Foes / Adversaries",
|
|
5479
|
+
section: "Tokens",
|
|
5480
|
+
components: [
|
|
5481
|
+
{ name: "Brigands", level: 2, count: 8 },
|
|
5482
|
+
{ name: "Oreks", level: 2, count: 6 },
|
|
5483
|
+
{ name: "Shadow Wolves", level: 2, count: 8 },
|
|
5484
|
+
{ name: "Spine Fiend", level: 2, count: 6 },
|
|
5485
|
+
{ name: "Clan Of Neuri", level: 3, count: 5 },
|
|
5486
|
+
{ name: "Frost Troll", level: 3, count: 4 },
|
|
5487
|
+
{ name: "Lemure", level: 3, count: 6 },
|
|
5488
|
+
{ name: "Widowmade Spider", level: 3, count: 5 },
|
|
5489
|
+
{ name: "Dragon", level: 4, count: 2 },
|
|
5490
|
+
{ name: "Mormo", level: 4, count: 4 },
|
|
5491
|
+
{ name: "Striga", level: 4, count: 2 },
|
|
5492
|
+
{ name: "Titan", level: 4, count: 1 },
|
|
5493
|
+
{ name: "Ashstrider", level: 5, count: 1 },
|
|
5494
|
+
{ name: "Bane Of Omens", level: 5, count: 1 },
|
|
5495
|
+
{ name: "Empress Of Shades", level: 5, count: 1 },
|
|
5496
|
+
{ name: "Gaze Eternal", level: 5, count: 1 },
|
|
5497
|
+
{ name: "Gravemaw", level: 5, count: 1 },
|
|
5498
|
+
{ name: "Isa The Exile", level: 5, count: 1 },
|
|
5499
|
+
{ name: "Lingering Rot", level: 5, count: 1 },
|
|
5500
|
+
{ name: "U'tuk-Ku The Ice Herald", level: 5, count: 1 }
|
|
5501
|
+
]
|
|
5502
|
+
},
|
|
5503
|
+
{
|
|
5504
|
+
name: "Quest Markers & Bases",
|
|
5505
|
+
section: "Tokens",
|
|
5506
|
+
components: [
|
|
5507
|
+
{ name: "Adversary", count: 1 },
|
|
5508
|
+
{ name: "Companion", count: 1 },
|
|
5509
|
+
{ name: "Main Goal", count: 1 }
|
|
5510
|
+
]
|
|
5511
|
+
},
|
|
5512
|
+
{
|
|
5513
|
+
name: "Special",
|
|
5514
|
+
section: "Tokens",
|
|
5515
|
+
components: [
|
|
5516
|
+
{ name: "Caravans", type: "Eastern", count: 1 },
|
|
5517
|
+
{ name: "Caravans", type: "Northern", count: 1 },
|
|
5518
|
+
{ name: "Caravans", type: "Western", count: 1 },
|
|
5519
|
+
{ name: "River Of Fire", count: 4 },
|
|
5520
|
+
{ name: "Siege Trees", count: 8 },
|
|
5521
|
+
{ name: "Spores", count: 8 }
|
|
5522
|
+
]
|
|
5523
|
+
},
|
|
5524
|
+
{
|
|
5525
|
+
name: "Spirits",
|
|
5526
|
+
section: "Tokens",
|
|
5527
|
+
components: [
|
|
5528
|
+
{ name: "1's", count: 25 },
|
|
5529
|
+
{ name: "5's", count: 19 }
|
|
5530
|
+
]
|
|
5531
|
+
},
|
|
5532
|
+
{
|
|
5533
|
+
name: "Virtues",
|
|
5534
|
+
section: "Tokens",
|
|
5535
|
+
components: [
|
|
5536
|
+
{ name: "Anointed", count: 1 },
|
|
5537
|
+
{ name: "Blessed", count: 1 },
|
|
5538
|
+
{ name: "Callous", count: 1 },
|
|
5539
|
+
{ name: "Champion Of The East", count: 1 },
|
|
5540
|
+
{ name: "Champion Of The North", count: 1 },
|
|
5541
|
+
{ name: "Champion Of The South", count: 1 },
|
|
5542
|
+
{ name: "Champion Of The West", count: 1 },
|
|
5543
|
+
{ name: "Crafty", count: 1 },
|
|
5544
|
+
{ name: "Fortunate", count: 1 },
|
|
5545
|
+
{ name: "Infused", count: 1 },
|
|
5546
|
+
{ name: "Inspiring", count: 1 },
|
|
5547
|
+
{ name: "Inventive", count: 1 },
|
|
5548
|
+
{ name: "Lucky", count: 1 },
|
|
5549
|
+
{ name: "Relentless", count: 1 },
|
|
5550
|
+
{ name: "Resourceful", count: 1 },
|
|
5551
|
+
{ name: "Unseen", count: 1 }
|
|
5552
|
+
]
|
|
5553
|
+
},
|
|
5554
|
+
{
|
|
5555
|
+
name: "Warriors",
|
|
5556
|
+
section: "Tokens",
|
|
5557
|
+
components: [
|
|
5558
|
+
{ name: "1's", count: 30 },
|
|
5559
|
+
{ name: "5's", count: 25 }
|
|
5560
|
+
]
|
|
5561
|
+
},
|
|
5562
|
+
{
|
|
5563
|
+
name: "Companion",
|
|
5564
|
+
section: "Cards",
|
|
5565
|
+
components: [
|
|
5566
|
+
{ name: "Gleb", description: "The Outlaw King", count: 1 },
|
|
5567
|
+
{ name: "Grigor", description: "The Unbreakable", count: 1 },
|
|
5568
|
+
{ name: "Hakan", description: "The Artificer", count: 1 },
|
|
5569
|
+
{ name: "Letha", description: "The Dryad", count: 1 },
|
|
5570
|
+
{ name: "Miras", description: "The Horselord", count: 1 },
|
|
5571
|
+
{ name: "Nimet", description: "The Fathomless", count: 1 },
|
|
5572
|
+
{ name: "Tomas", description: "The Scout", count: 1 },
|
|
5573
|
+
{ name: "Vasa", description: "The Devine", count: 1 },
|
|
5574
|
+
{ name: "Yana", description: "The Assassin", count: 1 },
|
|
5575
|
+
{ name: "Zaida", description: "The Efreet", count: 1 }
|
|
5576
|
+
]
|
|
5577
|
+
},
|
|
5578
|
+
{
|
|
5579
|
+
name: "Corruption",
|
|
5580
|
+
section: "Cards",
|
|
5581
|
+
components: [
|
|
5582
|
+
{ name: "Cruel", count: 1 },
|
|
5583
|
+
{ name: "Cursed", count: 1 },
|
|
5584
|
+
{ name: "Feeble", count: 1 },
|
|
5585
|
+
{ name: "Feral", count: 1 },
|
|
5586
|
+
{ name: "Feverish", count: 1 },
|
|
5587
|
+
{ name: "Greedy", count: 1 },
|
|
5588
|
+
{ name: "Lost", count: 1 },
|
|
5589
|
+
{ name: "Selfish", count: 1 },
|
|
5590
|
+
{ name: "Suspicious", count: 1 },
|
|
5591
|
+
{ name: "Tempted", count: 1 },
|
|
5592
|
+
{ name: "Uncertain", count: 1 },
|
|
5593
|
+
{ name: "Weak", count: 1 }
|
|
5594
|
+
]
|
|
5595
|
+
},
|
|
5596
|
+
{
|
|
5597
|
+
name: "Foes",
|
|
5598
|
+
section: "Cards",
|
|
5599
|
+
components: [
|
|
5600
|
+
{ name: "Brigands", level: 2, count: 1 },
|
|
5601
|
+
{ name: "Oreks", level: 2, count: 1 },
|
|
5602
|
+
{ name: "Shadow Wolves", level: 2, count: 1 },
|
|
5603
|
+
{ name: "Spine Fiend", level: 2, count: 1 },
|
|
5604
|
+
{ name: "Clan Of Neuri", level: 3, count: 1 },
|
|
5605
|
+
{ name: "Frost Troll", level: 3, count: 1 },
|
|
5606
|
+
{ name: "Lemure", level: 3, count: 1 },
|
|
5607
|
+
{ name: "Widowmade Spider", level: 3, count: 1 },
|
|
5608
|
+
{ name: "Dragon", level: 4, count: 1 },
|
|
5609
|
+
{ name: "Mormo", level: 4, count: 1 },
|
|
5610
|
+
{ name: "Striga", level: 4, count: 1 },
|
|
5611
|
+
{ name: "Titan", level: 4, count: 1 },
|
|
5612
|
+
{ name: "Ashstrider", level: 5, count: 1 },
|
|
5613
|
+
{ name: "Bane Of Omens", level: 5, count: 1 },
|
|
5614
|
+
{ name: "Empress Of Shades", level: 5, count: 1 },
|
|
5615
|
+
{ name: "Gaze Eternal", level: 5, count: 1 },
|
|
5616
|
+
{ name: "Gravemaw", level: 5, count: 1 },
|
|
5617
|
+
{ name: "Isa The Exile", level: 5, count: 1 },
|
|
5618
|
+
{ name: "Lingering Rot", level: 5, count: 1 },
|
|
5619
|
+
{ name: "U'tuk-Ku The Ice Herald", level: 5, count: 1 }
|
|
5620
|
+
]
|
|
5621
|
+
},
|
|
5622
|
+
{
|
|
5623
|
+
name: "Gear",
|
|
5624
|
+
section: "Cards",
|
|
5625
|
+
components: [
|
|
5626
|
+
{ name: "Blessed Scepters", count: 3 },
|
|
5627
|
+
{ name: "Brass Talismans", count: 3 },
|
|
5628
|
+
{ name: "Dusky Cloaks", count: 3 },
|
|
5629
|
+
{ name: "LeaTher Armor", count: 3 },
|
|
5630
|
+
{ name: "Longswords", count: 3 },
|
|
5631
|
+
{ name: "Trusted Maps", count: 3 }
|
|
5632
|
+
]
|
|
5633
|
+
},
|
|
5634
|
+
{
|
|
5635
|
+
name: "Heroic Tests",
|
|
5636
|
+
section: "Cards",
|
|
5637
|
+
components: [
|
|
5638
|
+
{ name: "Finish Building The Shrine", type: "Compassion", count: 1 },
|
|
5639
|
+
{ name: "Suffer With The Silent Sisters", type: "Compassion", count: 1 },
|
|
5640
|
+
{ name: "Guide Abandoned Pilgrims", type: "Compassion", count: 1 },
|
|
5641
|
+
{ name: "Hold A Moonless Vigil", type: "Compassion", count: 1 },
|
|
5642
|
+
{ name: "Perform The Song Of Peril", type: "Prowess", count: 1 },
|
|
5643
|
+
{ name: "Race To The Golden Obelisk", type: "Prowess", count: 1 },
|
|
5644
|
+
{ name: "Solve The Riddle Of The Marid", type: "Prowess", count: 1 },
|
|
5645
|
+
{ name: "Survive The Drowned Barrows", type: "Prowess", count: 1 },
|
|
5646
|
+
{ name: "Consecrate Akartus", type: "Sacrifice", count: 1 },
|
|
5647
|
+
{ name: "Lay Plovo's Ghost To Rest", type: "Sacrifice", count: 1 },
|
|
5648
|
+
{ name: "Repair The Weeping Damn", type: "Sacrifice", count: 1 },
|
|
5649
|
+
{ name: "Supply The WatchTowers", type: "Sacrifice", count: 1 },
|
|
5650
|
+
{ name: "Activate The Ley Lines", type: "Valor", count: 1 },
|
|
5651
|
+
{ name: "Impress The Winter Fey", type: "Valor", count: 1 },
|
|
5652
|
+
{ name: "Protect The Radiant Castle", type: "Valor", count: 1 },
|
|
5653
|
+
{ name: "Win Egan's Tournament", type: "Valor", count: 1 }
|
|
5654
|
+
]
|
|
5655
|
+
},
|
|
5656
|
+
{
|
|
5657
|
+
name: "Potions",
|
|
5658
|
+
section: "Cards",
|
|
5659
|
+
components: [
|
|
5660
|
+
{ name: "Potion Of Dragon Teeth", count: 3 },
|
|
5661
|
+
{ name: "Potion Of Fortune's Favor", count: 3 },
|
|
5662
|
+
{ name: "Potion Of one Thousand Strides", count: 3 },
|
|
5663
|
+
{ name: "Potion Of Purifying Breath", count: 3 },
|
|
5664
|
+
{ name: "Potion Of The Golden Sun", count: 3 },
|
|
5665
|
+
{ name: "Potion Of The Siren's Song", count: 3 }
|
|
5666
|
+
]
|
|
5667
|
+
},
|
|
5668
|
+
{
|
|
5669
|
+
name: "Quest Items",
|
|
5670
|
+
section: "Cards",
|
|
5671
|
+
components: [
|
|
5672
|
+
{ name: "Amulet Of Annihilation", count: 1 },
|
|
5673
|
+
{ name: "Amulet Of Hope", count: 4 },
|
|
5674
|
+
{ name: "Bezoar", count: 1 },
|
|
5675
|
+
{ name: "Dragon Scales", count: 1 },
|
|
5676
|
+
{ name: "Fulminating Silver", count: 1 },
|
|
5677
|
+
{ name: "Golden Wolf Pelt", count: 1 },
|
|
5678
|
+
{ name: "Herbal Remedy", count: 1 },
|
|
5679
|
+
{ name: "Horn Of The Elements", count: 1 },
|
|
5680
|
+
{ name: "Mark Of The Outlaw", count: 1 },
|
|
5681
|
+
{ name: "Orb Of Pure Snow", count: 1 },
|
|
5682
|
+
{ name: "Relic Of Light", count: 1 },
|
|
5683
|
+
{ name: "Smuggler's Coin", count: 1 },
|
|
5684
|
+
{ name: "The Black Mark", count: 1 },
|
|
5685
|
+
{ name: "Tomas's Map", count: 1 },
|
|
5686
|
+
{ name: "Tools Of The Saboteur", count: 1 },
|
|
5687
|
+
{ name: "Turquoise Urn", count: 1 },
|
|
5688
|
+
{ name: "Wraps Of Invisibility", count: 1 }
|
|
5689
|
+
]
|
|
5690
|
+
},
|
|
5691
|
+
{
|
|
5692
|
+
name: "Treasures",
|
|
5693
|
+
section: "Cards",
|
|
5694
|
+
components: [
|
|
5695
|
+
{ name: "Acorns Of The White Oak", count: 1 },
|
|
5696
|
+
{ name: "Amulet Of The Marid", count: 1 },
|
|
5697
|
+
{ name: "Axe Of Soul Rending", count: 1 },
|
|
5698
|
+
{ name: "Azkol's Banner", count: 1 },
|
|
5699
|
+
{ name: "Azkol's Horn", count: 1 },
|
|
5700
|
+
{ name: "Azkol's Idol", count: 1 },
|
|
5701
|
+
{ name: "Circlet Of Conviction", count: 1 },
|
|
5702
|
+
{ name: "Cloak Of Stars", count: 1 },
|
|
5703
|
+
{ name: "Crown Of Azkol", count: 1 },
|
|
5704
|
+
{ name: "Golden Mace Of Azkol", count: 1 },
|
|
5705
|
+
{ name: "Hallowed Reliquary", count: 1 },
|
|
5706
|
+
{ name: "Kamaria's Carpet", count: 1 },
|
|
5707
|
+
{ name: "Lamp Of Darkness", count: 1 },
|
|
5708
|
+
{ name: "Lamp Of Hope", count: 1 },
|
|
5709
|
+
{ name: "Necklace Of Haggling", count: 1 },
|
|
5710
|
+
{ name: "Oakstone Bow", count: 1 },
|
|
5711
|
+
{ name: "Scroll Of Burning Sands", count: 1 },
|
|
5712
|
+
{ name: "Scroll Of The Great Serpent", count: 1 },
|
|
5713
|
+
{ name: "Scroll Of Twilight Shadow", count: 1 },
|
|
5714
|
+
{ name: "Spear Of Atish", count: 1 },
|
|
5715
|
+
{ name: "Tears Of The Shedu", count: 1 },
|
|
5716
|
+
{ name: "White Cauldron", count: 1 }
|
|
5717
|
+
]
|
|
5718
|
+
}
|
|
5719
|
+
]
|
|
5720
|
+
},
|
|
5721
|
+
{
|
|
5722
|
+
name: "Alliances",
|
|
5723
|
+
categories: [
|
|
5724
|
+
{
|
|
5725
|
+
name: "Boards",
|
|
5726
|
+
section: "Misc",
|
|
5727
|
+
components: [
|
|
5728
|
+
{ name: "Arcane Scouts", count: 1 },
|
|
5729
|
+
{ name: "Archwright", count: 1 },
|
|
5730
|
+
{ name: "Druids Circle", count: 1 },
|
|
5731
|
+
{ name: "Heroic Action", count: 4 },
|
|
5732
|
+
{ name: "Hunted Recluse", count: 1 },
|
|
5733
|
+
{ name: "Influence Vessel", count: 1 },
|
|
5734
|
+
{ name: "Paladins Order", count: 1 },
|
|
5735
|
+
{ name: "Thieves Guild", count: 1 }
|
|
5736
|
+
]
|
|
5737
|
+
},
|
|
5738
|
+
{
|
|
5739
|
+
name: "Mini Bases",
|
|
5740
|
+
section: "Misc",
|
|
5741
|
+
components: [
|
|
5742
|
+
{ color: "Brown", count: 1 },
|
|
5743
|
+
{ color: "Yellow", count: 1 }
|
|
5744
|
+
]
|
|
5745
|
+
},
|
|
5746
|
+
{
|
|
5747
|
+
name: "Flags",
|
|
5748
|
+
section: "Misc",
|
|
5749
|
+
components: [
|
|
5750
|
+
{ type: "Arcane Scouts", count: 1 },
|
|
5751
|
+
{ type: "Druids Circle", count: 1 },
|
|
5752
|
+
{ type: "Paladins Order", count: 1 },
|
|
5753
|
+
{ type: "Thieves Guild", count: 1 }
|
|
5754
|
+
]
|
|
5755
|
+
},
|
|
5756
|
+
{
|
|
5757
|
+
name: "Skulls",
|
|
5758
|
+
section: "Misc",
|
|
5759
|
+
components: [
|
|
5760
|
+
{ color: "Blue (Frost)", count: 11 },
|
|
5761
|
+
{ color: "Green (Blight)", count: 11 },
|
|
5762
|
+
{ color: "Purple (Omen)", count: 11 },
|
|
5763
|
+
{ color: "Red (Fire)", count: 11 }
|
|
5764
|
+
]
|
|
5765
|
+
},
|
|
5766
|
+
{
|
|
5767
|
+
name: "Influence",
|
|
5768
|
+
section: "Tokens",
|
|
5769
|
+
components: [
|
|
5770
|
+
{ name: "1's", count: 17 },
|
|
5771
|
+
{ name: "5's", count: 23 }
|
|
5772
|
+
]
|
|
5773
|
+
},
|
|
5774
|
+
{
|
|
5775
|
+
name: "Virtues",
|
|
5776
|
+
section: "Tokens",
|
|
5777
|
+
components: [
|
|
5778
|
+
{ name: "Exalted", count: 1 },
|
|
5779
|
+
{ name: "Shadowspinner", count: 1 },
|
|
5780
|
+
{ name: "Sinbearer", count: 1 },
|
|
5781
|
+
{ name: "Soulreaper", count: 1 },
|
|
5782
|
+
{ name: "Tactical", count: 1 },
|
|
5783
|
+
{ name: "Wily", count: 1 }
|
|
5784
|
+
]
|
|
5785
|
+
},
|
|
5786
|
+
{
|
|
5787
|
+
name: "Companion",
|
|
5788
|
+
section: "Cards",
|
|
5789
|
+
components: [
|
|
5790
|
+
{ name: "Amani", description: "The Vizier", count: 1 },
|
|
5791
|
+
{ name: "Berat", description: "The Wizard", count: 1 },
|
|
5792
|
+
{ name: "Burgoyn", description: "The Herbalist", count: 1 },
|
|
5793
|
+
{ name: "Ema", description: "The Grand Merchant", count: 1 },
|
|
5794
|
+
{ name: "Haraswa", description: "The Pegasus", count: 1 },
|
|
5795
|
+
{ name: "Lukas", description: "The Plunderer", count: 1 },
|
|
5796
|
+
{ name: "Maxim", description: "The Beast", count: 1 },
|
|
5797
|
+
{ name: "Omar", description: "The Healer", count: 1 },
|
|
5798
|
+
{ name: "Oola", description: "The Nomad", count: 1 },
|
|
5799
|
+
{ name: "Ruska", description: "The Barbarian", count: 1 },
|
|
5800
|
+
{ name: "Sanzhar", description: "The Zealot", count: 1 },
|
|
5801
|
+
{ name: "Xyr", description: "The Oracle", count: 1 }
|
|
5802
|
+
]
|
|
5803
|
+
},
|
|
5804
|
+
{
|
|
5805
|
+
name: "Treasures",
|
|
5806
|
+
section: "Cards",
|
|
5807
|
+
components: [
|
|
5808
|
+
{ name: "Coffer Of The Master Thief", count: 1 },
|
|
5809
|
+
{ name: "Crystal Blade", count: 1 },
|
|
5810
|
+
{ name: "Crystal Platemail", count: 1 },
|
|
5811
|
+
{ name: "Crystal Shield", count: 1 },
|
|
5812
|
+
{ name: "Diadem Of The Emmisary", count: 1 },
|
|
5813
|
+
{ name: "Druid's Incense", count: 1 },
|
|
5814
|
+
{ name: "Everlasting Brazier", count: 1 },
|
|
5815
|
+
{ name: "Ewer Of The Silent Child", count: 1 },
|
|
5816
|
+
{ name: "Forbidden Grimoire", count: 1 },
|
|
5817
|
+
{ name: "Iron Hound Of Azkol", count: 1 },
|
|
5818
|
+
{ name: "Jeweled Goblet Of Azkol", count: 1 },
|
|
5819
|
+
{ name: "Paladin's Greatshield", count: 1 },
|
|
5820
|
+
{ name: "Ring Of The Emmisary", count: 1 },
|
|
5821
|
+
{ name: "Robes Of The Last Sultan", count: 1 },
|
|
5822
|
+
{ name: "Scroll Of Forged Friendship", count: 1 },
|
|
5823
|
+
{ name: "Standard Of The Scouts", count: 1 },
|
|
5824
|
+
{ name: "Staff Of Wishes", count: 1 },
|
|
5825
|
+
{ name: "Trebblok's Hammer", count: 1 },
|
|
5826
|
+
{ name: "Vestments Of The Emmisary", count: 1 },
|
|
5827
|
+
{ name: "Zemayir's Teeth", count: 1 }
|
|
5828
|
+
]
|
|
5829
|
+
}
|
|
5830
|
+
]
|
|
5831
|
+
},
|
|
5832
|
+
{
|
|
5833
|
+
name: "Covenant",
|
|
5834
|
+
categories: [
|
|
5835
|
+
{
|
|
5836
|
+
name: "Boards",
|
|
5837
|
+
section: "Misc",
|
|
5838
|
+
components: [
|
|
5839
|
+
{ name: "Devious Swindler", count: 1 },
|
|
5840
|
+
{ name: "Relentless Warden", count: 1 },
|
|
5841
|
+
{ name: "Reverent Astromancer", count: 1 },
|
|
5842
|
+
{ name: "Undaunted Aegis", count: 1 }
|
|
5843
|
+
]
|
|
5844
|
+
},
|
|
5845
|
+
{
|
|
5846
|
+
name: "Mini Bases",
|
|
5847
|
+
section: "Misc",
|
|
5848
|
+
components: [
|
|
5849
|
+
{ color: "Blue", count: 1 },
|
|
5850
|
+
{ color: "Brown", count: 1 },
|
|
5851
|
+
{ color: "Green", count: 1 },
|
|
5852
|
+
{ color: "Orange", count: 1 }
|
|
5853
|
+
]
|
|
5854
|
+
},
|
|
5855
|
+
{
|
|
5856
|
+
name: "Monuments",
|
|
5857
|
+
section: "Misc",
|
|
5858
|
+
components: [
|
|
5859
|
+
{ type: "Arch of the Golden Sun", count: 1 },
|
|
5860
|
+
{ type: "Argent Oak", count: 1 },
|
|
5861
|
+
{ type: "Cenotaph of the First Prophet", count: 1 },
|
|
5862
|
+
{ type: "Colossus of Bjorn", count: 1 },
|
|
5863
|
+
{ type: "Endless Necropolis", count: 1 },
|
|
5864
|
+
{ type: "Moonstone Temple", count: 1 },
|
|
5865
|
+
{ type: "Nightmare Cage", count: 1 },
|
|
5866
|
+
{ type: "Tower Shard", count: 1 }
|
|
5867
|
+
]
|
|
5868
|
+
},
|
|
5869
|
+
{
|
|
5870
|
+
name: "Foundation",
|
|
5871
|
+
section: "Tokens",
|
|
5872
|
+
components: [
|
|
5873
|
+
{ name: "Arch of the Golden Sun / Nightmare Cage", count: 1 },
|
|
5874
|
+
{ name: "Argent Oak / Moonstone Temple", count: 1 },
|
|
5875
|
+
{ name: "Cenotaph of the First Prophet / Tower Shard", count: 1 },
|
|
5876
|
+
{ name: "Colossus of Bjorn / Endless Necropolis", count: 1 }
|
|
5877
|
+
]
|
|
5878
|
+
},
|
|
5879
|
+
{
|
|
5880
|
+
name: "Virtues",
|
|
5881
|
+
section: "Tokens",
|
|
5882
|
+
components: [
|
|
5883
|
+
{ name: "Bounteous", count: 1 },
|
|
5884
|
+
{ name: "Exalted", count: 1 },
|
|
5885
|
+
{ name: "Zealous", count: 1 },
|
|
5886
|
+
{ name: "Emboldened", count: 1 },
|
|
5887
|
+
{ name: "Resolute", count: 1 },
|
|
5888
|
+
{ name: "Steeled", count: 1 },
|
|
5889
|
+
{ name: "Keen-Eyed", count: 1 },
|
|
5890
|
+
{ name: "Inspiring", count: 1 },
|
|
5891
|
+
{ name: "Instinctive", count: 1 },
|
|
5892
|
+
{ name: "Calculating", count: 1 },
|
|
5893
|
+
{ name: "Opportunistic", count: 1 },
|
|
5894
|
+
{ name: "Fortuitous", count: 1 }
|
|
5895
|
+
]
|
|
5896
|
+
},
|
|
5897
|
+
{
|
|
5898
|
+
name: "Corruption",
|
|
5899
|
+
section: "Cards",
|
|
5900
|
+
components: [
|
|
5901
|
+
{ name: "Aquaphobic", count: 1 },
|
|
5902
|
+
{ name: "Arrogant", count: 1 },
|
|
5903
|
+
{ name: "Crestfallen", count: 1 },
|
|
5904
|
+
{ name: "Disreputable", count: 1 },
|
|
5905
|
+
{ name: "Fatigued", count: 1 },
|
|
5906
|
+
{ name: "Indolent", count: 1 },
|
|
5907
|
+
{ name: "Inobservant", count: 1 },
|
|
5908
|
+
{ name: "Reckless", count: 1 },
|
|
5909
|
+
{ name: "Shaken", count: 1 },
|
|
5910
|
+
{ name: "Snobby", count: 1 },
|
|
5911
|
+
{ name: "Timid", count: 1 },
|
|
5912
|
+
{ name: "Vain", count: 1 }
|
|
5913
|
+
]
|
|
5914
|
+
},
|
|
5915
|
+
{
|
|
5916
|
+
name: "Invocation",
|
|
5917
|
+
section: "Cards",
|
|
5918
|
+
components: [
|
|
5919
|
+
{ name: "Abate the Darkness", count: 1 },
|
|
5920
|
+
{ name: "Celestial Jaunt", count: 1 },
|
|
5921
|
+
{ name: "Commanding Rebuke", count: 1 },
|
|
5922
|
+
{ name: "Smite the Wicked", count: 1 }
|
|
5923
|
+
]
|
|
5924
|
+
},
|
|
5925
|
+
{
|
|
5926
|
+
name: "Monument",
|
|
5927
|
+
section: "Cards",
|
|
5928
|
+
components: [
|
|
5929
|
+
{ name: "Arch of the Golden Sun", description: "Bazaar", count: 1 },
|
|
5930
|
+
{ name: "Argent Oak", description: "Sanctuary", count: 1 },
|
|
5931
|
+
{ name: "Cenotaph of the First Prophet", description: "Citadel", count: 1 },
|
|
5932
|
+
{ name: "Colossus of Bjorn", description: "Village", count: 1 },
|
|
5933
|
+
{ name: "Endless Necropolis", description: "Village", count: 1 },
|
|
5934
|
+
{ name: "Moonstone Temple", description: "Sanctuary", count: 1 },
|
|
5935
|
+
{ name: "Nightmare Cage", description: "Bazaar", count: 1 },
|
|
5936
|
+
{ name: "Tower Shard", description: "Citadel", count: 1 }
|
|
5937
|
+
]
|
|
5938
|
+
},
|
|
5939
|
+
{
|
|
5940
|
+
name: "Spell",
|
|
5941
|
+
section: "Cards",
|
|
5942
|
+
components: [
|
|
5943
|
+
{ name: "Aura of Friendship", count: 1 },
|
|
5944
|
+
{ name: "Bestow Blessing", count: 1 },
|
|
5945
|
+
{ name: "Bounty of the Gods", count: 1 },
|
|
5946
|
+
{ name: "Ritual of Warding", count: 1 },
|
|
5947
|
+
{ name: "Soothing Word", count: 1 },
|
|
5948
|
+
{ name: "Winds of Change", count: 1 }
|
|
5949
|
+
]
|
|
5950
|
+
},
|
|
5951
|
+
{
|
|
5952
|
+
name: "Treasures",
|
|
5953
|
+
section: "Cards",
|
|
5954
|
+
components: [
|
|
5955
|
+
{ name: "Archwright's Sledge", count: 1 },
|
|
5956
|
+
{ name: "Azkol's Chakram", count: 1 },
|
|
5957
|
+
{ name: "Azkol's Ichor", count: 1 },
|
|
5958
|
+
{ name: "Azkol's Scroll", count: 1 },
|
|
5959
|
+
{ name: "Azkol's Vambraces", count: 1 },
|
|
5960
|
+
{ name: "Beacon Stone", count: 1 },
|
|
5961
|
+
{ name: "Brutal Warlord's Bell", count: 1 },
|
|
5962
|
+
{ name: "Everfilled Chest", count: 1 },
|
|
5963
|
+
{ name: "Grim Whisper", count: 1 },
|
|
5964
|
+
{ name: "Haunted Recluse's Effigy", count: 1 },
|
|
5965
|
+
{ name: "Opal of Protection", count: 1 },
|
|
5966
|
+
{ name: "Orhpaned Scion's Charm", count: 1 },
|
|
5967
|
+
{ name: "Relic Hunter's Flagon", count: 1 },
|
|
5968
|
+
{ name: "Sanctified Flask", count: 1 },
|
|
5969
|
+
{ name: "Spymaster's Journal", count: 1 },
|
|
5970
|
+
{ name: "Tent of Revelry", count: 1 },
|
|
5971
|
+
{ name: "The Iron Wall", count: 1 },
|
|
5972
|
+
{ name: "Wand of Celerity", count: 1 },
|
|
5973
|
+
{ name: "Wand of Conflagration", count: 1 },
|
|
5974
|
+
{ name: "Wand of Pacification", count: 1 }
|
|
5975
|
+
]
|
|
5976
|
+
}
|
|
5977
|
+
]
|
|
5978
|
+
},
|
|
5979
|
+
{
|
|
5980
|
+
name: "Dark Horde",
|
|
5981
|
+
categories: [
|
|
5982
|
+
{
|
|
5983
|
+
name: "Storage Tray 1 (Top)",
|
|
5984
|
+
section: "Minis",
|
|
5985
|
+
components: [
|
|
5986
|
+
{ name: "Briagands", count: 8 },
|
|
5987
|
+
{ name: "Clan Of Neuri", count: 5 },
|
|
5988
|
+
{ name: "Isa The Exile", count: 1 },
|
|
5989
|
+
{ name: "Lemure", count: 6 },
|
|
5990
|
+
{ name: "Mormo", count: 4 },
|
|
5991
|
+
{ name: "Oreks", count: 6 },
|
|
5992
|
+
{ name: "Shadow Wolves", count: 8 },
|
|
5993
|
+
{ name: "Spine Fiend", count: 6 },
|
|
5994
|
+
{ name: "Widowmade Spider", count: 5 }
|
|
5995
|
+
]
|
|
5996
|
+
},
|
|
5997
|
+
{
|
|
5998
|
+
name: "Storage Tray 2 (Bottom)",
|
|
5999
|
+
section: "Minis",
|
|
6000
|
+
components: [
|
|
6001
|
+
{ name: "Frost Trolls", count: 4 },
|
|
6002
|
+
{ name: "Gravemaw", count: 1 },
|
|
6003
|
+
{ name: "Dragon", count: 2 },
|
|
6004
|
+
{ name: "Empress Of Shades", count: 1 },
|
|
6005
|
+
{ name: "Bane Of Omens", count: 1 },
|
|
6006
|
+
{ name: "Lingering Rot", count: 1 },
|
|
6007
|
+
{ name: "Striga", count: 2 },
|
|
6008
|
+
{ name: "Ashstrider", count: 1 },
|
|
6009
|
+
{ name: "Titan", count: 1 },
|
|
6010
|
+
{ name: "Gaze Eternal", count: 1 },
|
|
6011
|
+
{ name: "U'tuk-Ku The Ice Herald", count: 1 },
|
|
6012
|
+
{ name: "Main Goal Marker", count: 1 },
|
|
6013
|
+
{ name: "Guild Quest Marker", count: 1 },
|
|
6014
|
+
{ name: "Adversary Quest Marker", count: 1 },
|
|
6015
|
+
{ name: "Companion Quest Marker", count: 1 }
|
|
6016
|
+
]
|
|
6017
|
+
}
|
|
6018
|
+
]
|
|
6019
|
+
}
|
|
6020
|
+
];
|
|
6021
|
+
var EXPANSIONS = expansions.reduce(
|
|
6022
|
+
(acc, e) => {
|
|
6023
|
+
acc[e.name] = e;
|
|
6024
|
+
return acc;
|
|
6025
|
+
},
|
|
6026
|
+
{}
|
|
6027
|
+
);
|
|
6028
|
+
var coffers = [
|
|
6029
|
+
{
|
|
6030
|
+
resource: "Influence",
|
|
6031
|
+
denominations: [
|
|
6032
|
+
{ name: "1's", count: 8 },
|
|
6033
|
+
{ name: "5's", count: 17 }
|
|
6034
|
+
],
|
|
6035
|
+
total: 25
|
|
6036
|
+
},
|
|
6037
|
+
{
|
|
6038
|
+
resource: "Spirits",
|
|
6039
|
+
denominations: [
|
|
6040
|
+
{ name: "1's", count: 24 },
|
|
6041
|
+
{ name: "5's", count: 16 }
|
|
6042
|
+
],
|
|
6043
|
+
total: 40
|
|
6044
|
+
},
|
|
6045
|
+
{
|
|
6046
|
+
resource: "Warriors",
|
|
6047
|
+
denominations: [
|
|
6048
|
+
{ name: "1's", count: 28 },
|
|
6049
|
+
{ name: "5's", count: 22 }
|
|
6050
|
+
],
|
|
6051
|
+
total: 50
|
|
6052
|
+
}
|
|
6053
|
+
];
|
|
6054
|
+
var coffers2 = {
|
|
6055
|
+
tokens: [
|
|
6056
|
+
{ name: "Advantage", count: 10 },
|
|
6057
|
+
{ name: "Charge", count: 10 },
|
|
6058
|
+
{ name: "Foundation", count: 4 },
|
|
6059
|
+
{ name: "Guild", count: 4 },
|
|
6060
|
+
{ name: "Protection", count: 6 },
|
|
6061
|
+
{ name: "Quarry", count: 1 },
|
|
6062
|
+
{ name: "Wasteland", count: 32 }
|
|
6063
|
+
],
|
|
6064
|
+
total: 67
|
|
6065
|
+
};
|
|
6066
|
+
var skullsPack = {
|
|
6067
|
+
tokens: [
|
|
6068
|
+
{ name: "White (Normal)", count: 10 },
|
|
6069
|
+
{ name: "Black (Doom)", count: 2 },
|
|
6070
|
+
{ name: "Blue (Frost)", count: 2 },
|
|
6071
|
+
{ name: "Green (Blight)", count: 2 },
|
|
6072
|
+
{ name: "Purple (Omen)", count: 2 },
|
|
6073
|
+
{ name: "Red (Fire)", count: 2 }
|
|
6074
|
+
],
|
|
6075
|
+
total: 20
|
|
6076
|
+
};
|
|
6077
|
+
var sleeves = [
|
|
6078
|
+
{ name: "Printed Large Sleeves", purposes: ["Monuments", "Treasure Cards"] },
|
|
6079
|
+
{
|
|
6080
|
+
name: "Printed Mini Sleeves",
|
|
6081
|
+
purposes: ["Gear", "Heroic Tests", "Invocations", "Potions", "Quest Items", "Spells"]
|
|
6082
|
+
},
|
|
6083
|
+
{ name: "Clear Large Sleeves", purposes: ["Companions", "Foes"] },
|
|
6084
|
+
{ name: "Clear Mini Sleeves", purposes: ["Blessings", "Corruptions"] }
|
|
6085
|
+
];
|
|
6086
|
+
|
|
6087
|
+
// src/seed/index.ts
|
|
6088
|
+
var seed_exports = {};
|
|
6089
|
+
__export(seed_exports, {
|
|
6090
|
+
ADVERSARIES: () => ADVERSARIES2,
|
|
6091
|
+
ALLIES: () => ALLIES,
|
|
6092
|
+
DIFFICULTIES: () => DIFFICULTIES,
|
|
6093
|
+
GAME_SOURCES: () => GAME_SOURCES,
|
|
6094
|
+
SystemRandom: () => SystemRandom,
|
|
6095
|
+
TIER1_FOES: () => TIER1_FOES,
|
|
6096
|
+
TIER2_FOES: () => TIER2_FOES,
|
|
6097
|
+
TIER3_FOES: () => TIER3_FOES,
|
|
6098
|
+
charToValue: () => charToValue,
|
|
6099
|
+
compareSeedsRaw: () => compareSeedsRaw,
|
|
6100
|
+
createSeed: () => createSeed,
|
|
6101
|
+
decodeRngSeed: () => decodeRngSeed,
|
|
6102
|
+
decodeSeed: () => decodeSeed,
|
|
6103
|
+
dumpSeedChars: () => dumpSeedChars,
|
|
6104
|
+
encodeSeed: () => encodeSeed,
|
|
6105
|
+
validateSeed: () => validateSeed,
|
|
6106
|
+
valueToChar: () => valueToChar
|
|
6107
|
+
});
|
|
6108
|
+
|
|
6109
|
+
// src/seed/udtSeedParser.ts
|
|
6110
|
+
var ALPHABET = "a123456789bcdefghijklmnpqrstuvwxyz";
|
|
6111
|
+
var BASE = 34;
|
|
6112
|
+
var SETUP_LENGTH = 6;
|
|
6113
|
+
var RNG_SEED_LENGTH = 6;
|
|
6114
|
+
var SEED_LENGTH = SETUP_LENGTH + RNG_SEED_LENGTH;
|
|
6115
|
+
var CHAR_TO_VALUE = /* @__PURE__ */ new Map();
|
|
6116
|
+
var VALUE_TO_CHAR = /* @__PURE__ */ new Map();
|
|
6117
|
+
for (let i = 0; i < ALPHABET.length; i++) {
|
|
6118
|
+
CHAR_TO_VALUE.set(ALPHABET[i], i);
|
|
6119
|
+
VALUE_TO_CHAR.set(i, ALPHABET[i]);
|
|
6120
|
+
}
|
|
6121
|
+
var TIER1_FOES = ["Brigands", "Oreks", "Shadow Wolves", "Spine Fiends"];
|
|
6122
|
+
var TIER2_FOES = ["Frost Trolls", "Clan of Neuri", "Lemures", "Widowmade Spiders"];
|
|
6123
|
+
var TIER3_FOES = ["Dragons", "Mormos", "Striga", "Titans"];
|
|
6124
|
+
var ADVERSARIES2 = [
|
|
6125
|
+
"Ashstrider",
|
|
6126
|
+
"Bane of Omens",
|
|
6127
|
+
"Empress of Shades",
|
|
6128
|
+
"Gaze Eternal",
|
|
6129
|
+
"Gravemaw",
|
|
6130
|
+
"Isa the Exile",
|
|
6131
|
+
"Lingering Rot",
|
|
6132
|
+
"Utuk'Ku"
|
|
6133
|
+
];
|
|
6134
|
+
var ALLIES = [
|
|
6135
|
+
"Gleb",
|
|
6136
|
+
"Grigor",
|
|
6137
|
+
"Hakan",
|
|
6138
|
+
"Letha",
|
|
6139
|
+
"Miras",
|
|
6140
|
+
"Nimet",
|
|
6141
|
+
"Tomas",
|
|
6142
|
+
"Vasa",
|
|
6143
|
+
"Yana",
|
|
6144
|
+
"Zaida"
|
|
6145
|
+
];
|
|
6146
|
+
var DIFFICULTIES = ["Heroic", "Gritty"];
|
|
6147
|
+
var GAME_SOURCES = ["Core", "Competitive"];
|
|
6148
|
+
function charToValue(c) {
|
|
6149
|
+
const v = CHAR_TO_VALUE.get(c.toLowerCase());
|
|
6150
|
+
if (v === void 0) {
|
|
6151
|
+
throw new Error(`Invalid seed character: '${c}'`);
|
|
6152
|
+
}
|
|
6153
|
+
return v;
|
|
6154
|
+
}
|
|
6155
|
+
function valueToChar(v) {
|
|
6156
|
+
const c = VALUE_TO_CHAR.get(v);
|
|
6157
|
+
if (c === void 0) {
|
|
6158
|
+
throw new Error(`Invalid seed value: ${v} (must be 0\u2013${BASE - 1})`);
|
|
6159
|
+
}
|
|
6160
|
+
return c;
|
|
6161
|
+
}
|
|
6162
|
+
function validateSeed(seed) {
|
|
6163
|
+
const stripped = seed.replace(/[-\s]/g, "").toLowerCase();
|
|
6164
|
+
if (stripped.length !== SEED_LENGTH) {
|
|
6165
|
+
throw new Error(`Invalid seed length: expected ${SEED_LENGTH} characters, got ${stripped.length}`);
|
|
6166
|
+
}
|
|
6167
|
+
for (const c of stripped) {
|
|
6168
|
+
if (!CHAR_TO_VALUE.has(c)) {
|
|
6169
|
+
throw new Error(`Invalid seed character: '${c}'`);
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
const upper = stripped.toUpperCase();
|
|
6173
|
+
return `${upper.slice(0, 4)}-${upper.slice(4, 8)}-${upper.slice(8, 12)}`;
|
|
6174
|
+
}
|
|
6175
|
+
function decodeSeed(seed) {
|
|
6176
|
+
const normalized = validateSeed(seed);
|
|
6177
|
+
const stripped = normalized.replace(/-/g, "").toLowerCase();
|
|
6178
|
+
const setup = [];
|
|
6179
|
+
for (let i = 0; i < SETUP_LENGTH; i++) {
|
|
6180
|
+
setup.push(charToValue(stripped[i]));
|
|
6181
|
+
}
|
|
6182
|
+
let rngSeed = 0;
|
|
6183
|
+
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
6184
|
+
const value = charToValue(stripped[SETUP_LENGTH + i]);
|
|
6185
|
+
rngSeed += value * Math.round(Math.pow(BASE, i));
|
|
6186
|
+
}
|
|
6187
|
+
const foeByteA = setup[0];
|
|
6188
|
+
const tier1 = foeByteA & 3;
|
|
6189
|
+
const tier2 = (foeByteA & 12) >> 2;
|
|
6190
|
+
const foeByteB = setup[1];
|
|
6191
|
+
const tier3 = (foeByteA & 16) >> 4 | (foeByteB & 16) >> 3;
|
|
6192
|
+
const adversaryIndex = foeByteB & 15;
|
|
6193
|
+
const allyIndex = setup[2];
|
|
6194
|
+
const extra = setup[3];
|
|
6195
|
+
const difficultyIndex = extra & 1;
|
|
6196
|
+
const expansionBits = (extra & 6) >> 1;
|
|
6197
|
+
const sourceBits = (extra & 8) >> 2;
|
|
6198
|
+
const playerCount = (setup[5] & 3) + 1;
|
|
6199
|
+
const expansions2 = [];
|
|
6200
|
+
if (expansionBits & 1) expansions2.push("Monuments");
|
|
6201
|
+
if (expansionBits & 2) expansions2.push("Alliances");
|
|
6202
|
+
let source;
|
|
6203
|
+
switch (sourceBits) {
|
|
6204
|
+
case 2:
|
|
6205
|
+
source = "Competitive";
|
|
6206
|
+
break;
|
|
6207
|
+
default:
|
|
6208
|
+
source = "Core";
|
|
6209
|
+
break;
|
|
6210
|
+
}
|
|
6211
|
+
const seedBank = {
|
|
6212
|
+
initializationSeed: rngSeed,
|
|
6213
|
+
questSeed: rngSeed - 1,
|
|
6214
|
+
seedString: normalized
|
|
6215
|
+
};
|
|
6216
|
+
return {
|
|
6217
|
+
seed: normalized,
|
|
6218
|
+
tier1Foe: TIER1_FOES[tier1],
|
|
6219
|
+
tier2Foe: TIER2_FOES[tier2],
|
|
6220
|
+
tier3Foe: TIER3_FOES[tier3],
|
|
6221
|
+
adversary: ADVERSARIES2[adversaryIndex],
|
|
6222
|
+
ally: ALLIES[allyIndex],
|
|
6223
|
+
difficulty: DIFFICULTIES[difficultyIndex],
|
|
6224
|
+
source,
|
|
6225
|
+
expansions: expansions2,
|
|
6226
|
+
playerCount,
|
|
6227
|
+
rngSeed,
|
|
6228
|
+
seedBank,
|
|
6229
|
+
setup
|
|
6230
|
+
};
|
|
6231
|
+
}
|
|
6232
|
+
function decodeRngSeed(seed) {
|
|
6233
|
+
const normalized = validateSeed(seed);
|
|
6234
|
+
const stripped = normalized.replace(/-/g, "").toLowerCase();
|
|
6235
|
+
let rngSeed = 0;
|
|
6236
|
+
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
6237
|
+
const value = charToValue(stripped[SETUP_LENGTH + i]);
|
|
6238
|
+
rngSeed += value * Math.round(Math.pow(BASE, i));
|
|
6239
|
+
}
|
|
6240
|
+
return rngSeed;
|
|
6241
|
+
}
|
|
6242
|
+
function createSeed(config) {
|
|
6243
|
+
let foeByteA = 0;
|
|
6244
|
+
let foeByteB = 0;
|
|
6245
|
+
const tier1Index = TIER1_FOES.indexOf(config.foes[0]);
|
|
6246
|
+
const tier2Index = TIER2_FOES.indexOf(config.foes[1]);
|
|
6247
|
+
const tier3Index = TIER3_FOES.indexOf(config.foes[2]);
|
|
6248
|
+
if (tier1Index < 0) throw new Error(`Invalid Tier 1 foe: ${config.foes[0]}`);
|
|
6249
|
+
if (tier2Index < 0) throw new Error(`Invalid Tier 2 foe: ${config.foes[1]}`);
|
|
6250
|
+
if (tier3Index < 0) throw new Error(`Invalid Tier 3 foe: ${config.foes[2]}`);
|
|
6251
|
+
foeByteA = tier1Index & 3;
|
|
6252
|
+
foeByteA |= (tier2Index & 3) << 2;
|
|
6253
|
+
foeByteA |= (tier3Index & 1) << 4;
|
|
6254
|
+
foeByteB |= (tier3Index >> 1 & 1) << 4;
|
|
6255
|
+
const adversaryIndex = ADVERSARIES2.indexOf(config.adversary);
|
|
6256
|
+
if (adversaryIndex < 0) throw new Error(`Invalid adversary: ${config.adversary}`);
|
|
6257
|
+
foeByteB |= adversaryIndex & 15;
|
|
6258
|
+
const allyIndex = ALLIES.indexOf(config.ally);
|
|
6259
|
+
if (allyIndex < 0) throw new Error(`Invalid ally: ${config.ally}`);
|
|
6260
|
+
let extraByte = 0;
|
|
6261
|
+
if (config.difficulty === "Gritty") extraByte |= 1;
|
|
6262
|
+
for (const expansion of config.expansions) {
|
|
6263
|
+
switch (expansion) {
|
|
6264
|
+
case "Monuments":
|
|
6265
|
+
extraByte |= 2;
|
|
6266
|
+
break;
|
|
6267
|
+
case "Alliances":
|
|
6268
|
+
extraByte |= 4;
|
|
6269
|
+
break;
|
|
6270
|
+
}
|
|
6271
|
+
}
|
|
6272
|
+
if (config.source === "Competitive") extraByte |= 8;
|
|
6273
|
+
const versionByte = 0;
|
|
6274
|
+
const playerCountByte = Math.max(0, Math.min(3, config.playerCount - 1));
|
|
6275
|
+
let seedStr = valueToChar(foeByteA) + valueToChar(foeByteB) + valueToChar(allyIndex) + valueToChar(extraByte) + valueToChar(versionByte) + valueToChar(playerCountByte);
|
|
6276
|
+
let rngValue = 0;
|
|
6277
|
+
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
6278
|
+
const value = Math.floor(Math.random() * BASE);
|
|
6279
|
+
seedStr += valueToChar(value);
|
|
6280
|
+
rngValue += value * Math.round(Math.pow(BASE, i));
|
|
6281
|
+
}
|
|
6282
|
+
const upper = seedStr.toUpperCase();
|
|
6283
|
+
const formatted = `${upper.slice(0, 4)}-${upper.slice(4, 8)}-${upper.slice(8, 12)}`;
|
|
6284
|
+
return { seed: formatted, rngValue };
|
|
6285
|
+
}
|
|
6286
|
+
function encodeSeed(config, rngValue) {
|
|
6287
|
+
let foeByteA = 0;
|
|
6288
|
+
let foeByteB = 0;
|
|
6289
|
+
const tier1Index = TIER1_FOES.indexOf(config.foes[0]);
|
|
6290
|
+
const tier2Index = TIER2_FOES.indexOf(config.foes[1]);
|
|
6291
|
+
const tier3Index = TIER3_FOES.indexOf(config.foes[2]);
|
|
6292
|
+
if (tier1Index < 0) throw new Error(`Invalid Tier 1 foe: ${config.foes[0]}`);
|
|
6293
|
+
if (tier2Index < 0) throw new Error(`Invalid Tier 2 foe: ${config.foes[1]}`);
|
|
6294
|
+
if (tier3Index < 0) throw new Error(`Invalid Tier 3 foe: ${config.foes[2]}`);
|
|
6295
|
+
foeByteA = tier1Index & 3;
|
|
6296
|
+
foeByteA |= (tier2Index & 3) << 2;
|
|
6297
|
+
foeByteA |= (tier3Index & 1) << 4;
|
|
6298
|
+
foeByteB |= (tier3Index >> 1 & 1) << 4;
|
|
6299
|
+
const adversaryIndex = ADVERSARIES2.indexOf(config.adversary);
|
|
6300
|
+
if (adversaryIndex < 0) throw new Error(`Invalid adversary: ${config.adversary}`);
|
|
6301
|
+
foeByteB |= adversaryIndex & 15;
|
|
6302
|
+
const allyIndex = ALLIES.indexOf(config.ally);
|
|
6303
|
+
if (allyIndex < 0) throw new Error(`Invalid ally: ${config.ally}`);
|
|
6304
|
+
let extraByte = 0;
|
|
6305
|
+
if (config.difficulty === "Gritty") extraByte |= 1;
|
|
6306
|
+
for (const expansion of config.expansions) {
|
|
6307
|
+
switch (expansion) {
|
|
6308
|
+
case "Monuments":
|
|
6309
|
+
extraByte |= 2;
|
|
6310
|
+
break;
|
|
6311
|
+
case "Alliances":
|
|
6312
|
+
extraByte |= 4;
|
|
6313
|
+
break;
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
if (config.source === "Competitive") extraByte |= 8;
|
|
6317
|
+
const versionByte = 0;
|
|
6318
|
+
const playerCountByte = Math.max(0, Math.min(3, config.playerCount - 1));
|
|
6319
|
+
let seedStr = valueToChar(foeByteA) + valueToChar(foeByteB) + valueToChar(allyIndex) + valueToChar(extraByte) + valueToChar(versionByte) + valueToChar(playerCountByte);
|
|
6320
|
+
let remaining = rngValue;
|
|
6321
|
+
for (let i = 0; i < RNG_SEED_LENGTH; i++) {
|
|
6322
|
+
const digit = remaining % BASE;
|
|
6323
|
+
seedStr += valueToChar(digit);
|
|
6324
|
+
remaining = Math.floor(remaining / BASE);
|
|
6325
|
+
}
|
|
6326
|
+
const upper = seedStr.toUpperCase();
|
|
6327
|
+
return `${upper.slice(0, 4)}-${upper.slice(4, 8)}-${upper.slice(8, 12)}`;
|
|
6328
|
+
}
|
|
6329
|
+
function compareSeedsRaw(seed1, seed2) {
|
|
6330
|
+
const n1 = validateSeed(seed1);
|
|
6331
|
+
const n2 = validateSeed(seed2);
|
|
6332
|
+
const s1 = n1.replace(/-/g, "").toLowerCase();
|
|
6333
|
+
const s2 = n2.replace(/-/g, "").toLowerCase();
|
|
6334
|
+
const diffs = [];
|
|
6335
|
+
for (let i = 0; i < SEED_LENGTH; i++) {
|
|
6336
|
+
const v1 = charToValue(s1[i]);
|
|
6337
|
+
const v2 = charToValue(s2[i]);
|
|
6338
|
+
if (v1 !== v2) {
|
|
6339
|
+
diffs.push({
|
|
6340
|
+
charIndex: i,
|
|
6341
|
+
value1: v1,
|
|
6342
|
+
value2: v2,
|
|
6343
|
+
char1: s1[i],
|
|
6344
|
+
char2: s2[i]
|
|
6345
|
+
});
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
return {
|
|
6349
|
+
seed1: n1,
|
|
6350
|
+
seed2: n2,
|
|
6351
|
+
diffs,
|
|
6352
|
+
setupDiffs: diffs.filter((d) => d.charIndex < SETUP_LENGTH),
|
|
6353
|
+
rngDiffs: diffs.filter((d) => d.charIndex >= SETUP_LENGTH)
|
|
6354
|
+
};
|
|
6355
|
+
}
|
|
6356
|
+
var SETUP_FIELD_LABELS = {
|
|
6357
|
+
0: "Tier1/Tier2/Tier3lo",
|
|
6358
|
+
1: "Adversary/Tier3hi",
|
|
6359
|
+
2: "Ally",
|
|
6360
|
+
3: "Difficulty/Expansions/Source",
|
|
6361
|
+
4: "Version",
|
|
6362
|
+
5: "PlayerCount"
|
|
6363
|
+
};
|
|
6364
|
+
function dumpSeedChars(seed) {
|
|
6365
|
+
const normalized = validateSeed(seed);
|
|
6366
|
+
const stripped = normalized.replace(/-/g, "").toLowerCase();
|
|
6367
|
+
const chars = [];
|
|
6368
|
+
for (let i = 0; i < SEED_LENGTH; i++) {
|
|
6369
|
+
const isSetup = i < SETUP_LENGTH;
|
|
6370
|
+
chars.push({
|
|
6371
|
+
index: i,
|
|
6372
|
+
char: stripped[i],
|
|
6373
|
+
value: charToValue(stripped[i]),
|
|
6374
|
+
section: isSetup ? "setup" : "rng",
|
|
6375
|
+
field: isSetup ? SETUP_FIELD_LABELS[i] : void 0
|
|
6376
|
+
});
|
|
6377
|
+
}
|
|
6378
|
+
return { seed: normalized, chars };
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
// src/seed/udtSystemRandom.ts
|
|
6382
|
+
var INT32_MAX = 2147483647;
|
|
6383
|
+
var MSEED = 161803398;
|
|
6384
|
+
function toInt32(n) {
|
|
6385
|
+
return n | 0;
|
|
6386
|
+
}
|
|
6387
|
+
var SystemRandom = class {
|
|
6388
|
+
/**
|
|
6389
|
+
* Create a new PRNG instance with the given seed.
|
|
6390
|
+
* Matches C# `new System.Random(seed)` exactly.
|
|
6391
|
+
*/
|
|
6392
|
+
constructor(seed) {
|
|
6393
|
+
this.seedArray = new Array(56).fill(0);
|
|
6394
|
+
this.inext = 0;
|
|
6395
|
+
this.inextp = 0;
|
|
6396
|
+
this.initialize(seed);
|
|
6397
|
+
}
|
|
6398
|
+
/**
|
|
6399
|
+
* Replicate .NET's System.Random constructor seeding algorithm.
|
|
6400
|
+
*/
|
|
6401
|
+
initialize(seed) {
|
|
6402
|
+
let subtraction;
|
|
6403
|
+
if (seed === -2147483648) {
|
|
6404
|
+
subtraction = INT32_MAX;
|
|
6405
|
+
} else {
|
|
6406
|
+
subtraction = Math.abs(seed);
|
|
6407
|
+
}
|
|
6408
|
+
let mj = toInt32(MSEED - subtraction);
|
|
6409
|
+
this.seedArray[55] = mj;
|
|
6410
|
+
let mk = 1;
|
|
6411
|
+
for (let i = 1; i < 55; i++) {
|
|
6412
|
+
const ii = 21 * i % 55;
|
|
6413
|
+
this.seedArray[ii] = mk;
|
|
6414
|
+
mk = toInt32(mj - mk);
|
|
6415
|
+
if (mk < 0) mk = toInt32(mk + INT32_MAX);
|
|
6416
|
+
mj = this.seedArray[ii];
|
|
6417
|
+
}
|
|
6418
|
+
for (let k = 1; k < 5; k++) {
|
|
6419
|
+
for (let i = 1; i < 56; i++) {
|
|
6420
|
+
this.seedArray[i] = toInt32(this.seedArray[i] - this.seedArray[1 + (i + 30) % 55]);
|
|
6421
|
+
if (this.seedArray[i] < 0) {
|
|
6422
|
+
this.seedArray[i] = toInt32(this.seedArray[i] + INT32_MAX);
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
this.inext = 0;
|
|
6427
|
+
this.inextp = 21;
|
|
6428
|
+
}
|
|
6429
|
+
/**
|
|
6430
|
+
* Internal sample — returns value in range [0, Int32.MaxValue).
|
|
6431
|
+
* Matches C#'s InternalSample().
|
|
6432
|
+
*/
|
|
6433
|
+
internalSample() {
|
|
6434
|
+
let retVal;
|
|
6435
|
+
let locINext = this.inext;
|
|
6436
|
+
let locINextp = this.inextp;
|
|
6437
|
+
if (++locINext >= 56) locINext = 1;
|
|
6438
|
+
if (++locINextp >= 56) locINextp = 1;
|
|
6439
|
+
retVal = toInt32(this.seedArray[locINext] - this.seedArray[locINextp]);
|
|
6440
|
+
if (retVal === INT32_MAX) retVal--;
|
|
6441
|
+
if (retVal < 0) retVal = toInt32(retVal + INT32_MAX);
|
|
6442
|
+
this.seedArray[locINext] = retVal;
|
|
6443
|
+
this.inext = locINext;
|
|
6444
|
+
this.inextp = locINextp;
|
|
6445
|
+
return retVal;
|
|
6446
|
+
}
|
|
6447
|
+
/**
|
|
6448
|
+
* Sample — returns a double in range [0.0, 1.0).
|
|
6449
|
+
* Matches C#'s Sample().
|
|
6450
|
+
*/
|
|
6451
|
+
sample() {
|
|
6452
|
+
return this.internalSample() * (1 / INT32_MAX);
|
|
6453
|
+
}
|
|
6454
|
+
/**
|
|
6455
|
+
* Sample for ranges wider than Int32.MaxValue.
|
|
6456
|
+
* Matches C#'s GetSampleForLargeRange(): draws two internal samples (the
|
|
6457
|
+
* second decides sign) and normalizes to [0.0, 1.0).
|
|
6458
|
+
*/
|
|
6459
|
+
getSampleForLargeRange() {
|
|
6460
|
+
let result = this.internalSample();
|
|
6461
|
+
const negative = this.internalSample() % 2 === 0;
|
|
6462
|
+
if (negative) {
|
|
6463
|
+
result = -result;
|
|
6464
|
+
}
|
|
6465
|
+
let d = result;
|
|
6466
|
+
d += INT32_MAX - 1;
|
|
6467
|
+
d /= 2 * INT32_MAX - 1;
|
|
6468
|
+
return d;
|
|
6469
|
+
}
|
|
6470
|
+
/**
|
|
6471
|
+
* Returns a non-negative random integer less than Int32.MaxValue.
|
|
6472
|
+
* Matches C# `Random.Next()`.
|
|
6473
|
+
*/
|
|
6474
|
+
next() {
|
|
6475
|
+
return this.internalSample();
|
|
6476
|
+
}
|
|
6477
|
+
/**
|
|
6478
|
+
* Returns a non-negative random integer less than maxValue.
|
|
6479
|
+
* Matches C# `Random.Next(maxValue)`.
|
|
6480
|
+
*/
|
|
6481
|
+
nextMax(maxValue) {
|
|
6482
|
+
if (maxValue < 0) {
|
|
6483
|
+
throw new Error("maxValue must be non-negative");
|
|
6484
|
+
}
|
|
6485
|
+
return toInt32(this.sample() * maxValue);
|
|
6486
|
+
}
|
|
6487
|
+
/**
|
|
6488
|
+
* Returns a random integer in range [minValue, maxValue).
|
|
6489
|
+
* Matches C# `Random.Next(minValue, maxValue)`.
|
|
6490
|
+
*/
|
|
6491
|
+
nextRange(minValue, maxValue) {
|
|
6492
|
+
if (minValue > maxValue) {
|
|
6493
|
+
throw new Error("minValue must be less than or equal to maxValue");
|
|
6494
|
+
}
|
|
6495
|
+
const range = maxValue - minValue;
|
|
6496
|
+
if (range <= INT32_MAX) {
|
|
6497
|
+
return toInt32(this.sample() * range) + minValue;
|
|
6498
|
+
}
|
|
6499
|
+
return Math.floor(this.getSampleForLargeRange() * range) + minValue;
|
|
6500
|
+
}
|
|
6501
|
+
/**
|
|
6502
|
+
* Returns a random double in range [0.0, 1.0).
|
|
6503
|
+
* Matches C# `Random.NextDouble()`.
|
|
6504
|
+
*/
|
|
6505
|
+
nextDouble() {
|
|
6506
|
+
return this.sample();
|
|
6507
|
+
}
|
|
6508
|
+
};
|
|
6509
|
+
|
|
5434
6510
|
// src/index.ts
|
|
5435
6511
|
var index_default = UltimateDarkTower_default;
|
|
5436
6512
|
export {
|
|
5437
|
-
ADVERSARIES,
|
|
5438
|
-
ADVERSARY_ROSTER,
|
|
5439
|
-
ALLIES,
|
|
5440
|
-
ALL_FOES,
|
|
5441
6513
|
AUDIO_COMMAND_POS,
|
|
5442
6514
|
BATTERY_STATUS_FREQUENCY,
|
|
5443
|
-
BOARD_ADJACENCY,
|
|
5444
|
-
BOARD_ANCHORS,
|
|
5445
|
-
BOARD_GROUPINGS,
|
|
5446
|
-
BOARD_IMAGE_INFO,
|
|
5447
|
-
BOARD_LOCATIONS,
|
|
5448
|
-
BOARD_LOCATION_BY_NAME,
|
|
5449
6515
|
BluetoothAdapterFactory,
|
|
5450
6516
|
BluetoothConnectionError,
|
|
5451
6517
|
BluetoothDeviceNotFoundError,
|
|
@@ -5459,7 +6525,6 @@ export {
|
|
|
5459
6525
|
DEFAULT_CONNECTION_MONITORING_FREQUENCY,
|
|
5460
6526
|
DEFAULT_CONNECTION_MONITORING_TIMEOUT,
|
|
5461
6527
|
DEFAULT_RETRY_SEND_COMMAND_MAX,
|
|
5462
|
-
DIFFICULTIES,
|
|
5463
6528
|
DIS_FIRMWARE_REVISION_UUID,
|
|
5464
6529
|
DIS_HARDWARE_REVISION_UUID,
|
|
5465
6530
|
DIS_IEEE_REGULATORY_UUID,
|
|
@@ -5472,14 +6537,7 @@ export {
|
|
|
5472
6537
|
DIS_SYSTEM_ID_UUID,
|
|
5473
6538
|
DOMOutput,
|
|
5474
6539
|
DRUM_PACKETS,
|
|
5475
|
-
FOES,
|
|
5476
|
-
FOE_BY_ID,
|
|
5477
|
-
FOE_BY_NAME,
|
|
5478
|
-
FOE_STATUSES,
|
|
5479
|
-
GAME_SOURCES,
|
|
5480
6540
|
GLYPHS,
|
|
5481
|
-
HEROES,
|
|
5482
|
-
HERO_BY_ID,
|
|
5483
6541
|
InMemorySink,
|
|
5484
6542
|
IndexedDBSink,
|
|
5485
6543
|
LAYER_TO_POSITION,
|
|
@@ -5488,16 +6546,10 @@ export {
|
|
|
5488
6546
|
LIGHT_EFFECTS,
|
|
5489
6547
|
LIGHT_INDEX_TO_DIRECTION,
|
|
5490
6548
|
Logger,
|
|
5491
|
-
MONUMENTS,
|
|
5492
|
-
MONUMENT_BY_ID,
|
|
5493
6549
|
RING_LIGHT_POSITIONS,
|
|
5494
6550
|
SKULL_DROP_COUNT_POS,
|
|
5495
6551
|
STATE_DATA_LENGTH,
|
|
5496
|
-
SystemRandom,
|
|
5497
6552
|
TC,
|
|
5498
|
-
TIER1_FOES,
|
|
5499
|
-
TIER2_FOES,
|
|
5500
|
-
TIER3_FOES,
|
|
5501
6553
|
TOWER_AUDIO_LIBRARY,
|
|
5502
6554
|
TOWER_COMMANDS,
|
|
5503
6555
|
TOWER_COMMAND_HEADER_SIZE,
|
|
@@ -5520,26 +6572,16 @@ export {
|
|
|
5520
6572
|
VOLUME_DESCRIPTIONS,
|
|
5521
6573
|
VOLUME_ICONS,
|
|
5522
6574
|
bytesToHex,
|
|
5523
|
-
charToValue,
|
|
5524
|
-
compareSeedsRaw,
|
|
5525
6575
|
createDefaultTowerState,
|
|
5526
|
-
|
|
5527
|
-
decodeRngSeed,
|
|
5528
|
-
decodeSeed,
|
|
6576
|
+
data_exports as data,
|
|
5529
6577
|
index_default as default,
|
|
5530
6578
|
drumPositionCmds,
|
|
5531
|
-
dumpSeedChars,
|
|
5532
|
-
encodeSeed,
|
|
5533
6579
|
isCalibrated,
|
|
5534
6580
|
logger,
|
|
5535
6581
|
milliVoltsToPercentage,
|
|
5536
6582
|
milliVoltsToPercentageNumber,
|
|
5537
|
-
neighborsOf,
|
|
5538
6583
|
parseDifferentialReadings,
|
|
5539
6584
|
rtdt_pack_state,
|
|
5540
6585
|
rtdt_unpack_state,
|
|
5541
|
-
|
|
5542
|
-
stepDistance,
|
|
5543
|
-
validateSeed,
|
|
5544
|
-
valueToChar
|
|
6586
|
+
seed_exports as seed
|
|
5545
6587
|
};
|