dankgrinder 8.105.0 → 8.109.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/lib/commands/index.js +2 -1
- package/lib/grinder.js +365 -163
- package/package.json +1 -1
package/lib/commands/index.js
CHANGED
|
@@ -16,7 +16,7 @@ const { runPostMemes } = require('./postmemes');
|
|
|
16
16
|
const { runScratch } = require('./scratch');
|
|
17
17
|
const { runBlackjack } = require('./blackjack');
|
|
18
18
|
const { runTrivia, triviaDB } = require('./trivia');
|
|
19
|
-
const { runWorkShift } = require('./work');
|
|
19
|
+
const { runWorkShift, autoApplyForJob } = require('./work');
|
|
20
20
|
const { runCointoss, runRoulette, runSlots, runSnakeeyes } = require('./gamble');
|
|
21
21
|
const { runDeposit } = require('./deposit');
|
|
22
22
|
const { runGeneric, runAlert } = require('./generic');
|
|
@@ -44,6 +44,7 @@ module.exports = {
|
|
|
44
44
|
runBlackjack,
|
|
45
45
|
runTrivia,
|
|
46
46
|
runWorkShift,
|
|
47
|
+
autoApplyForJob,
|
|
47
48
|
runCointoss,
|
|
48
49
|
runRoulette,
|
|
49
50
|
runSlots,
|
package/lib/grinder.js
CHANGED
|
@@ -670,8 +670,10 @@ class AccountWorker {
|
|
|
670
670
|
this._levelQuestActive = false;
|
|
671
671
|
this._levelQuestQueue = [];
|
|
672
672
|
this._levelQuestDone = new Set();
|
|
673
|
+
this._levelQuestProgressCache = new Map();
|
|
674
|
+
this._levelQuestRunMap = new Map(); // level -> { startedAt, updatedAt, entries: Map<cmd, state> }
|
|
673
675
|
this._questBetOverride = null;
|
|
674
|
-
|
|
676
|
+
this._lastCommandMeta = null;
|
|
675
677
|
this._commandRunning = false; // prevents grinding commands from overlapping with quest commands
|
|
676
678
|
this._verifyLevelUnlock = null; // holds level to verify after quest completion
|
|
677
679
|
this.commandQueue = null;
|
|
@@ -842,148 +844,13 @@ class AccountWorker {
|
|
|
842
844
|
}
|
|
843
845
|
|
|
844
846
|
async buyItem(itemName, quantity = 1) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
}
|
|
853
|
-
let response = await this.waitForDankMemer(10000);
|
|
854
|
-
if (!response) {
|
|
855
|
-
this.log('warn', 'No response to shop view command.');
|
|
856
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
const responseText = getFullText(response).toLowerCase();
|
|
861
|
-
const hasShopComponents = (response.components || []).some(row =>
|
|
862
|
-
(row.components || []).some(comp => comp.type === 3 || (comp.type === 2 && comp.label && comp.label.toLowerCase().includes('buy')))
|
|
863
|
-
);
|
|
864
|
-
|
|
865
|
-
if (!hasShopComponents && (responseText.includes('lucky') || responseText.includes('event') || responseText.includes('for the rest of the day'))) {
|
|
866
|
-
this.log('warn', 'Got event response instead of shop. Retrying...');
|
|
867
|
-
await humanDelay(3000, 5000);
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
if (!hasShopComponents && responseText.includes('shop')) {
|
|
871
|
-
const shopUI = await this.waitForDankMemer(8000);
|
|
872
|
-
if (shopUI) response = shopUI;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// Navigate to Coin Shop
|
|
876
|
-
let coinShopMenuId = null;
|
|
877
|
-
let coinShopOption = null;
|
|
878
|
-
for (const row of response.components || []) {
|
|
879
|
-
for (const comp of row.components || []) {
|
|
880
|
-
if (comp.type === 3) {
|
|
881
|
-
const opt = (comp.options || []).find(o => o.label && o.label.includes('Coin Shop'));
|
|
882
|
-
if (opt) { coinShopMenuId = comp.customId; coinShopOption = opt; }
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
if (coinShopMenuId && coinShopOption) {
|
|
888
|
-
this.log('buy', 'Navigating to Coin Shop...');
|
|
889
|
-
try {
|
|
890
|
-
await response.selectMenu(coinShopMenuId, [coinShopOption.value]);
|
|
891
|
-
const updatedMsg = await this.waitForDankMemer(8000);
|
|
892
|
-
if (updatedMsg) response = updatedMsg;
|
|
893
|
-
} catch (e) {
|
|
894
|
-
this.log('error', `Failed to open Coin Shop: ${e.message}`);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
await humanDelay(300, 600);
|
|
898
|
-
|
|
899
|
-
// Find Buy button — match by full name or partial name
|
|
900
|
-
let buyBtn = null;
|
|
901
|
-
const searchNames = [
|
|
902
|
-
itemName.toLowerCase(),
|
|
903
|
-
itemName.toLowerCase().replace('hunting ', '').replace('fishing ', ''),
|
|
904
|
-
itemName.toLowerCase().split(' ')[0],
|
|
905
|
-
];
|
|
906
|
-
for (const row of response.components || []) {
|
|
907
|
-
for (const comp of row.components || []) {
|
|
908
|
-
if (comp.type !== 2 || !comp.label) continue;
|
|
909
|
-
const label = comp.label.toLowerCase();
|
|
910
|
-
if (searchNames.some(s => label.includes(s) || s.includes(label))) {
|
|
911
|
-
buyBtn = comp; break;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
if (buyBtn) break;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
if (!buyBtn) {
|
|
918
|
-
this.log('warn', `Could not find Buy button for ${itemName} (attempt ${attempt})`);
|
|
919
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
920
|
-
return false;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
this.log('buy', `Clicking Buy ${itemName}...`);
|
|
924
|
-
try { await safeClickButton(response, buyBtn); } catch (e) {
|
|
925
|
-
this.log('error', `Buy click failed: ${e.message}`);
|
|
926
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
927
|
-
return false;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// Handle Modal
|
|
931
|
-
const modal = await new Promise((resolve) => {
|
|
932
|
-
const timer = setTimeout(() => resolve(null), 8000);
|
|
933
|
-
const handler = (m) => {
|
|
934
|
-
clearTimeout(timer);
|
|
935
|
-
this.client.removeListener('interactionModalCreate', handler);
|
|
936
|
-
resolve(m);
|
|
937
|
-
};
|
|
938
|
-
this.client.on('interactionModalCreate', handler);
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
if (modal) {
|
|
942
|
-
this.log('buy', `Submitting quantity ${c.bold}${quantity}${c.reset} in modal...`);
|
|
943
|
-
try {
|
|
944
|
-
const quantityInputId = modal.components[0].components[0].customId;
|
|
945
|
-
await fetch('https://discord.com/api/v9/interactions', {
|
|
946
|
-
method: 'POST',
|
|
947
|
-
headers: { 'Authorization': this.client.token, 'Content-Type': 'application/json' },
|
|
948
|
-
body: JSON.stringify({
|
|
949
|
-
type: 5, application_id: modal.applicationId,
|
|
950
|
-
channel_id: this.channel.id, guild_id: this.channel.guild?.id,
|
|
951
|
-
data: {
|
|
952
|
-
id: modal.id, custom_id: modal.customId,
|
|
953
|
-
components: [{ type: 1, components: [{ type: 4, custom_id: quantityInputId, value: String(quantity) }] }]
|
|
954
|
-
},
|
|
955
|
-
session_id: this.client.sessionId || "dummy_session",
|
|
956
|
-
nonce: Date.now().toString()
|
|
957
|
-
})
|
|
958
|
-
});
|
|
959
|
-
} catch (e) {
|
|
960
|
-
this.log('error', `Modal submit failed: ${e.message}`);
|
|
961
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
962
|
-
return false;
|
|
963
|
-
}
|
|
964
|
-
} else {
|
|
965
|
-
this.log('warn', 'No modal appeared after clicking buy.');
|
|
966
|
-
if (attempt < MAX_RETRIES) { await humanDelay(2000, 4000); continue; }
|
|
967
|
-
return false;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
const confirmMsg = await this.waitForDankMemer(8000);
|
|
971
|
-
if (confirmMsg) {
|
|
972
|
-
const text = getFullText(confirmMsg).toLowerCase();
|
|
973
|
-
if (text.includes('bought') || text.includes('purchased') || text.includes('success')) {
|
|
974
|
-
this.log('success', `Bought ${c.bold}${quantity}x ${itemName}${c.reset}!`);
|
|
975
|
-
return true;
|
|
976
|
-
}
|
|
977
|
-
if (text.includes('not enough') || text.includes("can't afford") || text.includes('insufficient')) {
|
|
978
|
-
this.log('warn', `Not enough coins to buy ${itemName}.`);
|
|
979
|
-
return false;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
this.log('success', `Submitted purchase for ${quantity}x ${itemName}.`);
|
|
983
|
-
return true;
|
|
984
|
-
}
|
|
985
|
-
this.log('error', `Failed to buy ${itemName} after ${MAX_RETRIES} attempts.`);
|
|
986
|
-
return false;
|
|
847
|
+
return !!(await commands.buyItem({
|
|
848
|
+
channel: this.channel,
|
|
849
|
+
waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
|
|
850
|
+
client: this.client,
|
|
851
|
+
itemName,
|
|
852
|
+
quantity,
|
|
853
|
+
}));
|
|
987
854
|
}
|
|
988
855
|
|
|
989
856
|
// ── Check Balance ───────────────────────────────────────────
|
|
@@ -1386,7 +1253,7 @@ class AccountWorker {
|
|
|
1386
1253
|
// Each modular command handler sends the command, waits for response,
|
|
1387
1254
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
1388
1255
|
async runCommand(cmdName, prefix) {
|
|
1389
|
-
this._lastCommandMeta = { cmdName, nonPlay: false, spent: 0, earned: 0, holdTightReason: null, newMinBet: null, levelLocked: null };
|
|
1256
|
+
this._lastCommandMeta = { cmdName, nonPlay: false, spent: 0, earned: 0, holdTightReason: null, newMinBet: null, levelLocked: null, nextRetrySec: 0 };
|
|
1390
1257
|
let cmdString;
|
|
1391
1258
|
const bjBet = Math.max(5000, this.account.bet_amount || 5000);
|
|
1392
1259
|
const gambBet = Math.max(10000, this.account.bet_amount || 10000);
|
|
@@ -1501,7 +1368,7 @@ class AccountWorker {
|
|
|
1501
1368
|
case 'search': cmdResult = await commands.runSearch(cmdOpts); break;
|
|
1502
1369
|
case 'crime': cmdResult = await commands.runCrime(cmdOpts); break;
|
|
1503
1370
|
case 'hl': cmdResult = await commands.runHighLow(cmdOpts); break;
|
|
1504
|
-
|
|
1371
|
+
case 'farm': cmdResult = await commands.runFarm(cmdOpts); break;
|
|
1505
1372
|
case 'pm': cmdResult = await commands.runPostMemes(cmdOpts); break;
|
|
1506
1373
|
case 'hunt': cmdResult = await commands.runHunt(cmdOpts); break;
|
|
1507
1374
|
case 'dig': cmdResult = await commands.runDig(cmdOpts); break;
|
|
@@ -1511,6 +1378,20 @@ class AccountWorker {
|
|
|
1511
1378
|
case 'blackjack': cmdResult = await commands.runBlackjack(cmdOpts); break;
|
|
1512
1379
|
case 'trivia': cmdResult = await commands.runTrivia(cmdOpts); break;
|
|
1513
1380
|
case 'work shift': cmdResult = await commands.runWorkShift(cmdOpts); break;
|
|
1381
|
+
case 'work apply': {
|
|
1382
|
+
const ar = await commands.autoApplyForJob(cmdOpts);
|
|
1383
|
+
cmdResult = ar?.applied
|
|
1384
|
+
? { result: 'work apply completed', coins: 0, nextCooldownSec: 8 }
|
|
1385
|
+
: { result: `work apply failed${ar?.cooldownSec ? ` (${Math.ceil(ar.cooldownSec / 60)}m)` : ''}`, coins: 0, nonPlay: true, nextCooldownSec: ar?.cooldownSec || 60 };
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
case 'shop view': {
|
|
1389
|
+
const bought = await commands.buyItem({ ...cmdOpts, itemName: 'Shovel', quantity: 1 });
|
|
1390
|
+
cmdResult = bought
|
|
1391
|
+
? { result: 'shop buy item completed', coins: 0, nextCooldownSec: 10 }
|
|
1392
|
+
: { result: 'shop buy item failed', coins: 0, nonPlay: true, nextCooldownSec: 25 };
|
|
1393
|
+
break;
|
|
1394
|
+
}
|
|
1514
1395
|
case 'cointoss': cmdResult = await commands.runCointoss(cmdOpts); break;
|
|
1515
1396
|
case 'roulette': cmdResult = await commands.runRoulette(cmdOpts); break;
|
|
1516
1397
|
case 'slots': cmdResult = await commands.runSlots(cmdOpts); break;
|
|
@@ -1525,6 +1406,7 @@ class AccountWorker {
|
|
|
1525
1406
|
|
|
1526
1407
|
const result = cmdResult.result || 'done';
|
|
1527
1408
|
const resultLower = result.toLowerCase();
|
|
1409
|
+
this._lastCommandMeta.result = result;
|
|
1528
1410
|
|
|
1529
1411
|
// Rate limit detection — progressive backoff based on frequency
|
|
1530
1412
|
if (resultLower.includes('slow down') || resultLower.includes('rate limit') || resultLower.includes('too fast')) {
|
|
@@ -1534,6 +1416,7 @@ class AccountWorker {
|
|
|
1534
1416
|
const cooldownSec = Math.min(30 * Math.pow(2, Math.min(this._rateLimitHits - 1, 3)), 300);
|
|
1535
1417
|
this.log('warn', `Rate limited! ${cooldownSec}s cooldown (hit #${this._rateLimitHits})`);
|
|
1536
1418
|
this.globalCooldownUntil = Date.now() + cooldownSec * 1000;
|
|
1419
|
+
this._lastCommandMeta.nextRetrySec = cooldownSec;
|
|
1537
1420
|
await this.setCooldown(cmdName, cooldownSec);
|
|
1538
1421
|
// Reset rate limit count after 10 minutes of no hits
|
|
1539
1422
|
setTimeout(() => { if (this._rateLimitHits > 0) this._rateLimitHits = Math.max(0, this._rateLimitHits - 1); }, 600_000);
|
|
@@ -1544,6 +1427,8 @@ class AccountWorker {
|
|
|
1544
1427
|
if (resultLower.includes('cannot post another meme') || resultLower.includes('dead meme')) {
|
|
1545
1428
|
const minMatch = result.match(/(\d+)\s*minute/i);
|
|
1546
1429
|
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 + 30 : 150; // dead meme = N min + 30s buffer
|
|
1430
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1431
|
+
this._lastCommandMeta.nextRetrySec = cdSec;
|
|
1547
1432
|
this.log('warn', `${cmdName} on cooldown: ${cdSec}s`);
|
|
1548
1433
|
await this.setCooldown(cmdName, cdSec);
|
|
1549
1434
|
return;
|
|
@@ -1558,7 +1443,34 @@ class AccountWorker {
|
|
|
1558
1443
|
if (lvMatch) {
|
|
1559
1444
|
const targetLv = parseInt(lvMatch[1]);
|
|
1560
1445
|
cmdResult.levelLocked = targetLv;
|
|
1446
|
+
const progress = this._parseLevelQuestProgress(targetLv, allDetectionText);
|
|
1447
|
+
if (progress) {
|
|
1448
|
+
cmdResult.levelQuestProgress = progress;
|
|
1449
|
+
this._mergeLevelQuestProgress(targetLv, progress);
|
|
1450
|
+
}
|
|
1561
1451
|
this.log('warn', `Command /${cmdName} requires Level ${targetLv} quests`);
|
|
1452
|
+
} else {
|
|
1453
|
+
// Fallback: some responses are truncated/ephemeral and omit "Level X" in result text.
|
|
1454
|
+
// Run one profile check and infer lock level from raw profile text.
|
|
1455
|
+
try {
|
|
1456
|
+
const profile = await this.checkProfile(true);
|
|
1457
|
+
const profileText = String(profile?.rawText || '');
|
|
1458
|
+
if (profileText.toLowerCase().includes('not unlocked') || profileText.toLowerCase().includes('have not unlocked')) {
|
|
1459
|
+
const pm = profileText.match(/level\s*(\d+)/i);
|
|
1460
|
+
if (pm) {
|
|
1461
|
+
const targetLv = parseInt(pm[1], 10);
|
|
1462
|
+
if (Number.isFinite(targetLv) && targetLv > 0) {
|
|
1463
|
+
cmdResult.levelLocked = targetLv;
|
|
1464
|
+
const progress = this._parseLevelQuestProgress(targetLv, profileText);
|
|
1465
|
+
if (progress) {
|
|
1466
|
+
cmdResult.levelQuestProgress = progress;
|
|
1467
|
+
this._mergeLevelQuestProgress(targetLv, progress);
|
|
1468
|
+
}
|
|
1469
|
+
this.log('warn', `Command /${cmdName} requires Level ${targetLv} quests (profile fallback)`);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
} catch {}
|
|
1562
1474
|
}
|
|
1563
1475
|
}
|
|
1564
1476
|
|
|
@@ -1654,6 +1566,7 @@ class AccountWorker {
|
|
|
1654
1566
|
if (resultLower.includes('already got your daily') || resultLower.includes('already got your weekly') ||
|
|
1655
1567
|
resultLower.includes('already got your monthly') || resultLower.includes('already claimed') ||
|
|
1656
1568
|
resultLower.includes('try again <t:')) {
|
|
1569
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1657
1570
|
this.log('info', `${cmdName} already claimed — waiting`);
|
|
1658
1571
|
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
1659
1572
|
let waitSec;
|
|
@@ -1664,6 +1577,7 @@ class AccountWorker {
|
|
|
1664
1577
|
const defaultWaits = { daily: 86400, weekly: 604800, monthly: 2592000 };
|
|
1665
1578
|
waitSec = defaultWaits[cmdName] || 86400;
|
|
1666
1579
|
}
|
|
1580
|
+
this._lastCommandMeta.nextRetrySec = waitSec;
|
|
1667
1581
|
await this.setCooldown(cmdName, waitSec);
|
|
1668
1582
|
this.doneToday.set(cmdName, Date.now() + waitSec * 1000);
|
|
1669
1583
|
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', waitSec); } catch {}
|
|
@@ -1689,6 +1603,7 @@ class AccountWorker {
|
|
|
1689
1603
|
// Track net earnings (add wins, subtract losses)
|
|
1690
1604
|
this.stats.coins += (earned - spent);
|
|
1691
1605
|
if (cmdResult.nextCooldownSec) {
|
|
1606
|
+
this._lastCommandMeta.nextRetrySec = Math.max(this._lastCommandMeta.nextRetrySec || 0, cmdResult.nextCooldownSec);
|
|
1692
1607
|
await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
1693
1608
|
this._lastCooldownOverride = cmdResult.nextCooldownSec;
|
|
1694
1609
|
// Learn: record this cooldown as the known value for future fallback use
|
|
@@ -1732,6 +1647,7 @@ class AccountWorker {
|
|
|
1732
1647
|
this._lastCommandMeta.nonPlay = true;
|
|
1733
1648
|
this._lastCommandMeta.holdTightReason = reason;
|
|
1734
1649
|
const holdSec = 35;
|
|
1650
|
+
this._lastCommandMeta.nextRetrySec = Math.max(this._lastCommandMeta.nextRetrySec || 0, holdSec);
|
|
1735
1651
|
this.log('warn', `Hold Tight: /${reason} — ${holdSec}s global cooldown`);
|
|
1736
1652
|
const reasonMap = { postmemes: 'pm', highlow: 'hl', blackjack: 'bj', 'work shift': 'work shift' };
|
|
1737
1653
|
const mappedCmd = reasonMap[reason] || reason;
|
|
@@ -1746,10 +1662,10 @@ class AccountWorker {
|
|
|
1746
1662
|
this._lastCommandMeta.nonPlay = true;
|
|
1747
1663
|
this._lastCommandMeta.levelLocked = targetLv;
|
|
1748
1664
|
this.log('warn', `Command /${cmdName} is LOCKED at Level ${targetLv} — starting quest unlock flow`);
|
|
1749
|
-
if (this._startLevelQuests(targetLv)) {
|
|
1665
|
+
if (this._startLevelQuests(targetLv, cmdResult.levelQuestProgress || null)) {
|
|
1750
1666
|
this.log('info', `[QUEST] Quest mode activated for Level ${targetLv} — grinding will resume after quests complete`);
|
|
1751
1667
|
} else {
|
|
1752
|
-
this.log('warn', `[QUEST] Level ${targetLv} quests already done or not defined —
|
|
1668
|
+
this.log('warn', `[QUEST] Level ${targetLv} quests already done or not defined — waiting for verification`);
|
|
1753
1669
|
}
|
|
1754
1670
|
// Don't count as success — skip normal post-command processing
|
|
1755
1671
|
return;
|
|
@@ -1911,10 +1827,21 @@ class AccountWorker {
|
|
|
1911
1827
|
{ cmd: 'search', times: 2 },
|
|
1912
1828
|
{ cmd: 'tidy', times: 2 },
|
|
1913
1829
|
],
|
|
1830
|
+
2: [
|
|
1831
|
+
{ cmd: 'inventory', times: 1, progressMatch: 'inventory', progressSlash: 'inventory' },
|
|
1832
|
+
{ cmd: 'balance', times: 1, progressMatch: 'balance', progressSlash: 'balance' },
|
|
1833
|
+
{ cmd: 'hunt', times: 2, progressMatch: 'hunt', progressSlash: 'hunt' },
|
|
1834
|
+
{ cmd: 'dig', times: 2, progressMatch: 'dig', progressSlash: 'dig' },
|
|
1835
|
+
],
|
|
1914
1836
|
3: [
|
|
1915
|
-
{ cmd: 'work apply',
|
|
1916
|
-
{ cmd: 'work shift',
|
|
1917
|
-
{ cmd: 'shop sell
|
|
1837
|
+
{ cmd: 'work apply', times: 1, progressMatch: 'get a job', progressSlash: 'work apply' },
|
|
1838
|
+
{ cmd: 'work shift', times: 1, progressMatch: 'work a shift', progressSlash: 'work shift' },
|
|
1839
|
+
{ cmd: 'shop sell item shovel 1',times: 2, progressMatch: 'sell items', progressSlash: 'shop sell' },
|
|
1840
|
+
{ cmd: 'shop view', times: 1, progressMatch: 'buy an item', progressSlash: 'shop view' },
|
|
1841
|
+
],
|
|
1842
|
+
4: [
|
|
1843
|
+
// Keep this command-driven so the quest runner can continue progression.
|
|
1844
|
+
{ cmd: 'bal', times: 1, progressMatch: 'balance', progressSlash: 'balance' },
|
|
1918
1845
|
],
|
|
1919
1846
|
5: [
|
|
1920
1847
|
// "Lose 50,000 coins in /slots" etc — loseTarget means keep playing until cumulative losses hit 50k
|
|
@@ -1922,13 +1849,141 @@ class AccountWorker {
|
|
|
1922
1849
|
{ cmd: 'cointoss', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1923
1850
|
{ cmd: 'snakeeyes', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1924
1851
|
],
|
|
1852
|
+
6: [
|
|
1853
|
+
// Slash text in lock-card maps to regular pls command strings below.
|
|
1854
|
+
{ cmd: 'item new player pack', times: 1, progressMatch: 'new player pack', progressSlash: 'item' },
|
|
1855
|
+
{ cmd: 'use player 1', times: 1, progressMatch: 'new player pack', progressSlash: 'use' },
|
|
1856
|
+
{ cmd: 'use normie 1', times: 1, progressMatch: 'normie box', progressSlash: 'use' },
|
|
1857
|
+
],
|
|
1858
|
+
7: [
|
|
1859
|
+
{ cmd: 'title set newbie', times: 1, progressMatch: 'newbie title', progressSlash: 'title' },
|
|
1860
|
+
{ cmd: 'profile', times: 1, progressMatch: 'profile', progressSlash: 'profile' },
|
|
1861
|
+
{ cmd: 'daily', times: 1, progressMatch: 'daily', progressSlash: 'daily' },
|
|
1862
|
+
{ cmd: 'hl', times: 3, progressMatch: 'highlow', progressSlash: 'highlow' },
|
|
1863
|
+
],
|
|
1925
1864
|
};
|
|
1926
1865
|
|
|
1927
|
-
|
|
1866
|
+
_parseLevelQuestProgress(targetLevel, rawText) {
|
|
1867
|
+
const text = String(rawText || '');
|
|
1868
|
+
const out = {};
|
|
1869
|
+
|
|
1870
|
+
if (targetLevel === 5) {
|
|
1871
|
+
const re = /(\d[\d,]*)\s*\/\s*50,000[\s\S]{0,180}?<\/(slots|cointoss|snakeeyes):/gi;
|
|
1872
|
+
let m;
|
|
1873
|
+
while ((m = re.exec(text)) !== null) {
|
|
1874
|
+
const cur = parseInt(String(m[1] || '').replace(/,/g, ''), 10);
|
|
1875
|
+
const cmd = String(m[2] || '').toLowerCase();
|
|
1876
|
+
if (Number.isFinite(cur) && cmd) out[cmd] = Math.max(0, cur);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
if (targetLevel === 6) {
|
|
1881
|
+
// handled below by generic times-based parser
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if (targetLevel !== 5) {
|
|
1885
|
+
const quests = AccountWorker.LEVEL_QUESTS[targetLevel] || [];
|
|
1886
|
+
const lines = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
1887
|
+
for (const q of quests) {
|
|
1888
|
+
if (q.loseTarget) continue;
|
|
1889
|
+
const cmd = String(q.cmd || '').toLowerCase();
|
|
1890
|
+
const cmdParts = cmd.split(/\s+/).filter(Boolean);
|
|
1891
|
+
if (cmdParts.length === 0) continue;
|
|
1892
|
+
|
|
1893
|
+
const slashHints = new Set();
|
|
1894
|
+
slashHints.add(cmdParts[0]);
|
|
1895
|
+
if (q.progressSlash) {
|
|
1896
|
+
for (const p of String(q.progressSlash).toLowerCase().split(/[\s,/|]+/).filter(Boolean)) {
|
|
1897
|
+
slashHints.add(p);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (cmd === 'hl') slashHints.add('highlow');
|
|
1901
|
+
if (cmd === 'highlow') slashHints.add('hl');
|
|
1902
|
+
|
|
1903
|
+
const matchPhrase = String(q.progressMatch || q.cmd || '').toLowerCase();
|
|
1904
|
+
const tail = matchPhrase
|
|
1905
|
+
.split(/\s+/)
|
|
1906
|
+
.filter(Boolean)
|
|
1907
|
+
.filter(t => !/^\d+$/.test(t))
|
|
1908
|
+
.filter(t => t !== cmdParts[0]);
|
|
1909
|
+
|
|
1910
|
+
for (const line of lines) {
|
|
1911
|
+
const lower = line.toLowerCase();
|
|
1912
|
+
const hasSlash = Array.from(slashHints).some(h => lower.includes(`/${h}`));
|
|
1913
|
+
if (!hasSlash) continue;
|
|
1914
|
+
|
|
1915
|
+
let ok = true;
|
|
1916
|
+
for (const token of tail) {
|
|
1917
|
+
if (!lower.includes(token)) { ok = false; break; }
|
|
1918
|
+
}
|
|
1919
|
+
if (!ok) continue;
|
|
1920
|
+
|
|
1921
|
+
const m = lower.match(/(\d[\d,]*)\s*\/\s*(\d[\d,]*)/);
|
|
1922
|
+
if (!m) continue;
|
|
1923
|
+
const cur = parseInt(String(m[1] || '').replace(/,/g, ''), 10);
|
|
1924
|
+
if (!Number.isFinite(cur)) continue;
|
|
1925
|
+
out[cmd] = Math.max(out[cmd] || 0, cur);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
_mergeLevelQuestProgress(targetLevel, progressHint) {
|
|
1934
|
+
if (!progressHint) return;
|
|
1935
|
+
const prev = this._levelQuestProgressCache.get(targetLevel) || {};
|
|
1936
|
+
const merged = { ...prev };
|
|
1937
|
+
for (const [cmd, val] of Object.entries(progressHint)) {
|
|
1938
|
+
const n = Number(val);
|
|
1939
|
+
if (!Number.isFinite(n) || n < 0) continue;
|
|
1940
|
+
merged[cmd] = Math.max(merged[cmd] || 0, n);
|
|
1941
|
+
}
|
|
1942
|
+
this._levelQuestProgressCache.set(targetLevel, merged);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
_startLevelQuests(targetLevel, progressHint = null) {
|
|
1928
1946
|
if (this._levelQuestDone.has(targetLevel)) return false;
|
|
1929
1947
|
const quests = AccountWorker.LEVEL_QUESTS[targetLevel];
|
|
1930
1948
|
if (!quests || quests.length === 0) return false;
|
|
1931
|
-
|
|
1949
|
+
|
|
1950
|
+
this._mergeLevelQuestProgress(targetLevel, progressHint);
|
|
1951
|
+
const cached = this._levelQuestProgressCache.get(targetLevel) || {};
|
|
1952
|
+
const seededAll = quests
|
|
1953
|
+
.map(q => {
|
|
1954
|
+
const target = Number(q.loseTarget || 0);
|
|
1955
|
+
const seededLoss = target > 0 ? Math.min(target, Math.max(0, Number(cached[q.cmd] || 0))) : 0;
|
|
1956
|
+
const seededDone = target === 0 ? Math.max(0, Math.min(Number(q.times || 0), Number(cached[q.cmd] || 0))) : 0;
|
|
1957
|
+
const timesRemaining = target === 0 ? Math.max(0, Number(q.times || 0) - seededDone) : Number(q.times || 0);
|
|
1958
|
+
const defaultCdSec = this._getCommandDefaultCooldownSec(q.cmd);
|
|
1959
|
+
return {
|
|
1960
|
+
...q,
|
|
1961
|
+
level: targetLevel,
|
|
1962
|
+
_lostSoFar: seededLoss,
|
|
1963
|
+
_doneSoFar: seededDone,
|
|
1964
|
+
times: timesRemaining,
|
|
1965
|
+
_defaultCdSec: defaultCdSec,
|
|
1966
|
+
_nextRunAt: 0,
|
|
1967
|
+
};
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
const seededQueue = seededAll
|
|
1971
|
+
.filter(q => {
|
|
1972
|
+
if (q.loseTarget) return q._lostSoFar < q.loseTarget;
|
|
1973
|
+
return q.times > 0;
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
this._initLevelQuestRunState(targetLevel, seededAll);
|
|
1977
|
+
|
|
1978
|
+
if (seededQueue.length === 0) {
|
|
1979
|
+
this.log('info', `[QUEST] Level ${targetLevel} tasks already complete from progress cache — verifying unlock`);
|
|
1980
|
+
this._verifyLevelUnlock = targetLevel;
|
|
1981
|
+
this.setStatus('verifying level...');
|
|
1982
|
+
this.commandQueue = null;
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
this._levelQuestQueue = seededQueue;
|
|
1932
1987
|
this._levelQuestActive = true;
|
|
1933
1988
|
this._questBetOverride = null;
|
|
1934
1989
|
// Clear commandQueue so no stale grinding commands fire after quests finish
|
|
@@ -1936,13 +1991,81 @@ class AccountWorker {
|
|
|
1936
1991
|
this._commandRunning = false; // cancel any in-flight grinding command
|
|
1937
1992
|
this.log('info', `[QUEST] Level ${targetLevel} quests started — grinding PAUSED`);
|
|
1938
1993
|
const questList = this._levelQuestQueue.map(q => {
|
|
1939
|
-
if (q.loseTarget)
|
|
1994
|
+
if (q.loseTarget) {
|
|
1995
|
+
const rem = Math.max(0, q.loseTarget - (q._lostSoFar || 0));
|
|
1996
|
+
return `"${q.cmd}" lose ⏣${rem.toLocaleString()} more`;
|
|
1997
|
+
}
|
|
1940
1998
|
return `"${q.cmd}" x${q.times}`;
|
|
1941
1999
|
}).join(', ');
|
|
1942
2000
|
this.log('info', `[QUEST] Quests: ${questList}`);
|
|
1943
2001
|
return true;
|
|
1944
2002
|
}
|
|
1945
2003
|
|
|
2004
|
+
_getCommandDefaultCooldownSec(cmdName) {
|
|
2005
|
+
const cmd = String(cmdName || '').toLowerCase().trim();
|
|
2006
|
+
if (!cmd) return 8;
|
|
2007
|
+
const map = AccountWorker.COMMAND_MAP || [];
|
|
2008
|
+
const exact = map.find(r => String(r.cmd || '').toLowerCase() === cmd);
|
|
2009
|
+
if (exact && Number.isFinite(exact.defaultCd) && exact.defaultCd > 0) return exact.defaultCd;
|
|
2010
|
+
if (cmd === 'highlow') {
|
|
2011
|
+
const hl = map.find(r => String(r.cmd || '').toLowerCase() === 'hl');
|
|
2012
|
+
if (hl?.defaultCd) return hl.defaultCd;
|
|
2013
|
+
}
|
|
2014
|
+
const head = cmd.split(/\s+/)[0];
|
|
2015
|
+
const byHead = map.find(r => String(r.cmd || '').toLowerCase() === head);
|
|
2016
|
+
if (byHead && Number.isFinite(byHead.defaultCd) && byHead.defaultCd > 0) return byHead.defaultCd;
|
|
2017
|
+
return 10;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
_initLevelQuestRunState(targetLevel, questRows) {
|
|
2021
|
+
const startedAt = Date.now();
|
|
2022
|
+
const state = {
|
|
2023
|
+
level: targetLevel,
|
|
2024
|
+
startedAt,
|
|
2025
|
+
updatedAt: startedAt,
|
|
2026
|
+
completedAt: 0,
|
|
2027
|
+
entries: new Map(),
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
for (const q of (questRows || [])) {
|
|
2031
|
+
const cmd = String(q.cmd || '').toLowerCase();
|
|
2032
|
+
if (!cmd) continue;
|
|
2033
|
+
const required = q.loseTarget
|
|
2034
|
+
? Number(q.loseTarget || 0)
|
|
2035
|
+
: Math.max(0, Number(q._doneSoFar || 0) + Number(q.times || 0));
|
|
2036
|
+
const completed = q.loseTarget
|
|
2037
|
+
? Math.max(0, Number(q._lostSoFar || 0))
|
|
2038
|
+
: Math.max(0, Number(q._doneSoFar || 0));
|
|
2039
|
+
const remaining = q.loseTarget
|
|
2040
|
+
? Math.max(0, required - completed)
|
|
2041
|
+
: Math.max(0, Number(q.times || 0));
|
|
2042
|
+
|
|
2043
|
+
state.entries.set(cmd, {
|
|
2044
|
+
cmd,
|
|
2045
|
+
required,
|
|
2046
|
+
completed,
|
|
2047
|
+
remaining,
|
|
2048
|
+
attemptsLeft: Number(q.times || 0),
|
|
2049
|
+
runs: 0,
|
|
2050
|
+
success: 0,
|
|
2051
|
+
failed: 0,
|
|
2052
|
+
nonPlay: 0,
|
|
2053
|
+
lastResult: '',
|
|
2054
|
+
lastRunAt: 0,
|
|
2055
|
+
nextRunAt: Number(q._nextRunAt || 0),
|
|
2056
|
+
status: remaining <= 0 ? 'done' : 'pending',
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
this._levelQuestRunMap.set(targetLevel, state);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
_getLevelQuestRunEntry(level, cmdName) {
|
|
2064
|
+
const st = this._levelQuestRunMap.get(level);
|
|
2065
|
+
if (!st?.entries) return null;
|
|
2066
|
+
return st.entries.get(String(cmdName || '').toLowerCase()) || null;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
1946
2069
|
async buildCommandQueue() {
|
|
1947
2070
|
const heap = new MinHeap();
|
|
1948
2071
|
const now = Date.now();
|
|
@@ -2204,11 +2327,18 @@ class AccountWorker {
|
|
|
2204
2327
|
if (currentLv >= targetLv) {
|
|
2205
2328
|
this.log('success', `[QUEST] Level ${targetLv} verified ✓ — level unlocked!`);
|
|
2206
2329
|
this._level = currentLv;
|
|
2330
|
+
this._levelQuestProgressCache.delete(targetLv);
|
|
2331
|
+
const st = this._levelQuestRunMap.get(targetLv);
|
|
2332
|
+
if (st) {
|
|
2333
|
+
st.updatedAt = Date.now();
|
|
2334
|
+
st.verifiedAt = Date.now();
|
|
2335
|
+
}
|
|
2207
2336
|
} else {
|
|
2208
2337
|
this.log('warn', `[QUEST] Level ${targetLv} NOT verified — still at ${currentLv}. Re-triggering quests.`);
|
|
2209
2338
|
// Re-trigger quests for this level
|
|
2210
2339
|
this._levelQuestDone.delete(targetLv);
|
|
2211
|
-
this.
|
|
2340
|
+
const cached = this._levelQuestProgressCache.get(targetLv) || null;
|
|
2341
|
+
this._startLevelQuests(targetLv, cached);
|
|
2212
2342
|
this.tickTimeout = setTimeout(() => this.tick(), 2000);
|
|
2213
2343
|
return;
|
|
2214
2344
|
}
|
|
@@ -2223,7 +2353,22 @@ class AccountWorker {
|
|
|
2223
2353
|
|
|
2224
2354
|
// BLOCK: quest mode active — run quests only, no normal grinding
|
|
2225
2355
|
if (this._levelQuestActive && this._levelQuestQueue.length > 0) {
|
|
2356
|
+
const now = Date.now();
|
|
2357
|
+
let dueIdx = this._levelQuestQueue.findIndex(q => !q._nextRunAt || q._nextRunAt <= now);
|
|
2358
|
+
if (dueIdx < 0) {
|
|
2359
|
+
const soonest = Math.min(...this._levelQuestQueue.map(q => Number(q._nextRunAt || now + 5000)));
|
|
2360
|
+
const waitMs = Math.max(1200, Math.min(60000, soonest - now));
|
|
2361
|
+
this.setStatus(`[QUEST] cooldown wait ${Math.ceil(waitMs / 1000)}s`);
|
|
2362
|
+
this.tickTimeout = setTimeout(() => this.tick(), waitMs);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (dueIdx > 0) {
|
|
2366
|
+
const [dueQuest] = this._levelQuestQueue.splice(dueIdx, 1);
|
|
2367
|
+
this._levelQuestQueue.unshift(dueQuest);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2226
2370
|
const quest = this._levelQuestQueue[0];
|
|
2371
|
+
let nextQuestDelayMs = 1200;
|
|
2227
2372
|
if (quest.loseTarget) {
|
|
2228
2373
|
const remaining = Math.max(0, quest.loseTarget - (quest._lostSoFar || 0));
|
|
2229
2374
|
const floorBet = quest._minBet || quest.bet || 1;
|
|
@@ -2231,7 +2376,6 @@ class AccountWorker {
|
|
|
2231
2376
|
} else {
|
|
2232
2377
|
this._questBetOverride = quest.bet || null;
|
|
2233
2378
|
}
|
|
2234
|
-
const questBetUsed = this._questBetOverride;
|
|
2235
2379
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
2236
2380
|
|
|
2237
2381
|
if (quest.loseTarget) {
|
|
@@ -2250,7 +2394,13 @@ class AccountWorker {
|
|
|
2250
2394
|
this._commandRunning = true;
|
|
2251
2395
|
this.busy = true;
|
|
2252
2396
|
await this.runCommand(quest.cmd, prefix);
|
|
2253
|
-
|
|
2397
|
+
const questMeta = this._lastCommandMeta || {};
|
|
2398
|
+
const qEntry = this._getLevelQuestRunEntry(quest.level, quest.cmd);
|
|
2399
|
+
if (qEntry) {
|
|
2400
|
+
qEntry.runs++;
|
|
2401
|
+
qEntry.lastRunAt = Date.now();
|
|
2402
|
+
qEntry.lastResult = String(questMeta.result || '');
|
|
2403
|
+
}
|
|
2254
2404
|
this._commandRunning = false;
|
|
2255
2405
|
this.busy = false;
|
|
2256
2406
|
this._questBetOverride = null;
|
|
@@ -2270,13 +2420,13 @@ class AccountWorker {
|
|
|
2270
2420
|
quest._minBet = Math.max(quest._minBet || 1, questMeta.newMinBet);
|
|
2271
2421
|
}
|
|
2272
2422
|
|
|
2273
|
-
// Fallback only for actual played rounds where parsed/observed loss was zero.
|
|
2274
|
-
if (!nonPlay && lostThisRound === 0 && (questBetUsed || quest.bet)) {
|
|
2275
|
-
lostThisRound = questBetUsed || quest.bet;
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
2423
|
if (!nonPlay) {
|
|
2279
2424
|
quest._lostSoFar = (quest._lostSoFar || 0) + lostThisRound;
|
|
2425
|
+
this._mergeLevelQuestProgress(quest.level, { [quest.cmd]: quest._lostSoFar });
|
|
2426
|
+
if (qEntry) qEntry.success++;
|
|
2427
|
+
} else if (qEntry) {
|
|
2428
|
+
qEntry.nonPlay++;
|
|
2429
|
+
qEntry.failed++;
|
|
2280
2430
|
}
|
|
2281
2431
|
|
|
2282
2432
|
this.log('info', `[QUEST] ${quest.cmd} — lost ⏣${quest._lostSoFar.toLocaleString()} / ⏣${quest.loseTarget.toLocaleString()}`);
|
|
@@ -2295,10 +2445,57 @@ class AccountWorker {
|
|
|
2295
2445
|
questStepDone = true;
|
|
2296
2446
|
this.log('info', `[QUEST] ${quest.cmd} max attempts reached — moving on`);
|
|
2297
2447
|
}
|
|
2448
|
+
|
|
2449
|
+
if (qEntry) {
|
|
2450
|
+
qEntry.completed = Math.max(0, Number(quest._lostSoFar || 0));
|
|
2451
|
+
qEntry.remaining = Math.max(0, Number(quest.loseTarget || 0) - qEntry.completed);
|
|
2452
|
+
qEntry.attemptsLeft = Number(quest.times || 0);
|
|
2453
|
+
qEntry.status = questStepDone ? 'done' : (nonPlay ? 'cooldown' : 'progress');
|
|
2454
|
+
}
|
|
2298
2455
|
} else {
|
|
2299
2456
|
// Normal times-based quest
|
|
2300
|
-
|
|
2457
|
+
const nonPlay = !!questMeta.nonPlay;
|
|
2458
|
+
if (!nonPlay) {
|
|
2459
|
+
quest.times--;
|
|
2460
|
+
quest._doneSoFar = (quest._doneSoFar || 0) + 1;
|
|
2461
|
+
this._mergeLevelQuestProgress(quest.level, { [quest.cmd]: quest._doneSoFar });
|
|
2462
|
+
if (qEntry) qEntry.success++;
|
|
2463
|
+
} else {
|
|
2464
|
+
if (qEntry) {
|
|
2465
|
+
qEntry.nonPlay++;
|
|
2466
|
+
qEntry.failed++;
|
|
2467
|
+
}
|
|
2468
|
+
this.log('warn', `[QUEST] ${quest.cmd} non-play response — progress unchanged`);
|
|
2469
|
+
// Rotate to next quest so we don't spam one command during cooldown.
|
|
2470
|
+
if (this._levelQuestQueue.length > 1) {
|
|
2471
|
+
const rotated = this._levelQuestQueue.shift();
|
|
2472
|
+
this._levelQuestQueue.push(rotated);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2301
2475
|
if (quest.times <= 0) questStepDone = true;
|
|
2476
|
+
|
|
2477
|
+
if (qEntry) {
|
|
2478
|
+
qEntry.completed = Math.max(0, Number(quest._doneSoFar || 0));
|
|
2479
|
+
qEntry.remaining = Math.max(0, Number(quest.times || 0));
|
|
2480
|
+
qEntry.attemptsLeft = Number(quest.times || 0);
|
|
2481
|
+
qEntry.status = questStepDone ? 'done' : (nonPlay ? 'cooldown' : 'progress');
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
const retrySec = Math.max(
|
|
2486
|
+
1,
|
|
2487
|
+
Number(questMeta.nextRetrySec || 0),
|
|
2488
|
+
Number(quest._defaultCdSec || 0)
|
|
2489
|
+
);
|
|
2490
|
+
quest._nextRunAt = Date.now() + retrySec * 1000;
|
|
2491
|
+
nextQuestDelayMs = Math.max(nextQuestDelayMs, Math.min(60000, Math.floor(retrySec * 1000)));
|
|
2492
|
+
if (qEntry) qEntry.nextRunAt = Number(quest._nextRunAt || 0);
|
|
2493
|
+
|
|
2494
|
+
const runState = this._levelQuestRunMap.get(quest.level);
|
|
2495
|
+
if (runState) runState.updatedAt = Date.now();
|
|
2496
|
+
|
|
2497
|
+
if (questMeta.nextRetrySec && Number.isFinite(questMeta.nextRetrySec) && questMeta.nextRetrySec > 0) {
|
|
2498
|
+
nextQuestDelayMs = Math.max(nextQuestDelayMs, Math.min(60000, Math.floor(questMeta.nextRetrySec * 1000)));
|
|
2302
2499
|
}
|
|
2303
2500
|
|
|
2304
2501
|
if (questStepDone) this._levelQuestQueue.shift();
|
|
@@ -2307,6 +2504,11 @@ class AccountWorker {
|
|
|
2307
2504
|
this._levelQuestActive = false;
|
|
2308
2505
|
this._levelQuestDone.add(quest.level);
|
|
2309
2506
|
const justCompletedLevel = quest.level;
|
|
2507
|
+
const st = this._levelQuestRunMap.get(justCompletedLevel);
|
|
2508
|
+
if (st) {
|
|
2509
|
+
st.completedAt = Date.now();
|
|
2510
|
+
st.updatedAt = st.completedAt;
|
|
2511
|
+
}
|
|
2310
2512
|
this.log('success', `[QUEST] Level ${justCompletedLevel} quests DONE — verifying unlock...`);
|
|
2311
2513
|
// Null out commandQueue so buildCommandQueue picks only unlocked commands
|
|
2312
2514
|
this.commandQueue = null;
|
|
@@ -2316,7 +2518,7 @@ class AccountWorker {
|
|
|
2316
2518
|
this.tickTimeout = setTimeout(() => this.tick(), 3000);
|
|
2317
2519
|
return;
|
|
2318
2520
|
}
|
|
2319
|
-
this.tickTimeout = setTimeout(() => this.tick(),
|
|
2521
|
+
this.tickTimeout = setTimeout(() => this.tick(), nextQuestDelayMs);
|
|
2320
2522
|
return;
|
|
2321
2523
|
}
|
|
2322
2524
|
|