dredless 0.7.0 → 1.0.0
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/README.md +22 -57
- package/dist/browser/dredless.global.js +794 -98
- package/dist/browser/dredless.global.js.map +4 -4
- package/docs/ACCOUNT.md +146 -0
- package/docs/API_REFERENCE.md +63 -2563
- package/docs/BROWSER_API.md +151 -70
- package/docs/CLIENT.md +183 -0
- package/docs/EVENTS.md +136 -0
- package/docs/GETTING_STARTED.md +135 -0
- package/docs/MACHINES.md +351 -0
- package/docs/MANAGEMENT.md +285 -0
- package/docs/NODE_API.md +120 -318
- package/docs/PLAYER_INVENTORY.md +235 -0
- package/docs/PROTOCOL.md +227 -0
- package/docs/README.md +44 -0
- package/docs/TROUBLESHOOTING.md +91 -0
- package/docs/WORLD_STATE.md +183 -0
- package/package.json +1 -2
- package/src/browser.d.ts +31 -1
- package/src/browser.js +10 -0
- package/src/client/core.js +302 -15
- package/src/client/domains/index.js +57 -7
- package/src/client/index.d.ts +1 -1
- package/src/index.d.ts +299 -13
- package/src/index.js +10 -0
- package/src/network/account.js +182 -0
- package/src/network/disconnect.d.ts +2 -0
- package/src/network/disconnect.js +53 -0
- package/src/network/index.d.ts +1 -0
- package/src/network/index.js +1 -0
- package/src/network/invites.d.ts +0 -3
- package/src/network/invites.js +3 -40
- package/src/network/session.d.ts +2 -0
- package/src/network/session.js +21 -0
- package/src/protocol/outbound/ship-management.d.ts +66 -1
- package/src/protocol/outbound/ship-management.js +83 -0
- package/src/state/model/decoder.js +70 -10
- package/src/state/model/index.d.ts +4 -0
- package/src/state/model/state.js +22 -7
- package/src/state/world/index.js +27 -1
|
@@ -232,9 +232,9 @@
|
|
|
232
232
|
if (host === "local.drednot.io") return "local_";
|
|
233
233
|
return "";
|
|
234
234
|
}
|
|
235
|
-
function parseCookies(
|
|
235
|
+
function parseCookies(text2 = "") {
|
|
236
236
|
const cookies = /* @__PURE__ */ new Map();
|
|
237
|
-
for (const part of String(
|
|
237
|
+
for (const part of String(text2).split(/;\s*/)) {
|
|
238
238
|
const index = part.indexOf("=");
|
|
239
239
|
if (index <= 0) continue;
|
|
240
240
|
cookies.set(part.slice(0, index).trim(), decodeURIComponent(part.slice(index + 1).trim()));
|
|
@@ -396,8 +396,8 @@
|
|
|
396
396
|
async function fetchNoticeVersion(baseUrl = DEFAULT_BASE_URL) {
|
|
397
397
|
const response = await new HttpClient({ baseUrl }).request("index.html");
|
|
398
398
|
if (!response.ok) throw new Error(`Failed to fetch notice version: ${response.status} ${response.statusText}`);
|
|
399
|
-
const
|
|
400
|
-
const version = parseNoticeVersion(
|
|
399
|
+
const text2 = await response.text();
|
|
400
|
+
const version = parseNoticeVersion(text2);
|
|
401
401
|
if (version == null) throw new Error("Unable to locate notice version in drednot page");
|
|
402
402
|
return version;
|
|
403
403
|
}
|
|
@@ -411,14 +411,14 @@
|
|
|
411
411
|
if (server2 && typeof server2 === "object") return Number(server2.index ?? server2.id);
|
|
412
412
|
return Number(server2);
|
|
413
413
|
}
|
|
414
|
-
function parseGameVersion(
|
|
415
|
-
return
|
|
414
|
+
function parseGameVersion(text2) {
|
|
415
|
+
return text2.match(/Object\.defineProperty\(exports,"GAME_VERSION",\{enumerable:true,get:\(\)=>e\}\);const e="([^"]+)"/)?.[1] || null;
|
|
416
416
|
}
|
|
417
|
-
function parseNoticeVersion(
|
|
418
|
-
const moduleMatch =
|
|
417
|
+
function parseNoticeVersion(text2) {
|
|
418
|
+
const moduleMatch = text2.match(/Object\.defineProperty\(exports,"checkNotice"[\s\S]{0,8000}?setCookie\)\("notice_version",([A-Za-z_$][\w$]*)\)/);
|
|
419
419
|
if (moduleMatch) {
|
|
420
420
|
const constant = moduleMatch[1];
|
|
421
|
-
const prefix =
|
|
421
|
+
const prefix = text2.slice(Math.max(0, moduleMatch.index - 1e3), moduleMatch.index + moduleMatch[0].length);
|
|
422
422
|
const constantMatch = prefix.match(new RegExp(`const\\s+${constant}\\s*=\\s*(\\d+)\\s*;`));
|
|
423
423
|
if (constantMatch) return Number(constantMatch[1]);
|
|
424
424
|
}
|
|
@@ -429,14 +429,14 @@
|
|
|
429
429
|
/notice[^0-9]{0,40}version[^0-9]{0,20}(\d+)/i
|
|
430
430
|
];
|
|
431
431
|
for (const pattern of patterns) {
|
|
432
|
-
const match =
|
|
432
|
+
const match = text2.match(pattern);
|
|
433
433
|
if (match) return Number(match[1]);
|
|
434
434
|
}
|
|
435
435
|
return null;
|
|
436
436
|
}
|
|
437
|
-
function parseServerInfo(
|
|
437
|
+
function parseServerInfo(text2, baseUrl) {
|
|
438
438
|
const host = hostName(baseUrl);
|
|
439
|
-
const match =
|
|
439
|
+
const match = text2.match(/const n=(true|false);const \$="([^"]+)";const o="([^"]+)";const T="([^"]+)";const t="([^"]+)";const S=\[([^\]]+)\];const c=\[([^\]]+)\];const A="([^"]+)";const I="([^"]+)"/s);
|
|
440
440
|
if (!match) return null;
|
|
441
441
|
const [localDomain, baseDomain, testDomain, testGameDomain] = match.slice(2, 6);
|
|
442
442
|
const domains = [...match[6].matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((m) => m[1]);
|
|
@@ -512,8 +512,8 @@
|
|
|
512
512
|
async #fetchMeta() {
|
|
513
513
|
const response = await new HttpClient({ baseUrl: this.baseUrl }).request("index.html");
|
|
514
514
|
if (!response.ok) throw new Error(`Failed to fetch bundle: ${response.status} ${response.statusText}`);
|
|
515
|
-
const
|
|
516
|
-
return { text, version: parseGameVersion(
|
|
515
|
+
const text2 = await response.text();
|
|
516
|
+
return { text: text2, version: parseGameVersion(text2), serverInfo: parseServerInfo(text2, this.baseUrl) };
|
|
517
517
|
}
|
|
518
518
|
};
|
|
519
519
|
fetchGameVersion = (baseUrl = DEFAULT_BASE_URL) => new ServerDirectory(baseUrl).version();
|
|
@@ -550,6 +550,57 @@
|
|
|
550
550
|
}
|
|
551
551
|
};
|
|
552
552
|
|
|
553
|
+
// src/network/disconnect.js
|
|
554
|
+
var DropReason = Object.freeze({
|
|
555
|
+
ProtocolError: 4e3,
|
|
556
|
+
BadVersion: 4001,
|
|
557
|
+
ConnectLimit: 4002,
|
|
558
|
+
ConnectRate: 4003,
|
|
559
|
+
TeamLimit: 4004,
|
|
560
|
+
FullPlayers: 4005,
|
|
561
|
+
FullTeams: 4006,
|
|
562
|
+
TeamNotAvailable: 4007,
|
|
563
|
+
Kicked: 4008,
|
|
564
|
+
Banned: 4009,
|
|
565
|
+
Idle: 4010,
|
|
566
|
+
ShipDestruct: 4011,
|
|
567
|
+
JoinBlocked: 4012,
|
|
568
|
+
HardBanned: 4013,
|
|
569
|
+
NoLoad: 4014,
|
|
570
|
+
Saved: 4015,
|
|
571
|
+
InvalidInvite: 4016,
|
|
572
|
+
Shutdown: 4017,
|
|
573
|
+
ServerError: 4018,
|
|
574
|
+
GTFO: 4019
|
|
575
|
+
});
|
|
576
|
+
var reasonNames = new Map(Object.entries(DropReason).map(([name, code]) => [code, name]));
|
|
577
|
+
var textDecoder = new TextDecoder();
|
|
578
|
+
function normalizeDisconnect(eventOrCode, reason = "") {
|
|
579
|
+
const event = eventOrCode != null && typeof eventOrCode === "object" ? eventOrCode : null;
|
|
580
|
+
const code = event?.code ?? eventOrCode;
|
|
581
|
+
const rawReason = event?.reason ?? reason;
|
|
582
|
+
let text2 = "";
|
|
583
|
+
if (typeof rawReason === "string") text2 = rawReason;
|
|
584
|
+
else if (ArrayBuffer.isView(rawReason)) {
|
|
585
|
+
text2 = textDecoder.decode(new Uint8Array(rawReason.buffer, rawReason.byteOffset, rawReason.byteLength));
|
|
586
|
+
} else if (rawReason instanceof ArrayBuffer) text2 = textDecoder.decode(rawReason);
|
|
587
|
+
const normalizedCode = Number.isInteger(code) ? code : 1006;
|
|
588
|
+
return Object.freeze({
|
|
589
|
+
code: normalizedCode,
|
|
590
|
+
name: reasonNames.get(normalizedCode) ?? null,
|
|
591
|
+
reason: text2,
|
|
592
|
+
// EventEmitter close callbacks don't report whether the handshake was clean.
|
|
593
|
+
wasClean: typeof event?.wasClean === "boolean" ? event.wasClean : null
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
var DisconnectError = class extends Error {
|
|
597
|
+
constructor(disconnect, message = "Disconnected") {
|
|
598
|
+
super(`${message}: ${disconnect.name ?? "Unknown"} (${disconnect.code})${disconnect.reason ? ` \u2014 ${disconnect.reason}` : ""}`);
|
|
599
|
+
this.name = "DisconnectError";
|
|
600
|
+
this.disconnect = disconnect;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
553
604
|
// src/client/core.js
|
|
554
605
|
init_constants();
|
|
555
606
|
init_runtime();
|
|
@@ -588,16 +639,68 @@
|
|
|
588
639
|
}
|
|
589
640
|
};
|
|
590
641
|
|
|
642
|
+
// src/protocol/outbound/comms.js
|
|
643
|
+
var COMMS_MESSAGE_TYPE = 3;
|
|
644
|
+
function buildCommsMessage(message = "") {
|
|
645
|
+
return {
|
|
646
|
+
type: COMMS_MESSAGE_TYPE,
|
|
647
|
+
msg: String(message)
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function normalizeCommsEvent(event) {
|
|
651
|
+
const rawMessages = Array.isArray(event?.msgs_text) ? event.msgs_text : [];
|
|
652
|
+
return {
|
|
653
|
+
...event,
|
|
654
|
+
entity: event?.ent_id != null && Number.isFinite(Number(event.ent_id)) ? Number(event.ent_id) : null,
|
|
655
|
+
rawMessages,
|
|
656
|
+
messages: rawMessages.map((message) => ({
|
|
657
|
+
raw: message,
|
|
658
|
+
text: flattenRichText(message)
|
|
659
|
+
}))
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function flattenRichText(value) {
|
|
663
|
+
if (value == null) return "";
|
|
664
|
+
if (typeof value === "string") return value;
|
|
665
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
666
|
+
if (Array.isArray(value)) return value.map(flattenRichText).join("");
|
|
667
|
+
if (typeof value === "object") {
|
|
668
|
+
if (Object.hasOwn(value, "c")) return flattenRichText(value.c);
|
|
669
|
+
if (Object.hasOwn(value, "text")) return flattenRichText(value.text);
|
|
670
|
+
}
|
|
671
|
+
return "";
|
|
672
|
+
}
|
|
673
|
+
|
|
591
674
|
// src/protocol/outbound/ship-management.js
|
|
592
675
|
var SHIP_MANAGEMENT_TYPE = 4;
|
|
676
|
+
var CREW_LOG_LENGTH = 500;
|
|
593
677
|
var SET_PRIVACY_ACTION = "set_privacy";
|
|
678
|
+
var GRAVITY_ACTION = "gravity";
|
|
679
|
+
var SET_PATRON_PERKS_ACTION = "set_patron_perks";
|
|
594
680
|
var STARTER_RECOVERY_ACTION = "starter_recovery";
|
|
595
681
|
var PLAYER_LIST_ACTION = "player_list";
|
|
596
682
|
var INVITE_RESET_ACTION = "invite_reset";
|
|
597
683
|
var SET_RANK_ACTION = "set_rank";
|
|
598
684
|
var KICK_ACTION = "kick";
|
|
599
685
|
var BAN_ACTION = "ban";
|
|
686
|
+
var UNBAN_ACTION = "unban";
|
|
600
687
|
var DEMOTE_SELF_ACTION = "demote_self";
|
|
688
|
+
var SAVE_SHIP_ACTION = "save_team";
|
|
689
|
+
var RENAME_REQUEST_ACTION = "rename_request";
|
|
690
|
+
var RENAME_COMMIT_ACTION = "rename_commit";
|
|
691
|
+
var GravityDirection = Object.freeze({
|
|
692
|
+
Down: "down",
|
|
693
|
+
Up: "up",
|
|
694
|
+
Left: "left",
|
|
695
|
+
Right: "right"
|
|
696
|
+
});
|
|
697
|
+
function normalizeGravityDirection(value) {
|
|
698
|
+
if (value === GravityDirection.Down || value === 0) return 0;
|
|
699
|
+
if (value === GravityDirection.Up || value === 1) return 1;
|
|
700
|
+
if (value === GravityDirection.Left || value === 2) return 2;
|
|
701
|
+
if (value === GravityDirection.Right || value === 3) return 3;
|
|
702
|
+
throw new RangeError('direction must be "down", "up", "left", "right", or an integer from 0 to 3');
|
|
703
|
+
}
|
|
601
704
|
function requireNonNegativeInteger(value, name) {
|
|
602
705
|
const number = Number(value);
|
|
603
706
|
if (!Number.isInteger(number) || number < 0) throw new RangeError(`${name} must be a non-negative integer`);
|
|
@@ -626,6 +729,13 @@
|
|
|
626
729
|
function buildShipPrivacyMessage(privacy) {
|
|
627
730
|
return buildShipManagementMessage(SET_PRIVACY_ACTION, normalizePrivacy(privacy));
|
|
628
731
|
}
|
|
732
|
+
function buildShipGravityMessage(direction) {
|
|
733
|
+
return buildShipManagementMessage(GRAVITY_ACTION, normalizeGravityDirection(direction));
|
|
734
|
+
}
|
|
735
|
+
function buildSetPatronPerksMessage(enabled) {
|
|
736
|
+
if (typeof enabled !== "boolean") throw new TypeError("enabled must be a boolean");
|
|
737
|
+
return buildShipManagementMessage(SET_PATRON_PERKS_ACTION, enabled);
|
|
738
|
+
}
|
|
629
739
|
function buildStarterRecoveryMessage(itemId) {
|
|
630
740
|
return buildShipManagementMessage(STARTER_RECOVERY_ACTION, requireNonNegativeInteger(itemId, "itemId"));
|
|
631
741
|
}
|
|
@@ -644,9 +754,26 @@
|
|
|
644
754
|
function buildBanPlayerMessage(refId) {
|
|
645
755
|
return buildShipManagementMessage(BAN_ACTION, normalizePlayerRef(refId));
|
|
646
756
|
}
|
|
757
|
+
function buildUnbanPlayerMessage(refId) {
|
|
758
|
+
return buildShipManagementMessage(UNBAN_ACTION, normalizePlayerRef(refId));
|
|
759
|
+
}
|
|
647
760
|
function buildDemoteSelfMessage() {
|
|
648
761
|
return buildShipManagementMessage(DEMOTE_SELF_ACTION);
|
|
649
762
|
}
|
|
763
|
+
function buildSaveShipMessage() {
|
|
764
|
+
return buildShipManagementMessage(SAVE_SHIP_ACTION);
|
|
765
|
+
}
|
|
766
|
+
function buildShipRenameRequestMessage() {
|
|
767
|
+
return buildShipManagementMessage(RENAME_REQUEST_ACTION);
|
|
768
|
+
}
|
|
769
|
+
function buildShipRenameCommitMessage(name, color) {
|
|
770
|
+
if (typeof name !== "string") throw new TypeError("name must be a string");
|
|
771
|
+
if (name.length > 32) throw new RangeError("name must be at most 32 UTF-16 code units");
|
|
772
|
+
if (typeof color !== "string" || !/^#[0-9a-f]{6}$/i.test(color)) {
|
|
773
|
+
throw new TypeError("color must be a #RRGGBB hex string");
|
|
774
|
+
}
|
|
775
|
+
return buildShipManagementMessage(RENAME_COMMIT_ACTION, name, { arg2: color });
|
|
776
|
+
}
|
|
650
777
|
function normalizeShipConfig(event = {}) {
|
|
651
778
|
const source = event && typeof event === "object" ? event : {};
|
|
652
779
|
const config = source.config && typeof source.config === "object" ? source.config : {};
|
|
@@ -665,6 +792,27 @@
|
|
|
665
792
|
enableCheats: Boolean(source.enable_cheats)
|
|
666
793
|
};
|
|
667
794
|
}
|
|
795
|
+
function normalizeCrewLog(event = {}, previous = null) {
|
|
796
|
+
const source = event && typeof event === "object" ? event : {};
|
|
797
|
+
const reset = Boolean(source.reset);
|
|
798
|
+
const amend = Boolean(source.amend);
|
|
799
|
+
const entries = reset ? [] : (previous?.entries || []).slice();
|
|
800
|
+
if (amend) entries.pop();
|
|
801
|
+
const changes = Array.isArray(source.entries) ? source.entries.map((entry) => {
|
|
802
|
+
const value = entry && typeof entry === "object" ? entry : {};
|
|
803
|
+
return {
|
|
804
|
+
icon: value.icon ?? null,
|
|
805
|
+
text: flattenRichText(value.text),
|
|
806
|
+
rawText: value.text ?? null,
|
|
807
|
+
count: value.count ?? 1,
|
|
808
|
+
refId: value.ref ?? null,
|
|
809
|
+
secondaryRefId: value.ref2 ?? null
|
|
810
|
+
};
|
|
811
|
+
}) : [];
|
|
812
|
+
for (const entry of changes) entries.push(entry);
|
|
813
|
+
if (entries.length > CREW_LOG_LENGTH) entries.splice(0, entries.length - CREW_LOG_LENGTH);
|
|
814
|
+
return { entries, changes, reset, amend };
|
|
815
|
+
}
|
|
668
816
|
function normalizeShipPlayerList(event = {}, previous = null, currentCaptainSubrank = null) {
|
|
669
817
|
const source = event && typeof event === "object" ? event : {};
|
|
670
818
|
const changes = Array.isArray(source.player_list) ? source.player_list.map(normalizePlayerListChange) : [];
|
|
@@ -934,7 +1082,7 @@
|
|
|
934
1082
|
const timeout = timeoutValue(options);
|
|
935
1083
|
if (!handle.exists()) throw new Error("Rank target no longer exists");
|
|
936
1084
|
if (handle.rank === value) return this.client;
|
|
937
|
-
const
|
|
1085
|
+
const text2 = { 0: "Restrict to Guests", 1: "Restrict to Crew", 3: "Restrict to Captains" }[value];
|
|
938
1086
|
await this.#open(handle, options);
|
|
939
1087
|
await waitFor(
|
|
940
1088
|
this.client,
|
|
@@ -942,7 +1090,7 @@
|
|
|
942
1090
|
() => handle.rank === value,
|
|
943
1091
|
timeout,
|
|
944
1092
|
"access rank update",
|
|
945
|
-
() => this.#select(
|
|
1093
|
+
() => this.#select(text2)
|
|
946
1094
|
);
|
|
947
1095
|
return this.client;
|
|
948
1096
|
});
|
|
@@ -3550,8 +3698,8 @@
|
|
|
3550
3698
|
}
|
|
3551
3699
|
return out;
|
|
3552
3700
|
}
|
|
3553
|
-
function encodeStringBytes(
|
|
3554
|
-
const bytes = encoder.encode(String(
|
|
3701
|
+
function encodeStringBytes(text2) {
|
|
3702
|
+
const bytes = encoder.encode(String(text2));
|
|
3555
3703
|
const length = bytes.length;
|
|
3556
3704
|
if (length <= 31) {
|
|
3557
3705
|
const out2 = new Uint8Array(1 + length);
|
|
@@ -4453,8 +4601,8 @@
|
|
|
4453
4601
|
if (end > reader.bytes.length) return false;
|
|
4454
4602
|
const blob = reader.bytes.slice(peek.offset, end);
|
|
4455
4603
|
try {
|
|
4456
|
-
const
|
|
4457
|
-
return
|
|
4604
|
+
const text2 = decoder.decode(blob);
|
|
4605
|
+
return text2.length > 0 && !/[\u0000-\u0008\u000e-\u001f]/.test(text2);
|
|
4458
4606
|
} catch (_) {
|
|
4459
4607
|
return false;
|
|
4460
4608
|
}
|
|
@@ -4646,6 +4794,7 @@
|
|
|
4646
4794
|
}],
|
|
4647
4795
|
[55, {
|
|
4648
4796
|
name: "player",
|
|
4797
|
+
defaults: { q20: 1, q60: 10, q64: 10, q68: 10 },
|
|
4649
4798
|
packedBeforeValues: true,
|
|
4650
4799
|
fields: numericFields({
|
|
4651
4800
|
256: 20,
|
|
@@ -4680,8 +4829,10 @@
|
|
|
4680
4829
|
]
|
|
4681
4830
|
}],
|
|
4682
4831
|
[56, {
|
|
4683
|
-
name: "
|
|
4684
|
-
|
|
4832
|
+
name: "player_appearance",
|
|
4833
|
+
// Official StateOutfit: four body colors, then four color/style pairs.
|
|
4834
|
+
defaults: { q20: 0, q24: 0, q28: 0, q32: 0, q36: 0, q40: 0, q44: 0, q48: 0, q52: 0, q56: 0, q60: 0, q64: 0 },
|
|
4835
|
+
fields: numericFields({ 1: 20, 2: 24, 4: 28, 8: 32, 16: 36, 32: 40, 64: 44, 128: 48, 256: 52, 512: 56, 1024: 60, 2048: 64 })
|
|
4685
4836
|
}],
|
|
4686
4837
|
[59, { name: "numeric_sparse", fields: numericFields({ 1: 20, 2: 24, 4: 28, 8: 32, 16: 36 }) }],
|
|
4687
4838
|
[60, { name: "fluid_tank", packedBits: [{ bit: 4, offset: 29 }], ...TWO_FIELD_SPEC }],
|
|
@@ -5063,7 +5214,9 @@
|
|
|
5063
5214
|
const tau = 2 * Math.PI;
|
|
5064
5215
|
const absoluteAngleRadians = relativeAngleRadians == null || mountingAngleRadians == null ? null : ((mountingAngleRadians + relativeAngleRadians) % tau + tau) % tau;
|
|
5065
5216
|
const ammoItemId = record.q24 == null ? null : CANNON_AMMO_COLOR_ITEM_IDS.get(record.q24) ?? null;
|
|
5066
|
-
const
|
|
5217
|
+
const indicatorValue = (record.q40 ?? 0) / 50;
|
|
5218
|
+
const indicatorVisible = indicatorValue >= 1;
|
|
5219
|
+
const indicatorBlinking = indicatorValue === 4;
|
|
5067
5220
|
const recoil = record.q36 ?? null;
|
|
5068
5221
|
const recoil2 = record.q48 ?? null;
|
|
5069
5222
|
return {
|
|
@@ -5078,13 +5231,21 @@
|
|
|
5078
5231
|
recoil,
|
|
5079
5232
|
recoil2,
|
|
5080
5233
|
recoils: [recoil, recoil2],
|
|
5081
|
-
|
|
5082
|
-
|
|
5234
|
+
indicatorValue,
|
|
5235
|
+
indicatorVisible,
|
|
5236
|
+
indicatorColor: !indicatorVisible ? null : indicatorBlinking ? 16711680 : cannonIndicatorColor(indicatorValue),
|
|
5237
|
+
indicatorBlinking,
|
|
5238
|
+
indicatorBlinkColor: indicatorBlinking ? 16776960 : null,
|
|
5083
5239
|
spin: record.q52 ?? null,
|
|
5084
|
-
|
|
5240
|
+
resourceCount: record.q56 ?? 0,
|
|
5085
5241
|
state: cloneRecord(record)
|
|
5086
5242
|
};
|
|
5087
5243
|
}
|
|
5244
|
+
function cannonIndicatorColor(value) {
|
|
5245
|
+
const distance = Math.abs(Math.fround(2 - Math.fround(value)));
|
|
5246
|
+
const channel = Math.max(0, Math.min(255, Math.trunc(Math.fround(Math.fround(1 - distance) * 255))));
|
|
5247
|
+
return value < 2 ? channel << 16 | 65280 : 16711680 | channel << 8;
|
|
5248
|
+
}
|
|
5088
5249
|
function summarizeThruster(entity, typeId, record) {
|
|
5089
5250
|
if (!THRUSTER_TYPE_IDS.has(typeId)) return null;
|
|
5090
5251
|
const facing = Number.isFinite(Number(record?.q20)) ? Number(record.q20) : 0;
|
|
@@ -5662,7 +5823,36 @@
|
|
|
5662
5823
|
state: cloneRecord(record)
|
|
5663
5824
|
};
|
|
5664
5825
|
}
|
|
5665
|
-
function
|
|
5826
|
+
function summarizePlayerAppearance(record, player) {
|
|
5827
|
+
if (!record) return null;
|
|
5828
|
+
const style = (colorOffset, styleOffset) => ({
|
|
5829
|
+
color: record[qKey(colorOffset)] ?? 0,
|
|
5830
|
+
styleId: record[qKey(styleOffset)] ?? 0
|
|
5831
|
+
});
|
|
5832
|
+
const override = (offset) => {
|
|
5833
|
+
const id = player[qKey(offset)] ?? 0;
|
|
5834
|
+
return id === 0 ? null : { itemId: id, itemName: entityNameFromType(id) };
|
|
5835
|
+
};
|
|
5836
|
+
return {
|
|
5837
|
+
bodyColor: record.q20 ?? 0,
|
|
5838
|
+
legsColor: record.q24 ?? 0,
|
|
5839
|
+
skinColor: record.q28 ?? 0,
|
|
5840
|
+
feetColor: record.q32 ?? 0,
|
|
5841
|
+
hair: style(36, 40),
|
|
5842
|
+
hat: style(44, 48),
|
|
5843
|
+
face1: style(52, 56),
|
|
5844
|
+
face2: style(60, 64),
|
|
5845
|
+
itemOverrides: {
|
|
5846
|
+
back: override(36),
|
|
5847
|
+
hands: override(40),
|
|
5848
|
+
feet: override(44),
|
|
5849
|
+
head1: override(48),
|
|
5850
|
+
head2: override(52)
|
|
5851
|
+
},
|
|
5852
|
+
state: cloneRecord(record)
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
function summarizePlayer(entity, record, previewRecord = null, blueprintItems = [], appearanceRecord = null, localPlayerEntity = null) {
|
|
5666
5856
|
if (!record) return null;
|
|
5667
5857
|
const rawHeldItemId = record.q28 == null ? null : Number(record.q28);
|
|
5668
5858
|
const heldItemId = Number.isFinite(rawHeldItemId) && rawHeldItemId !== 0 ? rawHeldItemId : null;
|
|
@@ -5673,14 +5863,29 @@
|
|
|
5673
5863
|
name: decodeText(record.blob92),
|
|
5674
5864
|
heldItemId,
|
|
5675
5865
|
heldItemName: entityNameFromType(heldItemId),
|
|
5676
|
-
|
|
5677
|
-
|
|
5866
|
+
itemMode: record.q32 ?? null,
|
|
5867
|
+
buildMultiplier: numberOrNull(record.q68, 10),
|
|
5868
|
+
heldItemBeamDistance: numberOrNull(record.q56, 10),
|
|
5869
|
+
heldItemAngle: numberOrNull(record.q80, 200),
|
|
5678
5870
|
shipRank: PLAYER_SHIP_RANKS.get(teamRank) || null,
|
|
5679
5871
|
patronTier: patronTierName(gameRank),
|
|
5680
5872
|
isDeveloper: gameRank === 1,
|
|
5681
5873
|
isPatron: gameRank >= 2 && gameRank <= 6,
|
|
5682
|
-
|
|
5874
|
+
isLocal: entity === localPlayerEntity,
|
|
5875
|
+
zoomedOut: Boolean(record.q107),
|
|
5683
5876
|
muted: Boolean(record.q112),
|
|
5877
|
+
invisible: Boolean(record.q106),
|
|
5878
|
+
cheats: {
|
|
5879
|
+
noclip: Boolean(record.q105),
|
|
5880
|
+
sandboxBuild: Boolean(record.q108)
|
|
5881
|
+
},
|
|
5882
|
+
itemEffectActive: Boolean(record.q111),
|
|
5883
|
+
animation: {
|
|
5884
|
+
facing: record.q20 ?? 1,
|
|
5885
|
+
orientation: record.q24 ?? 0,
|
|
5886
|
+
walking: Boolean(record.q109)
|
|
5887
|
+
},
|
|
5888
|
+
appearance: summarizePlayerAppearance(appearanceRecord, record),
|
|
5684
5889
|
actionPreview: summarizePlayerPreview(entity, previewRecord, blueprintItems),
|
|
5685
5890
|
state: cloneRecord(record)
|
|
5686
5891
|
};
|
|
@@ -5938,6 +6143,7 @@
|
|
|
5938
6143
|
defineLazyProperty: defineLazyProperty2
|
|
5939
6144
|
} = decoder_exports;
|
|
5940
6145
|
var ModelState = class {
|
|
6146
|
+
#localPlayerEntity = null;
|
|
5941
6147
|
#blueprintItems = null;
|
|
5942
6148
|
#helmOccupied = /* @__PURE__ */ new Map();
|
|
5943
6149
|
#commsStationOccupied = /* @__PURE__ */ new Map();
|
|
@@ -5959,6 +6165,16 @@
|
|
|
5959
6165
|
this.isOverworld = next;
|
|
5960
6166
|
this.#invalidateDerived();
|
|
5961
6167
|
}
|
|
6168
|
+
get localPlayerEntity() {
|
|
6169
|
+
return this.#localPlayerEntity;
|
|
6170
|
+
}
|
|
6171
|
+
setLocalPlayerEntity(entity) {
|
|
6172
|
+
const next = entity == null ? null : Number(entity);
|
|
6173
|
+
if (next !== null && (!Number.isInteger(next) || next < 0)) throw new RangeError("local player entity must be a non-negative integer or null");
|
|
6174
|
+
if (this.#localPlayerEntity === next) return;
|
|
6175
|
+
this.#localPlayerEntity = next;
|
|
6176
|
+
this.#invalidateDerived();
|
|
6177
|
+
}
|
|
5962
6178
|
table(id) {
|
|
5963
6179
|
return this.tables.get(Number(id)) || EMPTY_TABLE2;
|
|
5964
6180
|
}
|
|
@@ -6197,25 +6413,25 @@
|
|
|
6197
6413
|
#updateHelmOccupancy(update) {
|
|
6198
6414
|
const table7 = update.sections.find((section) => section.table === 7);
|
|
6199
6415
|
if (!table7) return;
|
|
6200
|
-
const
|
|
6416
|
+
const changedZoomState = /* @__PURE__ */ new Map();
|
|
6201
6417
|
for (const section of update.sections) {
|
|
6202
6418
|
if (section.table !== 55) continue;
|
|
6203
6419
|
for (const changed of section.records) {
|
|
6204
6420
|
if (!(changed.mask & 16)) continue;
|
|
6205
6421
|
const player = this.record(55, changed.entity);
|
|
6206
|
-
|
|
6422
|
+
changedZoomState.set(changed.entity, Boolean(player?.q107));
|
|
6207
6423
|
}
|
|
6208
6424
|
}
|
|
6209
|
-
const
|
|
6425
|
+
const zoomedOutPlayerCount = [...this.table(55).values()].filter((record) => record?.q107).length;
|
|
6210
6426
|
for (const changed of table7.records) {
|
|
6211
6427
|
const record = this.record(7, changed.entity);
|
|
6212
6428
|
const typeId = entityTypeIdFromRecord2(record);
|
|
6213
6429
|
if (!HELM_TYPE_IDS2.has(typeId)) continue;
|
|
6214
|
-
if (
|
|
6430
|
+
if (zoomedOutPlayerCount > 0 && record?.q20 === 0 && HELM_TYPE_IDS2.has(Number(record?.q32))) {
|
|
6215
6431
|
this.#helmOccupied.set(changed.entity, true);
|
|
6216
6432
|
}
|
|
6217
|
-
if (!(changed.mask & 8) ||
|
|
6218
|
-
this.#helmOccupied.set(changed.entity, [...
|
|
6433
|
+
if (!(changed.mask & 8) || changedZoomState.size === 0) continue;
|
|
6434
|
+
this.#helmOccupied.set(changed.entity, [...changedZoomState.values()].some(Boolean));
|
|
6219
6435
|
}
|
|
6220
6436
|
}
|
|
6221
6437
|
#updateCommsStationOccupancy(update) {
|
|
@@ -6545,7 +6761,7 @@
|
|
|
6545
6761
|
const shieldGenerator = typeId === 256 ? summarizeShieldGenerator2(entityId, shieldRecord, itemHolderRecord, shieldGeneratorBoostRecord) : null;
|
|
6546
6762
|
const shieldProjector = typeId === 257 ? summarizeShieldProjector2(entityId, shieldProjectorRecord) : null;
|
|
6547
6763
|
const blueprintPreview = summarizeBlueprintPreview2(entityId, blueprintPreviewRecord, transformRecord);
|
|
6548
|
-
const player = summarizePlayer2(entityId, playerRecord, rec(14), this.#blueprintPreviewItems());
|
|
6764
|
+
const player = summarizePlayer2(entityId, playerRecord, rec(14), this.#blueprintPreviewItems(), rec(56), this.#localPlayerEntity);
|
|
6549
6765
|
const shipControl = summarizeShipControl2(entityId, shipControlRecord);
|
|
6550
6766
|
const helm = summarizeHelm2(entityId, typeId, this.#helmOccupied.get(entityId));
|
|
6551
6767
|
const commsStation = summarizeCommsStation2(entityId, typeId, commsStationRecord, this.#commsStationOccupied.get(entityId));
|
|
@@ -7362,6 +7578,7 @@
|
|
|
7362
7578
|
var WorldStore = class {
|
|
7363
7579
|
#overworldId = null;
|
|
7364
7580
|
#shipWorldId = null;
|
|
7581
|
+
#cameraFocus = null;
|
|
7365
7582
|
constructor(limits = {}) {
|
|
7366
7583
|
this.currentWorldId = null;
|
|
7367
7584
|
this.worlds = /* @__PURE__ */ new Map();
|
|
@@ -7378,6 +7595,17 @@
|
|
|
7378
7595
|
}
|
|
7379
7596
|
apply(packet) {
|
|
7380
7597
|
if (!packet || packet.world == null) return null;
|
|
7598
|
+
if (packet.type === 21) {
|
|
7599
|
+
if (this.#cameraFocus && this.#cameraFocus.world !== packet.world) {
|
|
7600
|
+
this.worlds.get(Number(this.#cameraFocus.world))?.model.setLocalPlayerEntity(null);
|
|
7601
|
+
}
|
|
7602
|
+
this.#cameraFocus = packet;
|
|
7603
|
+
this.currentWorldId = Number(packet.world);
|
|
7604
|
+
const world = this.get(packet.world);
|
|
7605
|
+
world.model.setLocalPlayerEntity(null);
|
|
7606
|
+
this.#resolveLocalPlayer(world);
|
|
7607
|
+
return { type: "camera-focus", world, packet };
|
|
7608
|
+
}
|
|
7381
7609
|
if (packet.type === 22) return this.#applyMeta(packet);
|
|
7382
7610
|
if (packet.type === 23) return this.#applyTiles(packet);
|
|
7383
7611
|
if (packet.type === 20) return this.#applyModel(packet);
|
|
@@ -7423,16 +7651,25 @@
|
|
|
7423
7651
|
if (!shipWorld || shipWorld.parentWorld == null || shipWorld.parentEntity == null) return null;
|
|
7424
7652
|
return this.worlds.get(Number(shipWorld.parentWorld))?.entity(shipWorld.parentEntity) || null;
|
|
7425
7653
|
}
|
|
7654
|
+
#resolveLocalPlayer(world) {
|
|
7655
|
+
if (!this.#cameraFocus || Number(this.#cameraFocus.world) !== world.id || world.seed == null) return;
|
|
7656
|
+
const focus = world.decodeEncrypted(this.#cameraFocus.data);
|
|
7657
|
+
if (Array.isArray(focus) && focus.length === 2 && Number.isInteger(focus[0]) && focus[0] >= 0 && focus[1] === 0) {
|
|
7658
|
+
world.model.setLocalPlayerEntity(focus[0]);
|
|
7659
|
+
}
|
|
7660
|
+
}
|
|
7426
7661
|
#applyMeta(packet) {
|
|
7427
7662
|
if (packet.removed) {
|
|
7428
7663
|
const world2 = this.worlds.get(Number(packet.world));
|
|
7429
7664
|
if (world2) world2.readMeta(packet);
|
|
7430
7665
|
this.worlds.delete(Number(packet.world));
|
|
7666
|
+
if (Number(this.#cameraFocus?.world) === Number(packet.world)) this.#cameraFocus = null;
|
|
7431
7667
|
if (this.currentWorldId === Number(packet.world)) this.currentWorldId = null;
|
|
7432
7668
|
return { type: "world-removed", world: world2 || null, packet };
|
|
7433
7669
|
}
|
|
7434
7670
|
const world = this.get(packet.world);
|
|
7435
7671
|
world.readMeta(packet);
|
|
7672
|
+
this.#resolveLocalPlayer(world);
|
|
7436
7673
|
if (this.currentWorldId == null && !world.isOverworld) this.currentWorldId = world.id;
|
|
7437
7674
|
return { type: "world", world };
|
|
7438
7675
|
}
|
|
@@ -7480,7 +7717,10 @@
|
|
|
7480
7717
|
if (packet.model_data && world.seed != null) {
|
|
7481
7718
|
const previousModel = world.model;
|
|
7482
7719
|
try {
|
|
7483
|
-
if (packet.full)
|
|
7720
|
+
if (packet.full) {
|
|
7721
|
+
world.model = new ModelState({ isOverworld: world.isOverworld });
|
|
7722
|
+
world.model.setLocalPlayerEntity(previousModel.localPlayerEntity);
|
|
7723
|
+
}
|
|
7484
7724
|
result.decoded = decodeModelPayload(packet.model_data, packet.world, world.seed);
|
|
7485
7725
|
result.model = world.model.apply(result.decoded, { full: Boolean(packet.full) });
|
|
7486
7726
|
if (result.model?.error && packet.full && previousModel) world.model = previousModel;
|
|
@@ -7539,38 +7779,6 @@
|
|
|
7539
7779
|
};
|
|
7540
7780
|
}
|
|
7541
7781
|
|
|
7542
|
-
// src/protocol/outbound/comms.js
|
|
7543
|
-
var COMMS_MESSAGE_TYPE = 3;
|
|
7544
|
-
function buildCommsMessage(message = "") {
|
|
7545
|
-
return {
|
|
7546
|
-
type: COMMS_MESSAGE_TYPE,
|
|
7547
|
-
msg: String(message)
|
|
7548
|
-
};
|
|
7549
|
-
}
|
|
7550
|
-
function normalizeCommsEvent(event) {
|
|
7551
|
-
const rawMessages = Array.isArray(event?.msgs_text) ? event.msgs_text : [];
|
|
7552
|
-
return {
|
|
7553
|
-
...event,
|
|
7554
|
-
entity: event?.ent_id != null && Number.isFinite(Number(event.ent_id)) ? Number(event.ent_id) : null,
|
|
7555
|
-
rawMessages,
|
|
7556
|
-
messages: rawMessages.map((message) => ({
|
|
7557
|
-
raw: message,
|
|
7558
|
-
text: flattenRichText(message)
|
|
7559
|
-
}))
|
|
7560
|
-
};
|
|
7561
|
-
}
|
|
7562
|
-
function flattenRichText(value) {
|
|
7563
|
-
if (value == null) return "";
|
|
7564
|
-
if (typeof value === "string") return value;
|
|
7565
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
7566
|
-
if (Array.isArray(value)) return value.map(flattenRichText).join("");
|
|
7567
|
-
if (typeof value === "object") {
|
|
7568
|
-
if (Object.hasOwn(value, "c")) return flattenRichText(value.c);
|
|
7569
|
-
if (Object.hasOwn(value, "text")) return flattenRichText(value.text);
|
|
7570
|
-
}
|
|
7571
|
-
return "";
|
|
7572
|
-
}
|
|
7573
|
-
|
|
7574
7782
|
// src/protocol/outbound/commands.js
|
|
7575
7783
|
init_constants();
|
|
7576
7784
|
var SHA256_K = new Uint32Array([
|
|
@@ -7891,11 +8099,11 @@
|
|
|
7891
8099
|
}
|
|
7892
8100
|
return normalized;
|
|
7893
8101
|
}
|
|
7894
|
-
function buildSignTextMessage(
|
|
8102
|
+
function buildSignTextMessage(text2 = "", mode = SignDisplayMode.Always) {
|
|
7895
8103
|
return {
|
|
7896
8104
|
type: 5,
|
|
7897
8105
|
cmd: SIGN_TEXT_COMMAND,
|
|
7898
|
-
args: [String(
|
|
8106
|
+
args: [String(text2), normalizeSignDisplayMode(mode)]
|
|
7899
8107
|
};
|
|
7900
8108
|
}
|
|
7901
8109
|
|
|
@@ -8427,6 +8635,9 @@
|
|
|
8427
8635
|
get lastPacket() {
|
|
8428
8636
|
return this.client.lastPacket;
|
|
8429
8637
|
}
|
|
8638
|
+
get lastDisconnect() {
|
|
8639
|
+
return this.client.lastDisconnect;
|
|
8640
|
+
}
|
|
8430
8641
|
send(command = {}) {
|
|
8431
8642
|
return this.client.send(command);
|
|
8432
8643
|
}
|
|
@@ -8548,17 +8759,46 @@
|
|
|
8548
8759
|
constructor(client) {
|
|
8549
8760
|
this.client = client;
|
|
8550
8761
|
}
|
|
8762
|
+
canSave() {
|
|
8763
|
+
return this.client.canSaveShip();
|
|
8764
|
+
}
|
|
8765
|
+
save(options = {}) {
|
|
8766
|
+
return this.client.saveShip(options);
|
|
8767
|
+
}
|
|
8768
|
+
rename(name, options = {}) {
|
|
8769
|
+
return this.client.renameShip(name, options);
|
|
8770
|
+
}
|
|
8551
8771
|
requestPlayerList() {
|
|
8552
8772
|
return this.client.requestPlayerList();
|
|
8553
8773
|
}
|
|
8554
|
-
resetInvite() {
|
|
8555
|
-
return this.client.resetInvite();
|
|
8774
|
+
resetInvite(options = {}) {
|
|
8775
|
+
return this.client.resetInvite(options);
|
|
8556
8776
|
}
|
|
8557
8777
|
setPrivacy(privacy) {
|
|
8558
8778
|
return this.client.setShipPrivacy(privacy);
|
|
8559
8779
|
}
|
|
8560
|
-
|
|
8561
|
-
return this.client.
|
|
8780
|
+
setGravity(direction) {
|
|
8781
|
+
return this.client.setShipGravity(direction);
|
|
8782
|
+
}
|
|
8783
|
+
setPatronPerks(enabled) {
|
|
8784
|
+
return this.client.setShipPatronPerks(enabled);
|
|
8785
|
+
}
|
|
8786
|
+
enablePatronPerks() {
|
|
8787
|
+
return this.setPatronPerks(true);
|
|
8788
|
+
}
|
|
8789
|
+
disablePatronPerks() {
|
|
8790
|
+
return this.setPatronPerks(false);
|
|
8791
|
+
}
|
|
8792
|
+
patronPerks() {
|
|
8793
|
+
return this.client.shipConfig?.patronPerks ?? null;
|
|
8794
|
+
}
|
|
8795
|
+
hasPatronPerks(name) {
|
|
8796
|
+
if (name !== void 0 && typeof name !== "string") throw new TypeError("name must be a string");
|
|
8797
|
+
const perks = this.patronPerks();
|
|
8798
|
+
return name === void 0 ? Boolean(perks?.length) : Boolean(perks?.some((perk) => perk.name === name));
|
|
8799
|
+
}
|
|
8800
|
+
recoverStarterItem(itemId, options = {}) {
|
|
8801
|
+
return this.client.recoverStarterItem(itemId, options);
|
|
8562
8802
|
}
|
|
8563
8803
|
setPlayerRank(refId, rank) {
|
|
8564
8804
|
return this.client.setPlayerRank(refId, rank);
|
|
@@ -8569,8 +8809,11 @@
|
|
|
8569
8809
|
banPlayer(refId) {
|
|
8570
8810
|
return this.client.banPlayer(refId);
|
|
8571
8811
|
}
|
|
8572
|
-
|
|
8573
|
-
return this.client.
|
|
8812
|
+
unbanPlayer(refId) {
|
|
8813
|
+
return this.client.unbanPlayer(refId);
|
|
8814
|
+
}
|
|
8815
|
+
demoteSelf(options = {}) {
|
|
8816
|
+
return this.client.demoteSelf(options);
|
|
8574
8817
|
}
|
|
8575
8818
|
config() {
|
|
8576
8819
|
return this.client.shipConfig;
|
|
@@ -8581,6 +8824,9 @@
|
|
|
8581
8824
|
playerList() {
|
|
8582
8825
|
return this.client.playerList;
|
|
8583
8826
|
}
|
|
8827
|
+
crewLog() {
|
|
8828
|
+
return this.client.crewLog;
|
|
8829
|
+
}
|
|
8584
8830
|
};
|
|
8585
8831
|
var InventoryDomain = class {
|
|
8586
8832
|
constructor(client) {
|
|
@@ -9301,8 +9547,8 @@
|
|
|
9301
9547
|
get state() {
|
|
9302
9548
|
return summaryForEntity(this.client, this.scope, this.id)?.contents?.sign || null;
|
|
9303
9549
|
}
|
|
9304
|
-
setText(
|
|
9305
|
-
return this.client.setSignText(
|
|
9550
|
+
setText(text2 = "", mode = 0) {
|
|
9551
|
+
return this.client.setSignText(text2, mode);
|
|
9306
9552
|
}
|
|
9307
9553
|
get text() {
|
|
9308
9554
|
return this.state?.text;
|
|
@@ -9680,7 +9926,7 @@
|
|
|
9680
9926
|
const sid = Number(client?.sid);
|
|
9681
9927
|
if (!Number.isFinite(sid)) return null;
|
|
9682
9928
|
const players = worldStateFor(client, scope)?.model.players() || [];
|
|
9683
|
-
return players.find((player) => player.
|
|
9929
|
+
return players.find((player) => player.isLocal) || null;
|
|
9684
9930
|
}
|
|
9685
9931
|
function currentPlayerRank(client) {
|
|
9686
9932
|
const player = currentPlayerSummary(client);
|
|
@@ -9915,6 +10161,7 @@
|
|
|
9915
10161
|
this.ready = false;
|
|
9916
10162
|
this.packetCount = 0;
|
|
9917
10163
|
this.lastPacket = null;
|
|
10164
|
+
this.lastDisconnect = null;
|
|
9918
10165
|
this.packets = [];
|
|
9919
10166
|
this.packetHistory = normalizeHistoryLimit(packetHistory, DEFAULT_PACKET_HISTORY);
|
|
9920
10167
|
this.worlds = new WorldStore({ eventHistory, modelPacketHistory, chunkHistory });
|
|
@@ -9939,6 +10186,7 @@
|
|
|
9939
10186
|
this.shipConfig = null;
|
|
9940
10187
|
this.captainSubrank = null;
|
|
9941
10188
|
this.playerList = null;
|
|
10189
|
+
this.crewLog = null;
|
|
9942
10190
|
this.outfits = /* @__PURE__ */ new Map();
|
|
9943
10191
|
this.commandAcks = /* @__PURE__ */ new Map();
|
|
9944
10192
|
this.lastCommandAck = null;
|
|
@@ -9947,6 +10195,8 @@
|
|
|
9947
10195
|
this.#resolveReady = resolve;
|
|
9948
10196
|
this.#rejectReady = reject;
|
|
9949
10197
|
});
|
|
10198
|
+
this.readyPromise.catch(() => {
|
|
10199
|
+
});
|
|
9950
10200
|
this.attachMode = attachMode;
|
|
9951
10201
|
this.attached = Boolean(attachMode);
|
|
9952
10202
|
if (connect) this.#connect().catch((error) => this.#fail(error));
|
|
@@ -9968,6 +10218,10 @@
|
|
|
9968
10218
|
#attachedOpened = false;
|
|
9969
10219
|
#resolveReady = null;
|
|
9970
10220
|
#rejectReady = null;
|
|
10221
|
+
#savePending = false;
|
|
10222
|
+
#renamePending = false;
|
|
10223
|
+
#starterRecoveryPending = false;
|
|
10224
|
+
#managementPending = /* @__PURE__ */ new Set();
|
|
9971
10225
|
#inputSettings = {
|
|
9972
10226
|
wrench_mode: 1,
|
|
9973
10227
|
turret_mode: 0
|
|
@@ -10009,17 +10263,170 @@
|
|
|
10009
10263
|
sendShipManagement(act, arg = null) {
|
|
10010
10264
|
return this.sendMessage(buildShipManagementMessage(act, arg));
|
|
10011
10265
|
}
|
|
10266
|
+
canSaveShip() {
|
|
10267
|
+
if (!this.connected || !this.ready) return false;
|
|
10268
|
+
const ship = this.worlds.shipWorld();
|
|
10269
|
+
if (!ship || ship.isOverworld !== false || ship.parentWorld == null) return false;
|
|
10270
|
+
const parent = this.worlds.worlds.get(Number(ship.parentWorld));
|
|
10271
|
+
return parent?.meta?.danger_level === 0;
|
|
10272
|
+
}
|
|
10273
|
+
async saveShip({ timeout = 1e4 } = {}) {
|
|
10274
|
+
this.#assertWritable();
|
|
10275
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647) {
|
|
10276
|
+
throw new RangeError("timeout must be positive milliseconds no greater than 2147483647");
|
|
10277
|
+
}
|
|
10278
|
+
if (this.#savePending) throw new Error("A ship save is already in progress");
|
|
10279
|
+
if (!this.canSaveShip()) throw new Error("Saving requires a connected, ready client with its ship in a known Safe Zone");
|
|
10280
|
+
this.#savePending = true;
|
|
10281
|
+
try {
|
|
10282
|
+
return await new Promise((resolve, reject) => {
|
|
10283
|
+
const cleanup = () => {
|
|
10284
|
+
clearTimeout(timer);
|
|
10285
|
+
this.off("disconnect", disconnected);
|
|
10286
|
+
};
|
|
10287
|
+
const disconnected = (info) => {
|
|
10288
|
+
cleanup();
|
|
10289
|
+
if (info.code === DropReason.Saved) resolve(info);
|
|
10290
|
+
else reject(new DisconnectError(info, "Ship save was not confirmed"));
|
|
10291
|
+
};
|
|
10292
|
+
const timer = setTimeout(() => {
|
|
10293
|
+
cleanup();
|
|
10294
|
+
reject(new Error("Timed out waiting for ship save confirmation (Saved close code)"));
|
|
10295
|
+
}, timeout);
|
|
10296
|
+
this.on("disconnect", disconnected);
|
|
10297
|
+
try {
|
|
10298
|
+
this.sendMessage(buildSaveShipMessage());
|
|
10299
|
+
} catch (error) {
|
|
10300
|
+
cleanup();
|
|
10301
|
+
reject(error);
|
|
10302
|
+
}
|
|
10303
|
+
});
|
|
10304
|
+
} finally {
|
|
10305
|
+
this.#savePending = false;
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
async renameShip(name, { color, timeout = 1e4 } = {}) {
|
|
10309
|
+
this.#assertWritable();
|
|
10310
|
+
buildShipRenameCommitMessage(name, color === void 0 ? "#000000" : color);
|
|
10311
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647) {
|
|
10312
|
+
throw new RangeError("timeout must be positive milliseconds no greater than 2147483647");
|
|
10313
|
+
}
|
|
10314
|
+
if (!this.connected || !this.ready) throw new Error("Renaming requires a connected, ready client");
|
|
10315
|
+
if (this.#renamePending) throw new Error("A ship rename is already in progress");
|
|
10316
|
+
const worldId = this.worlds.currentWorldId;
|
|
10317
|
+
this.#renamePending = true;
|
|
10318
|
+
try {
|
|
10319
|
+
await new Promise((resolve, reject) => {
|
|
10320
|
+
const cleanup = () => {
|
|
10321
|
+
clearTimeout(timer);
|
|
10322
|
+
this.off("session", responded);
|
|
10323
|
+
this.off("disconnect", disconnected);
|
|
10324
|
+
this.off("demote", demoted);
|
|
10325
|
+
};
|
|
10326
|
+
const demoted = () => {
|
|
10327
|
+
cleanup();
|
|
10328
|
+
reject(new Error("Ship rename interrupted by demotion"));
|
|
10329
|
+
};
|
|
10330
|
+
const disconnected = (info) => {
|
|
10331
|
+
cleanup();
|
|
10332
|
+
reject(new DisconnectError(info, "Ship rename interrupted"));
|
|
10333
|
+
};
|
|
10334
|
+
const responded = (packet) => {
|
|
10335
|
+
const response = packet.submessage;
|
|
10336
|
+
if (response?.type !== "rename_response") return;
|
|
10337
|
+
cleanup();
|
|
10338
|
+
try {
|
|
10339
|
+
if (!response.allow) throw new Error(response.message || "Ship rename unavailable");
|
|
10340
|
+
if (this.worlds.currentWorldId !== worldId) throw new Error("World changed while requesting ship rename");
|
|
10341
|
+
this.sendMessage(buildShipRenameCommitMessage(name, color === void 0 ? response.initial_color : color));
|
|
10342
|
+
resolve();
|
|
10343
|
+
} catch (error) {
|
|
10344
|
+
reject(error);
|
|
10345
|
+
}
|
|
10346
|
+
};
|
|
10347
|
+
const timer = setTimeout(() => {
|
|
10348
|
+
cleanup();
|
|
10349
|
+
reject(new Error("Timed out waiting for ship rename permission"));
|
|
10350
|
+
}, timeout);
|
|
10351
|
+
this.on("session", responded);
|
|
10352
|
+
this.on("disconnect", disconnected);
|
|
10353
|
+
this.on("demote", demoted);
|
|
10354
|
+
try {
|
|
10355
|
+
this.sendMessage(buildShipRenameRequestMessage());
|
|
10356
|
+
} catch (error) {
|
|
10357
|
+
cleanup();
|
|
10358
|
+
reject(error);
|
|
10359
|
+
}
|
|
10360
|
+
});
|
|
10361
|
+
} finally {
|
|
10362
|
+
this.#renamePending = false;
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
10012
10365
|
requestPlayerList() {
|
|
10013
10366
|
return this.sendMessage(buildPlayerListMessage());
|
|
10014
10367
|
}
|
|
10015
|
-
resetInvite() {
|
|
10016
|
-
|
|
10368
|
+
resetInvite(options = {}) {
|
|
10369
|
+
const previousKey = this.shipConfig?.inviteKey;
|
|
10370
|
+
return this.#awaitManagementConfirmation("Invite reset", buildInviteResetMessage(), "ship-config", (config) => typeof config?.inviteKey === "string" && config.inviteKey.length > 0 && config.inviteKey !== previousKey, options);
|
|
10017
10371
|
}
|
|
10018
10372
|
setShipPrivacy(privacy) {
|
|
10019
10373
|
return this.sendMessage(buildShipPrivacyMessage(privacy));
|
|
10020
10374
|
}
|
|
10021
|
-
|
|
10022
|
-
return this.sendMessage(
|
|
10375
|
+
setShipGravity(direction) {
|
|
10376
|
+
return this.sendMessage(buildShipGravityMessage(direction));
|
|
10377
|
+
}
|
|
10378
|
+
setShipPatronPerks(enabled) {
|
|
10379
|
+
return this.sendMessage(buildSetPatronPerksMessage(enabled));
|
|
10380
|
+
}
|
|
10381
|
+
async recoverStarterItem(itemId, { timeout = 1e4 } = {}) {
|
|
10382
|
+
this.#assertWritable();
|
|
10383
|
+
const message = buildStarterRecoveryMessage(itemId);
|
|
10384
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647) {
|
|
10385
|
+
throw new RangeError("timeout must be positive milliseconds no greater than 2147483647");
|
|
10386
|
+
}
|
|
10387
|
+
if (!this.connected || !this.ready) throw new Error("Starter recovery requires a connected, ready client");
|
|
10388
|
+
if (this.#starterRecoveryPending) throw new Error("A starter recovery is already in progress");
|
|
10389
|
+
const worldId = this.worlds.currentWorldId;
|
|
10390
|
+
this.#starterRecoveryPending = true;
|
|
10391
|
+
try {
|
|
10392
|
+
await new Promise((resolve, reject) => {
|
|
10393
|
+
const cleanup = () => {
|
|
10394
|
+
clearTimeout(timer);
|
|
10395
|
+
this.off("starter-recovery", responded);
|
|
10396
|
+
this.off("disconnect", disconnected);
|
|
10397
|
+
this.off("demote", demoted);
|
|
10398
|
+
};
|
|
10399
|
+
const disconnected = (info) => {
|
|
10400
|
+
cleanup();
|
|
10401
|
+
reject(new DisconnectError(info, "Starter recovery interrupted"));
|
|
10402
|
+
};
|
|
10403
|
+
const demoted = () => {
|
|
10404
|
+
cleanup();
|
|
10405
|
+
reject(new Error("Starter recovery interrupted by demotion"));
|
|
10406
|
+
};
|
|
10407
|
+
const responded = (result) => {
|
|
10408
|
+
cleanup();
|
|
10409
|
+
if (this.worlds.currentWorldId !== worldId) reject(new Error("World changed during starter recovery"));
|
|
10410
|
+
else if (!result.success) reject(new Error(result.failReason));
|
|
10411
|
+
else resolve();
|
|
10412
|
+
};
|
|
10413
|
+
const timer = setTimeout(() => {
|
|
10414
|
+
cleanup();
|
|
10415
|
+
reject(new Error("Timed out waiting for starter recovery response"));
|
|
10416
|
+
}, timeout);
|
|
10417
|
+
this.on("starter-recovery", responded);
|
|
10418
|
+
this.on("disconnect", disconnected);
|
|
10419
|
+
this.on("demote", demoted);
|
|
10420
|
+
try {
|
|
10421
|
+
this.sendMessage(message);
|
|
10422
|
+
} catch (error) {
|
|
10423
|
+
cleanup();
|
|
10424
|
+
reject(error);
|
|
10425
|
+
}
|
|
10426
|
+
});
|
|
10427
|
+
} finally {
|
|
10428
|
+
this.#starterRecoveryPending = false;
|
|
10429
|
+
}
|
|
10023
10430
|
}
|
|
10024
10431
|
setPlayerRank(refId, rank) {
|
|
10025
10432
|
return this.sendMessage(buildSetPlayerRankMessage(refId, rank));
|
|
@@ -10039,8 +10446,56 @@
|
|
|
10039
10446
|
banPlayer(refId) {
|
|
10040
10447
|
return this.sendMessage(buildBanPlayerMessage(refId));
|
|
10041
10448
|
}
|
|
10042
|
-
|
|
10043
|
-
return this.sendMessage(
|
|
10449
|
+
unbanPlayer(refId) {
|
|
10450
|
+
return this.sendMessage(buildUnbanPlayerMessage(refId));
|
|
10451
|
+
}
|
|
10452
|
+
async demoteSelf(options = {}) {
|
|
10453
|
+
await this.#awaitManagementConfirmation("Self-demotion", buildDemoteSelfMessage(), "demote", () => true, options);
|
|
10454
|
+
}
|
|
10455
|
+
async #awaitManagementConfirmation(operation, message, event, accept, { timeout = 1e4 } = {}) {
|
|
10456
|
+
this.#assertWritable();
|
|
10457
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647) {
|
|
10458
|
+
throw new RangeError("timeout must be positive milliseconds no greater than 2147483647");
|
|
10459
|
+
}
|
|
10460
|
+
if (!this.connected || !this.ready) throw new Error(`${operation} requires a connected, ready client`);
|
|
10461
|
+
if (this.#managementPending.has(operation)) throw new Error(`${operation} is already in progress`);
|
|
10462
|
+
const worldId = this.worlds.currentWorldId;
|
|
10463
|
+
this.#managementPending.add(operation);
|
|
10464
|
+
try {
|
|
10465
|
+
return await new Promise((resolve, reject) => {
|
|
10466
|
+
const cleanup = () => {
|
|
10467
|
+
clearTimeout(timer);
|
|
10468
|
+
this.off(event, responded);
|
|
10469
|
+
this.off("disconnect", disconnected);
|
|
10470
|
+
if (event !== "demote") this.off("demote", demoted);
|
|
10471
|
+
};
|
|
10472
|
+
const fail = (error) => {
|
|
10473
|
+
cleanup();
|
|
10474
|
+
reject(error);
|
|
10475
|
+
};
|
|
10476
|
+
const disconnected = (info) => fail(new DisconnectError(info, `${operation} interrupted`));
|
|
10477
|
+
const demoted = () => fail(new Error(`${operation} interrupted by demotion`));
|
|
10478
|
+
const responded = (result) => {
|
|
10479
|
+
if (this.worlds.currentWorldId !== worldId) {
|
|
10480
|
+
fail(new Error(`World changed during ${operation.toLowerCase()}`));
|
|
10481
|
+
} else if (accept(result)) {
|
|
10482
|
+
cleanup();
|
|
10483
|
+
resolve(result);
|
|
10484
|
+
}
|
|
10485
|
+
};
|
|
10486
|
+
const timer = setTimeout(() => fail(new Error(`Timed out waiting for ${operation.toLowerCase()} confirmation`)), timeout);
|
|
10487
|
+
this.on(event, responded);
|
|
10488
|
+
this.on("disconnect", disconnected);
|
|
10489
|
+
if (event !== "demote") this.on("demote", demoted);
|
|
10490
|
+
try {
|
|
10491
|
+
this.sendMessage(message);
|
|
10492
|
+
} catch (error) {
|
|
10493
|
+
fail(error);
|
|
10494
|
+
}
|
|
10495
|
+
});
|
|
10496
|
+
} finally {
|
|
10497
|
+
this.#managementPending.delete(operation);
|
|
10498
|
+
}
|
|
10044
10499
|
}
|
|
10045
10500
|
sendEntityCommand(cmd, args = [-1, -1, -1]) {
|
|
10046
10501
|
return this.sendMessage({ type: 5, cmd, args });
|
|
@@ -10084,8 +10539,8 @@
|
|
|
10084
10539
|
setLauncherPower(power) {
|
|
10085
10540
|
return this.sendEntityCommand("power", [normalizeLauncherPower(power)]);
|
|
10086
10541
|
}
|
|
10087
|
-
setSignText(
|
|
10088
|
-
return this.sendMessage(buildSignTextMessage(
|
|
10542
|
+
setSignText(text2 = "", mode = 0) {
|
|
10543
|
+
return this.sendMessage(buildSignTextMessage(text2, mode));
|
|
10089
10544
|
}
|
|
10090
10545
|
sendUiConfig(data) {
|
|
10091
10546
|
return this.sendMessage({ type: 8, data });
|
|
@@ -10344,12 +10799,14 @@
|
|
|
10344
10799
|
shipConfig: this.shipConfig,
|
|
10345
10800
|
captainSubrank: this.captainSubrank,
|
|
10346
10801
|
playerList: this.playerList,
|
|
10802
|
+
crewLog: this.crewLog,
|
|
10347
10803
|
outfits: [...this.outfits.entries()].map(([sid, outfit]) => ({ sid, outfit })),
|
|
10348
10804
|
commandAcks: [...this.commandAcks.entries()].map(([world, commandNumber]) => ({ world, commandNumber })),
|
|
10349
10805
|
lastCommandAck: this.lastCommandAck,
|
|
10350
10806
|
decodeErrors: this.decodeErrors.slice(-50),
|
|
10351
10807
|
packetCount: this.packetCount,
|
|
10352
|
-
lastPacket: this.lastPacket
|
|
10808
|
+
lastPacket: this.lastPacket,
|
|
10809
|
+
lastDisconnect: this.lastDisconnect
|
|
10353
10810
|
};
|
|
10354
10811
|
}
|
|
10355
10812
|
state(options = {}) {
|
|
@@ -10474,6 +10931,7 @@
|
|
|
10474
10931
|
}
|
|
10475
10932
|
this.#resolveReady?.(this);
|
|
10476
10933
|
this.#resolveReady = null;
|
|
10934
|
+
this.#rejectReady = null;
|
|
10477
10935
|
}
|
|
10478
10936
|
this.#bindAttachedSocket();
|
|
10479
10937
|
if (this.connected && this.#ownsSocket) this.#attachedOpen();
|
|
@@ -10481,6 +10939,7 @@
|
|
|
10481
10939
|
}
|
|
10482
10940
|
#bindSocket() {
|
|
10483
10941
|
this.ws.onopen = () => {
|
|
10942
|
+
this.lastDisconnect = null;
|
|
10484
10943
|
this.connected = true;
|
|
10485
10944
|
this.ws.send(encodeMsgpack({ type: 1 }));
|
|
10486
10945
|
this.emit("open", this);
|
|
@@ -10494,7 +10953,7 @@
|
|
|
10494
10953
|
const open = () => this.#attachedOpen();
|
|
10495
10954
|
const message = (event) => this.#handleSocketMessage(event?.data ?? event);
|
|
10496
10955
|
const error = (event) => this.#fail(event?.error || new Error("WebSocket error"));
|
|
10497
|
-
const close = (event) => this.#handleSocketClose(event);
|
|
10956
|
+
const close = (event, reason) => this.#handleSocketClose(event, reason);
|
|
10498
10957
|
if (typeof this.ws.addEventListener === "function") {
|
|
10499
10958
|
this.ws.addEventListener("open", open);
|
|
10500
10959
|
this.ws.addEventListener("message", message);
|
|
@@ -10509,13 +10968,16 @@
|
|
|
10509
10968
|
this.ws.on("close", close);
|
|
10510
10969
|
return;
|
|
10511
10970
|
}
|
|
10512
|
-
const
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10971
|
+
for (const [type, handler] of Object.entries({ open, message, error, close })) {
|
|
10972
|
+
const previous = this.ws[`on${type}`];
|
|
10973
|
+
this.ws[`on${type}`] = (...args) => {
|
|
10974
|
+
if (typeof previous === "function") previous.apply(this.ws, args);
|
|
10975
|
+
handler(...args);
|
|
10976
|
+
};
|
|
10977
|
+
}
|
|
10517
10978
|
}
|
|
10518
10979
|
#attachedOpen() {
|
|
10980
|
+
this.lastDisconnect = null;
|
|
10519
10981
|
this.connected = true;
|
|
10520
10982
|
if (this.#attachedOpened) return;
|
|
10521
10983
|
this.#attachedOpened = true;
|
|
@@ -10523,17 +10985,39 @@
|
|
|
10523
10985
|
this.emit("open", this);
|
|
10524
10986
|
if (this.#allowWrites) this.#flushMessages();
|
|
10525
10987
|
}
|
|
10526
|
-
#handleSocketClose(event) {
|
|
10988
|
+
#handleSocketClose(event, reason) {
|
|
10989
|
+
const info = normalizeDisconnect(event, reason);
|
|
10527
10990
|
this.connected = false;
|
|
10991
|
+
this.ready = false;
|
|
10992
|
+
this.sid = null;
|
|
10993
|
+
this.lastDisconnect = info;
|
|
10528
10994
|
clearInterval(this.#keepalive);
|
|
10529
10995
|
this.#keepalive = null;
|
|
10530
|
-
this
|
|
10996
|
+
this.#bootstrapped = false;
|
|
10997
|
+
this.#attachedOpened = false;
|
|
10998
|
+
this.#queuedCommands.length = 0;
|
|
10999
|
+
this.#queuedMessages.length = 0;
|
|
11000
|
+
this.shipConfig = null;
|
|
11001
|
+
this.captainSubrank = null;
|
|
11002
|
+
this.playerList = null;
|
|
11003
|
+
this.crewLog = null;
|
|
11004
|
+
this.puiPanels.clear();
|
|
11005
|
+
this.commsPanels.clear();
|
|
11006
|
+
this.currentCommsPanel = null;
|
|
11007
|
+
this.commandAcks.clear();
|
|
11008
|
+
this.lastCommandAck = null;
|
|
11009
|
+
this.#rejectReady?.(new DisconnectError(info, "Disconnected before the client was ready"));
|
|
11010
|
+
this.#resolveReady = null;
|
|
11011
|
+
this.#rejectReady = null;
|
|
11012
|
+
this.emit("disconnect", info);
|
|
11013
|
+
this.emit("close", event, reason);
|
|
10531
11014
|
}
|
|
10532
11015
|
#socketLooksOpen(websocket) {
|
|
10533
11016
|
return websocket?.readyState == null || websocket.readyState === 1 || websocket.readyState === websocket.OPEN;
|
|
10534
11017
|
}
|
|
10535
11018
|
#assertWritable() {
|
|
10536
11019
|
if (!this.#allowWrites) throw new Error("DredlessClient is attached in readonly mode");
|
|
11020
|
+
if (this.lastDisconnect) throw new DisconnectError(this.lastDisconnect);
|
|
10537
11021
|
}
|
|
10538
11022
|
#handleSocketMessage(data) {
|
|
10539
11023
|
if (data && typeof data.arrayBuffer === "function") {
|
|
@@ -10543,6 +11027,7 @@
|
|
|
10543
11027
|
this.#handleMessage(data);
|
|
10544
11028
|
}
|
|
10545
11029
|
#handleMessage(data) {
|
|
11030
|
+
if (this.lastDisconnect) return;
|
|
10546
11031
|
let packet;
|
|
10547
11032
|
try {
|
|
10548
11033
|
packet = decodeIncomingFrame(data);
|
|
@@ -10569,6 +11054,7 @@
|
|
|
10569
11054
|
}
|
|
10570
11055
|
#markReady(packet) {
|
|
10571
11056
|
this.sid = packet.sid >>> 0;
|
|
11057
|
+
this.worlds.apply(packet);
|
|
10572
11058
|
this.worlds.currentWorldId = packet.world ?? this.worlds.currentWorldId;
|
|
10573
11059
|
this.ready = true;
|
|
10574
11060
|
if (this.#ownsSocket) {
|
|
@@ -10581,6 +11067,7 @@
|
|
|
10581
11067
|
}
|
|
10582
11068
|
this.#resolveReady?.(this);
|
|
10583
11069
|
this.#resolveReady = null;
|
|
11070
|
+
this.#rejectReady = null;
|
|
10584
11071
|
this.emit("ready", this);
|
|
10585
11072
|
}
|
|
10586
11073
|
#sendBootstrap() {
|
|
@@ -10658,6 +11145,32 @@
|
|
|
10658
11145
|
#handleSessionSubmessage(packet) {
|
|
10659
11146
|
const submessage = packet?.submessage;
|
|
10660
11147
|
if (!submessage || typeof submessage !== "object") return;
|
|
11148
|
+
if (submessage.type === "demote") {
|
|
11149
|
+
this.shipConfig = null;
|
|
11150
|
+
this.captainSubrank = null;
|
|
11151
|
+
this.playerList = null;
|
|
11152
|
+
this.crewLog = null;
|
|
11153
|
+
this.emit("demote", packet);
|
|
11154
|
+
this.emit("ship-config", null, packet);
|
|
11155
|
+
this.emit("captain-subrank", null, packet);
|
|
11156
|
+
this.emit("player-list", null, packet);
|
|
11157
|
+
this.emit("crew-log", null, packet);
|
|
11158
|
+
return;
|
|
11159
|
+
}
|
|
11160
|
+
if (submessage.type === "log") {
|
|
11161
|
+
this.crewLog = normalizeCrewLog(submessage, this.crewLog);
|
|
11162
|
+
this.emit("crew-log", this.crewLog, packet);
|
|
11163
|
+
return;
|
|
11164
|
+
}
|
|
11165
|
+
if (submessage.type === "starter_recovery_response") {
|
|
11166
|
+
const failReason = submessage.fail_reason == null ? null : String(submessage.fail_reason);
|
|
11167
|
+
this.emit("starter-recovery", Object.freeze({ success: failReason === null, failReason }), packet);
|
|
11168
|
+
return;
|
|
11169
|
+
}
|
|
11170
|
+
if (submessage.type === "perk_response") {
|
|
11171
|
+
this.emit("patron-perks-response", Object.freeze({ failure: Boolean(submessage.failure) }), packet);
|
|
11172
|
+
return;
|
|
11173
|
+
}
|
|
10661
11174
|
if (submessage.type === "config") {
|
|
10662
11175
|
this.shipConfig = normalizeShipConfig(submessage);
|
|
10663
11176
|
this.emit("ship-config", this.shipConfig, packet);
|
|
@@ -10910,6 +11423,174 @@
|
|
|
10910
11423
|
init_cookies();
|
|
10911
11424
|
init_http();
|
|
10912
11425
|
init_servers();
|
|
11426
|
+
|
|
11427
|
+
// src/network/account.js
|
|
11428
|
+
init_runtime();
|
|
11429
|
+
async function fetchAccountDetails(session) {
|
|
11430
|
+
const response = await session.request("account/", {
|
|
11431
|
+
method: "GET",
|
|
11432
|
+
headers: { accept: "text/html" },
|
|
11433
|
+
credentials: "include",
|
|
11434
|
+
cache: "no-store"
|
|
11435
|
+
});
|
|
11436
|
+
session.mergeSetCookies(response);
|
|
11437
|
+
if (!response.ok) throw new Error(`Account page request failed: ${response.status} ${response.statusText}`);
|
|
11438
|
+
return parseAccountDetails(await response.text());
|
|
11439
|
+
}
|
|
11440
|
+
async function fetchClanTag(session) {
|
|
11441
|
+
return (await fetchAccountDetails(session)).clan;
|
|
11442
|
+
}
|
|
11443
|
+
async function setClanTag(session, tag) {
|
|
11444
|
+
if (typeof tag !== "string") throw new TypeError("tag must be a string");
|
|
11445
|
+
return updateClanTag(session, tag);
|
|
11446
|
+
}
|
|
11447
|
+
async function clearClanTag(session) {
|
|
11448
|
+
return updateClanTag(session, "");
|
|
11449
|
+
}
|
|
11450
|
+
async function updateClanTag(session, tag) {
|
|
11451
|
+
const body = tag === "" ? "" : new URLSearchParams({ tag }).toString();
|
|
11452
|
+
await postAccountForm(session, "account/clan", body, "Clan tag update");
|
|
11453
|
+
const clan = await fetchClanTag(session);
|
|
11454
|
+
if ((clan.tag ?? "") !== tag) throw new Error("Clan tag update was not confirmed by the account page");
|
|
11455
|
+
return clan;
|
|
11456
|
+
}
|
|
11457
|
+
async function removeShipPerks(session, shipId) {
|
|
11458
|
+
const id = requirePerkShipId(shipId);
|
|
11459
|
+
await postAccountForm(session, "account/remove_perks", new URLSearchParams({ ship: id }).toString(), "Ship perk removal");
|
|
11460
|
+
const { shipsWithPerks } = await fetchAccountDetails(session);
|
|
11461
|
+
if (shipsWithPerks === null || shipsWithPerks.some((ship) => ship.shipId === id)) {
|
|
11462
|
+
throw new Error("Ship perk removal was not confirmed by the account page");
|
|
11463
|
+
}
|
|
11464
|
+
return shipsWithPerks;
|
|
11465
|
+
}
|
|
11466
|
+
async function postAccountForm(session, path, body, operation) {
|
|
11467
|
+
const response = await session.request(path, {
|
|
11468
|
+
method: "POST",
|
|
11469
|
+
headers: {
|
|
11470
|
+
accept: "text/html",
|
|
11471
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
11472
|
+
...!isBrowser() ? { origin: new URL(session.baseUrl).origin } : {}
|
|
11473
|
+
},
|
|
11474
|
+
body,
|
|
11475
|
+
credentials: "include",
|
|
11476
|
+
referrer: `${session.baseUrl}/account/`,
|
|
11477
|
+
// Node must merge cookies before following the form's redirect.
|
|
11478
|
+
redirect: isBrowser() ? "follow" : "manual"
|
|
11479
|
+
});
|
|
11480
|
+
session.mergeSetCookies(response);
|
|
11481
|
+
if (!response.ok && response.status !== 302 && response.status !== 303) {
|
|
11482
|
+
throw new Error(`${operation} failed: ${response.status} ${response.statusText}`);
|
|
11483
|
+
}
|
|
11484
|
+
await response.text();
|
|
11485
|
+
}
|
|
11486
|
+
function parseAccountDetails(html) {
|
|
11487
|
+
const source = String(html).replace(/<!--[\s\S]*?-->|<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "");
|
|
11488
|
+
let tag = null;
|
|
11489
|
+
let found = false;
|
|
11490
|
+
for (const form of source.matchAll(/<form\b((?:"[^"]*"|'[^']*'|[^'">])*)>([\s\S]*?)<\/form\s*>/gi)) {
|
|
11491
|
+
const action = attributes(form[1]).get("action");
|
|
11492
|
+
if (!action || new URL(action, "https://drednot.io/").pathname !== "/account/clan") continue;
|
|
11493
|
+
for (const input of form[2].matchAll(/<input\b((?:"[^"]*"|'[^']*'|[^'">])*)>/gi)) {
|
|
11494
|
+
const attrs = attributes(input[1]);
|
|
11495
|
+
if (attrs.get("name") !== "tag") continue;
|
|
11496
|
+
tag = attrs.get("value") || null;
|
|
11497
|
+
found = true;
|
|
11498
|
+
break;
|
|
11499
|
+
}
|
|
11500
|
+
if (found) break;
|
|
11501
|
+
}
|
|
11502
|
+
if (!found) throw new Error("Account page has no clan form; authentication may be required or the page format has changed");
|
|
11503
|
+
const fields = /* @__PURE__ */ new Map();
|
|
11504
|
+
let name = null;
|
|
11505
|
+
let color = null;
|
|
11506
|
+
for (const paragraph of source.matchAll(/<p\b[^>]*>([\s\S]*?)<\/p\s*>/gi)) {
|
|
11507
|
+
const label = paragraph[1].match(/<b\b[^>]*>([\s\S]*?)<\/b\s*>/i);
|
|
11508
|
+
const code = paragraph[1].match(/<code\b((?:"[^"]*"|'[^']*'|[^'">])*)>([\s\S]*?)<\/code\s*>/i);
|
|
11509
|
+
if (!code) continue;
|
|
11510
|
+
const attrs = attributes(code[1]);
|
|
11511
|
+
if (label) fields.set(text(label[1]).replace(/:\s*$/, "").toLowerCase(), attrs.get("data-info") ?? null);
|
|
11512
|
+
if ((attrs.get("class") || "").split(/\s+/).includes("user") && /^You are:$/i.test(text(paragraph[1].slice(0, code.index)))) {
|
|
11513
|
+
name = decodeHtml(code[2].replace(/<[^>]*>/g, ""));
|
|
11514
|
+
color = parseAccountColor(attrs.get("style") || "");
|
|
11515
|
+
}
|
|
11516
|
+
}
|
|
11517
|
+
const rawIdentity = fields.get("identity (service)") || null;
|
|
11518
|
+
const identityParts = rawIdentity?.match(/^(.*?)\s*\(([^()]*)\)\s*$/);
|
|
11519
|
+
const count = text(source).match(/Your current clan tag has\s+([\d,]+)\s+other users?\b/i);
|
|
11520
|
+
return {
|
|
11521
|
+
name,
|
|
11522
|
+
color,
|
|
11523
|
+
email: fields.get("email") || null,
|
|
11524
|
+
identity: identityParts ? identityParts[1].trim() : rawIdentity,
|
|
11525
|
+
identityService: identityParts ? identityParts[2].trim() : null,
|
|
11526
|
+
clan: { tag, otherUsers: tag !== null && count ? Number(count[1].replace(/,/g, "")) : null },
|
|
11527
|
+
shipsWithPerks: parseShipsWithPerks(source)
|
|
11528
|
+
};
|
|
11529
|
+
}
|
|
11530
|
+
function parseAccountColor(style) {
|
|
11531
|
+
let color = null;
|
|
11532
|
+
for (const declaration of style.split(";")) {
|
|
11533
|
+
const match = declaration.match(/^\s*background(?:-color)?\s*:\s*#([0-9a-f]{6}|[0-9a-f]{3})\s*(?:!important\s*)?$/i);
|
|
11534
|
+
if (!match) continue;
|
|
11535
|
+
const hex = match[1].length === 3 ? [...match[1]].map((digit) => digit + digit).join("") : match[1];
|
|
11536
|
+
color = parseInt(hex, 16);
|
|
11537
|
+
}
|
|
11538
|
+
return color;
|
|
11539
|
+
}
|
|
11540
|
+
function parseShipsWithPerks(source) {
|
|
11541
|
+
const heading = source.match(/<(b|strong)\b[^>]*>\s*Ships\s+with\s+Perks\s+Applied\s*:\s*<\/\1\s*>/i);
|
|
11542
|
+
if (!heading) return null;
|
|
11543
|
+
const list = source.slice(heading.index + heading[0].length).match(/^\s*(?:<\/[^>]+>\s*)*<ul\b[^>]*>([\s\S]*?)<\/ul\s*>/i);
|
|
11544
|
+
if (!list) throw new Error("Account page's applied-perks list could not be parsed");
|
|
11545
|
+
const ships = [];
|
|
11546
|
+
for (const item of list[1].matchAll(/<li\b[^>]*>([\s\S]*?)<\/li\s*>/gi)) {
|
|
11547
|
+
const form = item[1].match(/<form\b((?:"[^"]*"|'[^']*'|[^'">])*)>([\s\S]*?)<\/form\s*>/i);
|
|
11548
|
+
const action = form && attributes(form[1]).get("action");
|
|
11549
|
+
if (!action || new URL(action, "https://drednot.io/").pathname !== "/account/remove_perks") {
|
|
11550
|
+
throw new Error("Account page's applied-perks entry has no removal form");
|
|
11551
|
+
}
|
|
11552
|
+
let shipId = null;
|
|
11553
|
+
for (const input of form[2].matchAll(/<input\b((?:"[^"]*"|'[^']*'|[^'">])*)>/gi)) {
|
|
11554
|
+
const attrs = attributes(input[1]);
|
|
11555
|
+
if (attrs.get("name") === "ship") shipId = requirePerkShipId(attrs.get("value"));
|
|
11556
|
+
}
|
|
11557
|
+
if (shipId === null) throw new Error("Account page's applied-perks entry has no ship identifier");
|
|
11558
|
+
const label = decodeHtml(item[1].slice(0, form.index).replace(/<[^>]*>/g, ""));
|
|
11559
|
+
const parts = label.match(/^([\s\S]*?) \{([^{}]+)\}\s*$/);
|
|
11560
|
+
ships.push({ shipId, name: parts ? parts[1] : label.trim(), hexCode: parts ? parts[2] : null });
|
|
11561
|
+
}
|
|
11562
|
+
return ships;
|
|
11563
|
+
}
|
|
11564
|
+
function requirePerkShipId(value) {
|
|
11565
|
+
if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
11566
|
+
throw new RangeError("shipId must be a positive safe integer or a decimal ID string from shipsWithPerks");
|
|
11567
|
+
}
|
|
11568
|
+
if (typeof value !== "string" && typeof value !== "number" || !/^[1-9]\d*$/.test(String(value))) {
|
|
11569
|
+
throw new TypeError("shipId must be a positive integer or a decimal ID string from shipsWithPerks");
|
|
11570
|
+
}
|
|
11571
|
+
return String(value);
|
|
11572
|
+
}
|
|
11573
|
+
function attributes(source) {
|
|
11574
|
+
const result = /* @__PURE__ */ new Map();
|
|
11575
|
+
for (const match of source.matchAll(/([^\s=<>"'`/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g)) {
|
|
11576
|
+
result.set(match[1].toLowerCase(), decodeHtml(match[2] ?? match[3] ?? match[4] ?? ""));
|
|
11577
|
+
}
|
|
11578
|
+
return result;
|
|
11579
|
+
}
|
|
11580
|
+
function text(source) {
|
|
11581
|
+
return decodeHtml(source.replace(/<[^>]*>/g, " ")).replace(/\s+/g, " ").trim();
|
|
11582
|
+
}
|
|
11583
|
+
function decodeHtml(value) {
|
|
11584
|
+
const named = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
|
|
11585
|
+
return value.replace(/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos|nbsp);/gi, (raw, entity) => {
|
|
11586
|
+
const name = entity.toLowerCase();
|
|
11587
|
+
if (Object.hasOwn(named, name)) return named[name];
|
|
11588
|
+
const code = name.startsWith("#x") ? parseInt(name.slice(2), 16) : Number(name.slice(1));
|
|
11589
|
+
return code > 0 && code <= 1114111 && !(code >= 55296 && code <= 57343) ? String.fromCodePoint(code) : "\uFFFD";
|
|
11590
|
+
});
|
|
11591
|
+
}
|
|
11592
|
+
|
|
11593
|
+
// src/network/session.js
|
|
10913
11594
|
var Session = class {
|
|
10914
11595
|
constructor(gameSession = null, gameVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
10915
11596
|
this.baseUrl = baseUrl;
|
|
@@ -10957,6 +11638,21 @@
|
|
|
10957
11638
|
this.#readStatus(raw);
|
|
10958
11639
|
return raw;
|
|
10959
11640
|
}
|
|
11641
|
+
fetchAccountDetails() {
|
|
11642
|
+
return fetchAccountDetails(this);
|
|
11643
|
+
}
|
|
11644
|
+
fetchClanTag() {
|
|
11645
|
+
return fetchClanTag(this);
|
|
11646
|
+
}
|
|
11647
|
+
setClanTag(tag) {
|
|
11648
|
+
return setClanTag(this, tag);
|
|
11649
|
+
}
|
|
11650
|
+
clearClanTag() {
|
|
11651
|
+
return clearClanTag(this);
|
|
11652
|
+
}
|
|
11653
|
+
removeShipPerks(shipId) {
|
|
11654
|
+
return removeShipPerks(this, shipId);
|
|
11655
|
+
}
|
|
10960
11656
|
async fetchShips(server2) {
|
|
10961
11657
|
return fetchShips(this, server2);
|
|
10962
11658
|
}
|