- 23 source files in src/ (build via scripts/build.sh) - Forum-sourced player knowledge in references/ - DESIGN.md with architecture and corrections - References to existing scripts (Monsterbation, jpx, HV Utils)
140 lines
3.7 KiB
JavaScript
140 lines
3.7 KiB
JavaScript
// ═══════════════════════════════════════════════════════════════════════
|
|
// STATE — global runtime state
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
|
|
const SP = 'hvunified_';
|
|
|
|
const STATE = {
|
|
page: null,
|
|
level: 1,
|
|
tier: 'novice',
|
|
isekai: false,
|
|
battleMode: '',
|
|
inBattle: false,
|
|
battleInitialized: false,
|
|
|
|
hoverTarget: -1,
|
|
interruptHover: false,
|
|
interruptAlert: false,
|
|
|
|
monsters: [],
|
|
hp: 1,
|
|
mp: 1,
|
|
sp: 1,
|
|
oc: 0,
|
|
spiritStance: false,
|
|
|
|
buffs: {},
|
|
spellsKnown: [],
|
|
skillsKnown: [],
|
|
itemsKnown: {},
|
|
cooldowns: {},
|
|
|
|
battleMonsterNames: [],
|
|
channeling: false,
|
|
monsterData: {},
|
|
difficulty: 'Normal',
|
|
|
|
battleStats: {
|
|
damageDealt: 0,
|
|
damageTaken: 0,
|
|
turns: 0,
|
|
drops: 0,
|
|
credits: 0,
|
|
},
|
|
|
|
round: 0,
|
|
settingsVisible: false,
|
|
};
|
|
|
|
// Load monster data from localStorage
|
|
try {
|
|
STATE.monsterData = JSON.parse(localStorage[SP + 'monsters'] || '{}');
|
|
} catch (e) {
|
|
STATE.monsterData = {};
|
|
}
|
|
|
|
// ── helpers ──
|
|
|
|
function loadConfig() {
|
|
try {
|
|
const saved = JSON.parse(localStorage[SP + 'cfg'] || '{}');
|
|
for (const [k, v] of Object.entries(saved)) {
|
|
if (CFG[k] !== undefined) CFG[k] = v;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
function saveConfig() {
|
|
try { localStorage[SP + 'cfg'] = JSON.stringify(CFG); } catch (e) {}
|
|
}
|
|
|
|
function setConfig(key, value) {
|
|
if (CFG[key] !== undefined) {
|
|
CFG[key] = value;
|
|
saveConfig();
|
|
}
|
|
}
|
|
|
|
function readCSSDigits(container) {
|
|
if (!container) return null;
|
|
const digits = [];
|
|
container.querySelectorAll('div').forEach(c => {
|
|
const m = (c.className || '').match(/c4(\d)\b/);
|
|
if (m) digits.push(m[1]);
|
|
});
|
|
if (digits.length === 0) return null;
|
|
const parsed = parseInt(digits.join(''));
|
|
return isNaN(parsed) ? null : parsed;
|
|
}
|
|
|
|
// CSS font character map: c5a=a, c5b=b, ..., c5z=z
|
|
// c59 = space, c4a = period, c40-c49 = 0-9
|
|
// Note: no \\b word boundary — JS handles boundaries differently in class names
|
|
function readCSSText(container) {
|
|
if (!container) return '';
|
|
let text = '';
|
|
container.querySelectorAll('div').forEach(c => {
|
|
const cls = c.className || '';
|
|
// c5a-c5z → letters a-z
|
|
const letterMatch = cls.match(/c5([a-z])\b/);
|
|
if (letterMatch) {
|
|
text += letterMatch[1];
|
|
return;
|
|
}
|
|
// c59 → space
|
|
if (/c59\b/.test(cls)) {
|
|
text += ' ';
|
|
return;
|
|
}
|
|
// c4a → period
|
|
if (/c4a\b/.test(cls)) {
|
|
text += '.';
|
|
return;
|
|
}
|
|
// c40-c49 → digits 0-9
|
|
const digitMatch = cls.match(/c4([0-9])\b/);
|
|
if (digitMatch) {
|
|
text += digitMatch[1];
|
|
return;
|
|
}
|
|
});
|
|
return text;
|
|
}
|
|
|
|
// Save difficulty from last overworld page read
|
|
function cacheDifficulty() {
|
|
if (STATE.difficulty && STATE.difficulty !== 'Normal') {
|
|
try { localStorage[SP + 'difficulty'] = STATE.difficulty; } catch (e) {}
|
|
}
|
|
}
|
|
|
|
// Load cached difficulty (for battle pages where difficulty isn't in DOM)
|
|
function restoreCachedDifficulty() {
|
|
if (STATE.difficulty === 'Normal' || !STATE.difficulty) {
|
|
try {
|
|
const cached = localStorage[SP + 'difficulty'];
|
|
if (cached) STATE.difficulty = cached;
|
|
} catch (e) {}
|
|
}
|
|
}
|