- 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)
689 lines
34 KiB
Markdown
689 lines
34 KiB
Markdown
# Deep-Structural Analysis of jpx HentaiVerse Auto-Battler Userscript
|
||
|
||
**File:** `/home/gabogg/Downloads/jpx20260706.txt`
|
||
**Version:** 2026.07.06
|
||
**Size:** 270,752 bytes, 6,215 lines
|
||
**Language:** Vanilla JavaScript (no framework), single-file userscript
|
||
|
||
---
|
||
|
||
## 1. ARCHITECTURE
|
||
|
||
### 1.1 Initialization Flow
|
||
|
||
The entire script is a single-file userscript with a declarative bootstrap:
|
||
|
||
1. **Userscript Header** (lines 1-11): `@run-at document-end`, matches `*.hentaiverse.org/*`, excludes `/equip/*` and `/isekai/equip/*`. No GM_ grants needed — works purely via DOM.
|
||
|
||
2. **Module Bootstrap** (lines 463-465): Three singleton-style modules are immediately invoked:
|
||
- `jpxPanelManager()` — creates/configures the control widget overlay
|
||
- `jpxMarket()` — market price fetcher/cacher
|
||
- `jpxUtils()` — utility library
|
||
|
||
Each follows the pattern `function jpxThing() { const ns = jpxThing; if (ns._init) return ns; ... ns._init = true; return ns; }` — single-init guard via `_init` flag on the function object itself.
|
||
|
||
3. **Entry Point** (line 6215): `initDo()` is called at file's end.
|
||
|
||
4. **`initDo()`** (line 1242): The router function that:
|
||
- Injects CSS via `<style id="jpx">`
|
||
- Registers `beforeunload` handler (`storeTmp`) for crash-safe state persistence
|
||
- Registers `pointerdown` handler to dismiss multi-select popup panels
|
||
- Registers `keydown` handler with throttled `actionManager` (75ms throttle)
|
||
- Initializes I18N (`initDoI18n`)
|
||
- **Page Detection** via `document.querySelector('#textlog')`:
|
||
- **If `#textlog` exists AND `doInitDoBattle` is false** → `initDoBattle()` — we're in a battle
|
||
- **If `#riddlemaster` exists** → `riddleRecorder()` — RiddleMaster encounter
|
||
- **If no `#textlog` (Lobby)** → reads player info (level, stamina, difficulty, persona, spell damage bonus) from DOM selectors and stores in localStorage
|
||
|
||
### 1.2 Hooking Into the Game
|
||
|
||
The script hooks into the game at three levels:
|
||
|
||
1. **MutationObserver on `#textlog`** (line 1541): A `MutationObserver` watches `log.firstChild` for `childList` changes. Every time new battle log text is added, `preProcessLog()` fires (throttled at 200ms, trailing). This is the primary event loop.
|
||
|
||
2. **DOM event listeners**:
|
||
- `keydown` on `document` (capture phase, line 1255) — for hotkeys and key-bound battle actions
|
||
- `pointerdown` on `document` (line 1249) — for dismissing multi-select panels
|
||
- `mousemove` on `window` (line 1366) — sets `jpxPanelManager.ready = true`
|
||
- `DOMContentLoaded` for `reDoBattle()` on round transitions (line 1369)
|
||
- `beforeunload` on `window` (line 1248) — calls `storeTmp()` to persist state
|
||
|
||
3. **Custom AJAX round advancement** (lines 1623-1716): When `ajaxRound` is enabled, the script intercepts the "Continue" button (`#btcp`) onclick, replaces it with an async XHR-based flow that fetches the next round HTML, handles RiddleMaster encounters inline, and re-initializes the game's `window.Battle` object via `document.dispatchEvent(new Event('DOMContentLoaded'))`.
|
||
|
||
### 1.3 Page/Context Detection
|
||
|
||
The script detects its environment via DOM selectors with no URL parsing beyond the `isekaiSuffix` (line 475):
|
||
|
||
| Condition | State | Action |
|
||
|-----------|-------|--------|
|
||
| `#textlog` exists + `!doInitDoBattle` | In battle | `initDoBattle()` |
|
||
| `#riddlemaster` exists | RiddleMaster | `riddleRecorder()` |
|
||
| No `#textlog`, Bazaar AM modify or Battle IW | Lobby (IW equipment screen) | Observe `#equipform` for world level/difficulty |
|
||
| No `#textlog`, `s=Battle` query | Lobby (battle selection) | Read level, stamina, persona, spell damage bonus |
|
||
| Default | Lobby (other) | No action |
|
||
|
||
---
|
||
|
||
## 2. RULE ENGINE
|
||
|
||
### 2.1 Configuration Structure
|
||
|
||
Battle configuration is stored in `cfgBattle` (line 13), merged from:
|
||
- `defaultCfgBattle` (lines 14-229) — hardcoded defaults for all fighting styles
|
||
- `localStorage['jpx_cfgBattle' + isekaiSuffix]` — user overrides
|
||
- Merged by `mergeCfg()` (line 5034) with version-aware reset logic
|
||
|
||
The config schema for each battle mode (`{Style}_{Type}`) has three sections:
|
||
- `supports` — array of support actions (healing, buffs, items) evaluated first
|
||
- `attacks` — array of offensive actions evaluated second
|
||
- `kb_*` (key-bindings) — direct hotkey-triggered action sequences stored as dynamic keys
|
||
|
||
The supported battle mode keys follow BATTLE_MODES (line 657):
|
||
```
|
||
Style × Variant: 10 styles × 6 types = 60 possible modes
|
||
Styles: OneHanded, 1H_Mage, TwoHanded, 2H_Mage, DualWielding, DW_Mage, NitenIchiryu, NI_Mage, Staff, Unarmed
|
||
Types: General, Arena, Encounter, Colosseum, Battle1000, Item, Tower
|
||
```
|
||
|
||
### 2.2 Action Types
|
||
|
||
Each action is an object with a `type` discriminator and optional `conditions` array:
|
||
|
||
| Type | Description | Parameters |
|
||
|------|-------------|------------|
|
||
| `stop` | Halts auto-battle with custom message | `customMessage`, `conditions` |
|
||
| `spellSupport` | Casts a supportive spell | `name` (from `SPELLS_SUPPORT`), `conditions` |
|
||
| `item` | Uses an inventory item | `name` (from `ITEMS`), `conditions` |
|
||
| `toggle` | Toggles Spirit Stance/Defend/Focus | `name`, `toggled` (boolean), `conditions` |
|
||
| `target` | Selects a target monster for subsequent actions | `priorityRule` (from `PRIORITY_RULES`), `conditions` |
|
||
| `smartDebuff` | Casts AoE debuffs with spatial targeting | `name`, `targetCount` (1-3), `bottomUp`, `tailSkip`, `maxAtFirst`, `minMonstersLeft`, `conditions` |
|
||
| `spellDebuff` | Casts single-target debuff on selected monster | `name`, `conditions` |
|
||
| `spellDamage` | Casts offensive spell on selected monster | `name` (can be `T1`/`T2`/`T3` aliases), `conditions` |
|
||
| `skill` | Uses a weapon skill | `name` (from `SKILLS`), `conditions` |
|
||
| `normalAttack` | Basic attack on selected monster | None |
|
||
|
||
### 2.3 Condition System
|
||
|
||
#### Condition Categories
|
||
|
||
**General Conditions** (`conditionsGeneral`, lines 947-979): Checked against global/player state:
|
||
- `world` — Persistent vs Isekai
|
||
- `pLevel` — Player level range
|
||
- `pMaxSpellType` — Player's strongest spell element
|
||
- `battleTypes` — Battle type(s) (Arena, Encounter, etc.)
|
||
- `difficulty` — Difficulty array
|
||
- `roundCurrent`, `roundLeft`, `roundTotal` — Round position
|
||
- `floor` — Tower floor range
|
||
- `pActionCooldown` — Cooldown range of specific actions (skill/spell/item)
|
||
- `pActionCounts` — Uses-per-round range of specific actions
|
||
- `pHP`, `pMP`, `pSP` — Player resource percentages
|
||
- `pOC` — Overcharge percentage
|
||
- `pSpiritStatus` — Spirit Stance active?
|
||
- `pEffects` — Player has effects with turns in range
|
||
- `pIgnoredEffects` — Player does NOT have these effects
|
||
- `pEffectStacks` — Player effect stack counts
|
||
- `monsters`, `activeMonsters`, `defeatedMonsters` — Monster counts
|
||
- `bosses`, `activeBosses`, `defeatedBosses` — Boss counts
|
||
- `mLevel` — Monster level range
|
||
- `mWithoutEffects` — Count of monsters without specific effects
|
||
|
||
**Target Conditions** (`conditionsTarget`, lines 980-999): Checked per-monster with offset/matched mechanics:
|
||
- `tName` — Regex-matchable monster name
|
||
- `tTypes` — Monster type (Normal, Rare, Legendary, etc.)
|
||
- `tClasses` — Monster class from MonsterDB (Arthropod, Avion, etc.)
|
||
- `tPowerLevel` — Monster power level range
|
||
- `tIndex` — Monster position (0-based, negative from end)
|
||
- `tHP`, `tMP`, `tSP` — Monster resource percentages
|
||
- `tEffects` — Monster has specific effects
|
||
- `tIgnoredEffects` — Monster does NOT have specific effects
|
||
- `tEffectStacks` — Monster effect stacks
|
||
- `tDaysSinceUpdate` — Days since MonsterDB last updated entry
|
||
|
||
Each target condition carries `offset` (range relative to target's index) and `matched` (count of monsters in the offset range that must satisfy the condition — defaults to `[1,1]` = exactly 1).
|
||
|
||
#### Condition Evaluation (`checkConditions`, line 2579)
|
||
|
||
```javascript
|
||
function checkConditions(conditions, target, checkGlobal, checkTarget)
|
||
```
|
||
- Iterates through all conditions, short-circuiting on first failure (AND logic)
|
||
- Global conditions: checked directly against global state objects
|
||
- Target conditions: evaluated with offset-based multi-monster checking
|
||
- `successCount` increments for each active monster in `[target.index + offset[0], target.index + offset[1]]` that satisfies the condition
|
||
- Final check: `jpxUtils.inRange(successCount, matched)`
|
||
|
||
The conditions system is lazily initialized via `initConditions()` (line 2445) which creates handler maps. Results are cached in `conditionsObj` per evaluation cycle (cleared in `preRender()`).
|
||
|
||
### 2.4 Action Execution (`actionManager`, line 2734)
|
||
|
||
The action manager processes action sequences with a sophisticated targeting phase:
|
||
|
||
```
|
||
actionManager(actions):
|
||
targetPhase = false
|
||
targetMonster = null
|
||
targetLocked = false
|
||
|
||
for each action in actions:
|
||
if action.disabled → skip
|
||
|
||
if action.type == 'target':
|
||
targetPhase = true
|
||
if not targetLocked:
|
||
result = handler(action, null) // evaluates target conditions globally, returns matching monster or null
|
||
if result found:
|
||
targetMonster = result
|
||
targetLocked = true // Lock in the first matching target
|
||
|
||
else if targetPhase and no targetMonster → skip (no target to act on)
|
||
|
||
else if handler(action, targetMonster) succeeds → return true (action taken)
|
||
targetLocked = false // Unlock for next target rule
|
||
|
||
return false // No action was possible
|
||
```
|
||
|
||
Key behaviors:
|
||
- **Target rules cascade**: Once a target rule finds a monster, `targetLocked` prevents re-evaluation until an action is successfully executed
|
||
- **Smart Debuff skips target phase**: `smartDebuff` actions are checked globally (conditions must pass without target), then internally handle their own targeting
|
||
- **Non-target actions after target rules require a locked target**, but "stop"/"item"/"spellSupport"/"toggle" can execute without a target if they appear before any target rule
|
||
|
||
### 2.5 Smart Debuff System (`doSpellsDebuffGoNext`, line 1810)
|
||
|
||
This is the most advanced targeting system in the script. It handles multi-target AoE debuff placement:
|
||
|
||
**Parameters:**
|
||
- `name` — Spell name
|
||
- `targetCount` — 1, 2, or 3 monsters to hit
|
||
- `bottomUp` — Iterate from monster J→A (true) or A→J (false)
|
||
- `tailSkip` — Skip last N monsters (positive) or only consider first N (negative)
|
||
- `maxAtFirst` — Maximum casts when ≤1 monster defeated
|
||
- `minMonstersLeft` — Threshold for infinite casting when many monsters remain
|
||
|
||
**Algorithm:**
|
||
1. Check cooldown and action count limits
|
||
2. Calculate `startIndex`/`endIndex` range from `activeMonsters` bounds
|
||
3. `tailSkip`: if positive, trim from end; if negative, limit to first N from start
|
||
4. First pass (targetCount ≥ 3): Find 3 consecutive alive + undebuffed monsters → cast on middle
|
||
5. Second pass (targetCount ≥ 2): Find 2 consecutive alive + undebuffed → cast on edge-appropriate monster
|
||
6. Third pass (targetCount ≥ 3, relaxed): Find 3 consecutive where middle is alive → cast on middle
|
||
7. Fourth pass (targetCount = 1): Any single undebuffed monster → cast
|
||
|
||
**`isUnDebuffed` memoization** (line 1843): Results cached in `undebuffedObj` to avoid re-evaluating conditions for the same index.
|
||
|
||
### 2.6 Priority Rules for Target Selection
|
||
|
||
Six rules (line 658):
|
||
- **Top Down** — Default, monster A first
|
||
- **Bottom Up** — Monster J first
|
||
- **Current HP Low to High** — Sort by absolute HP
|
||
- **Current HP High to Low** — Sort by absolute HP descending
|
||
- **Current HP Percent Low to High** — Sort by HP percentage
|
||
- **Current HP Percent High to Low** — Sort by HP percentage descending
|
||
|
||
Results are cached in `monstersObj.sorted` per priority rule per cycle.
|
||
|
||
---
|
||
|
||
## 3. DOM PARSING
|
||
|
||
### 3.1 Monster State (`getMonsters`, line 2191)
|
||
|
||
Parses all `.btm1` elements to build monster objects:
|
||
|
||
```javascript
|
||
monster = {
|
||
index, // 0-9
|
||
click(), // clicks the monster DOM element
|
||
level, // from .btm2 div text or HVClasses
|
||
name, // from .btm3 div text or HVClasses
|
||
type, // matched against bossTypes map (Normal/Rare/Legendary/etc.)
|
||
hpPercentage, // from .btm4 green bar width / 120
|
||
mpPercentage, // from .btm4 blue bar width / 120
|
||
spPercentage, // from .btm4 red bar width / 120
|
||
isAlive, // .btm1 has onclick attribute
|
||
effectObj: { name: { turns, stack } }, // from .btm6 img tooltips
|
||
monster_btm1 // reference to DOM element
|
||
}
|
||
```
|
||
|
||
Returns: `{ monsters, activeMonsters, bosses, activeBosses }`
|
||
|
||
### 3.2 Player Vitals (`getVitals`, line 2091)
|
||
|
||
Two code paths:
|
||
- **With OC bar** (Arena/Tower): `widthHP=414, widthMP/SP/OC=414`, reads `#dvrhb/#dvrhd`, `#dvrm`, `#dvrs`
|
||
- **Without OC bar** (Encounter/Grindfest): `widthHP=496, widthMP/SP=207`, reads `#vrhb/#vrhd`, `#vrm`, `#vrs`
|
||
|
||
OC calculation:
|
||
- With bar: `10 * parseInt(ocBar.style.width) / 414`
|
||
- Without bar: Count `<div>` elements in `#vcp` HTML (each `div` = 0.5 OC after the first), handle half-segment via `vcr` class
|
||
|
||
Returns: `{ oc, hpPercentage, mpPercentage, spPercentage, hpCurrent, mpCurrent, spCurrent, hpMax, mpMax, spMax }`
|
||
|
||
### 3.3 Action Cooldowns (`getActionCooldowns`, line 1950)
|
||
|
||
Dual scanner:
|
||
- **Spell cooldowns** (`.bts > div[onmouseover]`): Parse `onmouseover` tooltip with `regExp.spellInfo` → extract name and cooldown. If element has `onclick`, cooldown = 0 (ready). Otherwise: `lastUse + initCooldown - currentTurn`
|
||
- **Item cooldowns** (`.bti3 > div`): Parse with `regExp.itemInfo` → map item ID to name via `itemMap`. Fixed 40-turn cooldown.
|
||
|
||
Cooldown is `'-'` if still on cooldown, `0` if ready.
|
||
|
||
### 3.4 Player Effects (`getEffectDuration`, line 2145)
|
||
|
||
Parses `#pane_effects` images and `.btm6 > img` elements:
|
||
- Extract from `onmouseover` tooltip using `regExp.spellMatch` with named groups: `name`, `stack`, `description`, `turns`
|
||
- Builds `playerEffectsObj[name] = { turns, stack }`
|
||
- When `render=true`: creates duration overlay divs with color coding (<4 turns aquamarine, <9 lavender, auto/permanent special display)
|
||
|
||
### 3.5 Monster Effects Tracking (`updateMonsterEffects`, line 2291)
|
||
|
||
Handles the game's limitation of only showing 5 status effects per monster:
|
||
- **5 effects visible**: Reset saved state completely
|
||
- **6 effects visible**: The 6th is hidden behind a scroll — the script:
|
||
1. Calculates hidden turn deltas by comparing saved vs visible effect turns
|
||
2. Parses battle log for effect gains/expirations (`regExp.effectGain`, `regExp.effectExpired`, `regExp.effectWear`, etc.)
|
||
3. Applies turn decrement to hidden effects
|
||
4. Special handling for elemental effects (Searing Skin, Freezing Limbs, etc.) and Coalesced Mana
|
||
5. Prunes effects that would have expired
|
||
6. Renders hidden effect icons into the `.btm6` DOM
|
||
|
||
### 3.6 Monster Info (MonsterDB Integration, line 2404)
|
||
|
||
Polls `window.HVMonsterDB.getCurrentMonstersInformation()` with 250ms timeout for:
|
||
- `monsterClass` — e.g., "Arthropod", "Daimon"
|
||
- `attack` — attack type
|
||
- `plvl` — power level
|
||
- `lastUpdate` — timestamp for `tDaysSinceUpdate` condition
|
||
|
||
Stored in `allMonsterInfo` keyed by `mkey_{0-9}`.
|
||
|
||
### 3.7 Spell/Item Casting (`cast` / `use`, lines 1928-1948)
|
||
|
||
**cast(name):** Searches `.bts > div[onclick][onmouseover*="'name'"]` for spell buttons. If current selected spell differs, fires `onmouseover` (to select) then `onclick` (to cast) via a dummy div.
|
||
|
||
**use(name):** Looks up item ID from `itemMap`, finds matching div in `.bti3 > div[onclick][onmouseover]` containing that ID, fires `onmouseover` + `onclick`.
|
||
|
||
### 3.8 Spirit Status (`getSpiritStatus`, line 2139)
|
||
|
||
Checks if `#ckey_spirit` outerHTML contains `spirit_a.png` (active) or not (inactive).
|
||
|
||
---
|
||
|
||
## 4. FIGHTING STYLES
|
||
|
||
### 4.1 Auto-Detection (`initDoBattle`, line 1364)
|
||
|
||
Detection is based on which skills appear in the spell quickbar:
|
||
|
||
| Available Skill | Style (low spell damage) | Style (high spell damage) | Threshold |
|
||
|----------------|--------------------------|---------------------------|-----------|
|
||
| Shield Bash | `OneHanded` | `1H_Mage` | 70 |
|
||
| Great Cleave | `TwoHanded` | `2H_Mage` | 100 |
|
||
| Iris Strike | `DualWielding` | `DW_Mage` | 100 |
|
||
| Skyward Sword | `NitenIchiryu` | `NI_Mage` | 100 |
|
||
| Concussive Strike | `Staff` | `Staff` | N/A |
|
||
| None of above | `Unarmed` | `Unarmed` | N/A |
|
||
|
||
The threshold compares `spellDamageBonus.maxValue` (from `localStorage['jpx_spellDamageBonus']`) — the player's maximum spell damage bonus value. Above threshold = mage variant.
|
||
|
||
### 4.2 Battle Mode Resolution (`getBattleMode`, line 4173)
|
||
|
||
```javascript
|
||
getBattleMode(defaultBattleStyle = 'Unarmed'):
|
||
modeKey = `${battleStyle}_${battleType}` // e.g., "OneHanded_Arena"
|
||
|
||
if cfgBattle[modeKey] has non-empty supports, attacks, or key-bindings:
|
||
return modeKey // Specific mode exists
|
||
|
||
return `${battleStyle}_General` // Fall back to general mode
|
||
```
|
||
|
||
This means: check for a battle-type-specific config first, fall back to general.
|
||
|
||
### 4.3 Battle Type Detection (`initDoBattle`, lines 1410-1428)
|
||
|
||
Parses `#textlog` innerHTML with `regExp.battleTypeLog`:
|
||
- `arena challenge` + NOT `Round 1 / 1` → `Arena`
|
||
- `random encounter` → `Encounter`
|
||
- `arena challenge` + `Round 1 / 1` → `Colosseum`
|
||
- `Grindfest` → `Battle1000`
|
||
- `Item World` → `Item`
|
||
- `The Tower` → `Tower` (also extracts floor number)
|
||
|
||
### 4.4 Style-Specific Configurations (Default)
|
||
|
||
The bundled defaults show the script author's own configurations:
|
||
|
||
- **OneHanded_General**: Full support chain (Spirit→Health→Mana gems/potions/elixirs, Mystic Gem, Heartseeker, Regen, Draughts), attacks use smartDebuff (Weaken for Arena400/500), target rules by priority, Scan first round, skills (OFC, Merciful Blow, Vital Strike), ending with normalAttack
|
||
- **OneHanded_Tower**: Complex multi-stage smart debuff chain (Sleep→Weaken→Silence→Imperil) triggered by floor/round thresholds
|
||
- **Staff_General**: Mage-focused supports (Arcane Focus instead of Heartseeker), smartDebuff chain (Weaken→Silence→Imperil), target priority for Coalesced Mana monsters, T3→T2→T1 spell priority
|
||
- Other styles (2H, DW, Niten, Unarmed, mage variants): Empty default configs (lines 126-173, 221-228) — users configure them
|
||
|
||
---
|
||
|
||
## 5. BATTLE MODES
|
||
|
||
### 5.1 Enumeration (LINE 656-657)
|
||
|
||
```javascript
|
||
BATTLE_TYPES = ['Arena', 'Encounter', 'Colosseum', 'Battle1000', 'Item', 'Tower']
|
||
BATTLE_STYLES = ['OneHanded', '1H_Mage', 'TwoHanded', '2H_Mage', 'DualWielding', 'DW_Mage', 'NitenIchiryu', 'NI_Mage', 'Staff', 'Unarmed']
|
||
BATTLE_MODES = Cartesian product BATTLE_STYLES × ['General', ...BATTLE_TYPES]
|
||
```
|
||
|
||
### 5.2 Mode-Specific Behavior
|
||
|
||
| Battle Type | Special Behavior |
|
||
|-------------|-----------------|
|
||
| **Arena** | Sets difficulty to PFUDOR if ≥90 rounds. Uses roundInfo from localStorage for persistence |
|
||
| **Encounter** | Round info not tracked. Stamina cost = 0 |
|
||
| **Colosseum** | Single round. Stamina cost = 0 |
|
||
| **Battle1000** (Grindfest) | Stamina cost includes +1 entry fee |
|
||
| **Item** (Item World) | Reads worldLevel from localStorage (populated in lobby from IW equipment screen). Stamina cost formula with great/normal rates |
|
||
| **Tower** | Auto-derives difficulty from floor number (1-6 Normal, 7-13 Hard, ..., 34-39 IWBTH, 40+ PFUDOR). Caches floor in localStorage |
|
||
|
||
### 5.3 Difficulty Map (line 574)
|
||
|
||
```javascript
|
||
{ Normal: 1, Hard: 2, Nightmare: 4, Hell: 7, Nintendo: 10, IWBTH: 15, PFUDOR: 20 }
|
||
```
|
||
|
||
### 5.4 Stamina Cost Calculation (`getStaminaCost`, line 3172)
|
||
|
||
- Great cost: 0.03 (Persistent) or 0.06 (Isekai) per round
|
||
- Normal cost: 0.02 (Persistent) or 0.04 (Isekai) per round
|
||
- Great rounds capped by `floor(max(0, stamina - 60) / greatCost)` (60 stamina reserved for normal-cost rounds)
|
||
- Battle1000 adds +1 entry fee
|
||
- Daily quota tracking via `staminaRecords` in localStorage per date
|
||
|
||
---
|
||
|
||
## 6. KEYBINDINGS & UI
|
||
|
||
### 6.1 Hotkey System
|
||
|
||
**Global Keybinds** (line 912):
|
||
```javascript
|
||
KEYBINDS = {
|
||
openBattleRecords: { key: 'z', ctrl: false },
|
||
toggleActive: { key: 'm', ctrl: false },
|
||
openSettings: { key: ',', ctrl: false }
|
||
}
|
||
```
|
||
|
||
User-customizable, stored in `localStorage['jpx_userKeybinds' + isekaiSuffix]`.
|
||
|
||
**Battle Action Keybindings**: Stored as `kb_Ctrl+Shift+A` style keys in each battle mode config. Processed in `onKeyDown()`:
|
||
1. Check for global keybind match (non-repeat only)
|
||
2. If inactive and `#textlog` exists and monsters are alive, check for mode-specific keybinding
|
||
3. Repeat keys use throttled `actionManager` (75ms), initial press uses direct `actionManager`
|
||
|
||
**Key Capture** (`jpxUtils.captureKeyCombo`, line 6060): Uses `AbortController` for clean cancellation. Ignores modifier-only keypresses. Fires on first non-modifier keydown or pointerdown.
|
||
|
||
### 6.2 Control Widget (`ctrlWidget`)
|
||
|
||
Created by `jpxPanelManager.createCtrlWidget('battle')`:
|
||
- Positioned absolutely at top-right of battle screen (responsive via media query for landscape)
|
||
- Shows configurable rows (`ctrlWidgetRows`): Active status, Ready state, Network delay, Battle style, Battle type, Battle mode, Round
|
||
- Background color signals state: green (`#4f4`) = active, pink (`#fef`) = inactive, yellow (`#ff5`) = warning
|
||
- Click toggles auto-battle
|
||
- Sub-buttons: "Open Stats" → opens battle records in new window; "Open Settings" → opens settings panel
|
||
- Optional `mouseEnter` trigger for auto-battle activation
|
||
- Dispatches `jpx_ctrlWidget_update` CustomEvent for inter-script communication
|
||
|
||
### 6.3 Settings Panel (press `,`)
|
||
|
||
`renderSettings()` creates a full in-page settings panel with:
|
||
- **Two tabs**: Battle Settings, Stats Settings
|
||
- **Battle tab**: Keybind remapping UI, full schema-driven config editor for all battle modes, export/import/reset current mode
|
||
- **Stats tab**: Dark mode toggle, combat/revenue row picker, stats column picker, IndexedDB export/import
|
||
- **Schema-driven rendering**: `renderSchema()` with `fieldRenderers` registry supporting: heading, constant, boolean, text, number, rangeNumber, dropdown, array (with drag-and-drop reordering), fieldPicker (dual-list), object, keyBasedObjectArray (for keybindings), conditionsArray
|
||
|
||
### 6.4 Toast Notifications
|
||
|
||
`jpxUtils.createToast(content, duration)` — fixed-position toast at bottom-right with fade animation.
|
||
|
||
---
|
||
|
||
## 7. STORAGE
|
||
|
||
### 7.1 localStorage (prefix: `jpx_`, suffixed with `_isekai` for Isekai)
|
||
|
||
| Key | Data | Purpose |
|
||
|-----|------|---------|
|
||
| `cfgBattle` | Full battle config | User rules, passed through mergeCfg |
|
||
| `cfgStats` | Stats display config | Combat/revenue display preferences |
|
||
| `userKeybinds` | Custom keybindings | Overrides for global hotkeys |
|
||
| `spellDamageBonus` | `{maxType, maxValue}` | Spell element optimization |
|
||
| `worldLevel` | Number | IW world level |
|
||
| `difficulty` | String | Current difficulty |
|
||
| `playerLevel` | Number | Player level |
|
||
| `persona` | String | Active persona |
|
||
| `stamina` | Number | Current stamina |
|
||
| `battleType` | String | Last battle type |
|
||
| `towerFloor` | Number | Current tower floor |
|
||
| `roundInfo` | `{current, total}` | Round tracking across page loads |
|
||
| `monsterData` | Array of `{id, name, level, maxHP}` | Monster data for HP calculation |
|
||
| `battleLogRecord` | Array of strings | Raw battle log (if enabled) |
|
||
| `timeRecords` | `{action, turn, riddle, lastUse}` | Per-battle timing |
|
||
| `combatRecords` | Nested damage/result stats | Per-battle combat data |
|
||
| `revenueRecords` | Nested drop/currency stats | Per-battle revenue tracking |
|
||
| `staminaRecords` | `{lastUpdate, staminaCost}` | Daily stamina quota tracking |
|
||
| `priceData` | Market prices | Cached once per day |
|
||
|
||
### 7.2 IndexedDB
|
||
|
||
**Database**: `jpx`, version 1
|
||
**Object Store**: `battleRecords` with keyPath `timestamp`, index on `date`
|
||
|
||
Used for persistent battle history. Records contain: world, timestamp, date, playerLevel, difficulty, persona, battleType, worldLevel, towerFloor, roundInfo, result, deltaSeconds, deltaTime, turns, tps, riddle, combatRecords, revenueRecords.
|
||
|
||
**Operations**:
|
||
- `openDB()` — Open/create database
|
||
- `storeBattleRecords()` — Save after battle completion
|
||
- `getBattleRecordsRender()` — Query with filters (aggregate by day, world, battleType, difficulty, result, round range)
|
||
- `exportIndexedDB()` / `importIndexedDB()` — Full DB export/import with optional merge
|
||
|
||
### 7.3 State Management
|
||
|
||
- `storeTmp()` (beforeunload): Persists monsterData, battleLogRecord, timeRecords, combatRecords, revenueRecords — but only if battle is not complete (no finish button)
|
||
- On battle completion: `localStorage.removeItem()` clears all temp records
|
||
- Version-aware config migration via `mergeCfg()` comparing `battleVersion`/`statsVersion`
|
||
|
||
---
|
||
|
||
## 8. COMPATIBILITY
|
||
|
||
### 8.1 Monsterbation (Monster DB Script) Integration
|
||
|
||
The script integrates with `window.HVMonsterDB` (Monsterbation's monster database):
|
||
- `monsterDBReady()` (line 2404): Polls `window.HVMonsterDB.getCurrentMonstersInformation()` with 250ms timeout
|
||
- Used for: `monsterClass`, `attack` type, `plvl` (power level), `lastUpdate` date
|
||
- `tClasses`, `tPowerLevel`, `tDaysSinceUpdate` conditions depend on this data
|
||
- `showMonsterInfo` option displays monster class, attack type, and power level on each monster
|
||
- Falls back gracefully (`'?'` display) when MonsterDB is unavailable
|
||
|
||
### 8.2 Inter-Script Communication
|
||
|
||
`jpx_ctrlWidget_update` CustomEvent is dispatched by `jpxPanelManager.dispatchState()` (line 5614) with:
|
||
```javascript
|
||
{ active: isActiveBattle, background: color, suffix: isekaiSuffix, timestamp }
|
||
```
|
||
This is the designated hook for other userscripts to react to jpx state changes.
|
||
|
||
### 8.3 AJAX Round Compatibility
|
||
|
||
The `ajaxRound` feature has a config flag with explicit warning: "Disable it if other scripts don't support it." When enabled, it replaces standard page navigation with XHR fetches, which may break other scripts that rely on full page loads.
|
||
|
||
### 8.4 DOM Content Loaded Simulation
|
||
|
||
On AJAX round advancement, the script fires `document.dispatchEvent(new Event('DOMContentLoaded'))` (line 1680) to retrigger other scripts' initialization. It also re-creates `window.battle = new window.Battle()` and calls `clearInterval(window.timer)`.
|
||
|
||
---
|
||
|
||
## 9. KEY FUNCTIONS
|
||
|
||
### Initialization & Lifecycle
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `initDo()` | 1242 | Router: detects page context and dispatches to battle/lobby/riddle flows |
|
||
| `initDoBattle()` | 1364 | Battle initialization: load configs, detect style/type, parse initial state |
|
||
| `reDoBattle()` | 1457 | Re-initialization on round transition / DOMContentLoaded |
|
||
| `preDoBattle()` | 1476 | Parse round info, proficiency, monster data; setup MutationObserver |
|
||
| `preProcessLog()` | 1547 | MutationObserver callback: processes new log entries |
|
||
| `preRender()` | 1580 | Prepares all state objects (cooldowns, vitals, monsters, effects) for decision |
|
||
| `goNext()` | 1614 | Main loop: checks if active, handles end-of-round, triggers smartBattle |
|
||
| `storeTmp()` | 5469 | beforeunload handler: persists temp state |
|
||
|
||
### Battle Logic
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `smartBattle()` | 1792 | Evaluates supports then attacks; returns whether action was taken |
|
||
| `actionManager()` | 2734 | Processes action sequences with target locking |
|
||
| `checkConditions()` | 2579 | Universal condition evaluator |
|
||
| `initConditions()` | 2445 | Builds general/target handler maps |
|
||
| `doSpellsDebuffGoNext()` | 1810 | Smart debuff spatial targeting algorithm |
|
||
| `doSpellGoNext()` | 1897 | Cast spell on specific monster |
|
||
| `doAttackGoNext()` | 1907 | Normal attack on specific monster |
|
||
| `doToggleGoNext()` | 1916 | Toggle Spirit/Defend/Focus |
|
||
| `cast()` | 1928 | Cast named spell via DOM |
|
||
| `use()` | 1937 | Use named item via DOM |
|
||
|
||
### DOM Parsing
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `getActionCooldowns()` | 1950 | Parse spell/item cooldowns from quickbar DOM; render quickbar extensions |
|
||
| `getVitals()` | 2091 | Parse HP/MP/SP/OC from bar elements |
|
||
| `getSpiritStatus()` | 2139 | Check if Spirit Stance is active |
|
||
| `getEffectDuration()` | 2145 | Parse player/monster effect durations from tooltips; render overlays |
|
||
| `getMonsters()` | 2191 | Build monster state objects from `.btm1` elements |
|
||
| `updateMonsterEffects()` | 2291 | Track hidden (6th+) monster status effects |
|
||
| `updateMonsterInfo()` | 2428 | Poll MonsterDB for monster class/power/attack data |
|
||
| `monsterDBReady()` | 2404 | Async poller for MonsterDB availability |
|
||
|
||
### Battle Recording
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `battleRecorder()` | 2767 | Orchestrates all recording subsystems |
|
||
| `battleLogRecorder()` | 2789 | Records raw battle log text |
|
||
| `timeRecorder()` | 2823 | Tracks actions, turns, last use timestamps |
|
||
| `riddleRecorder()` | 2830 | Counts RiddleMaster solves |
|
||
| `combatRecorder()` | 2839 | Comprehensive damage/result tracking with type classification |
|
||
| `revenueRecorder()` | 3012 | Tracks EXP, credits, drops by category and quality |
|
||
|
||
### Battle Results Display
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `battleRecordPlayer()` | 3127 | Orchestrates end-of-battle record display and DB storage |
|
||
| `battleLogPlayer()` | 3290 | Creates downloadable battle log blob |
|
||
| `timeRecordPlayer()` | 3301 | Creates time/riddle/spark summary div |
|
||
| `combatRecordPlayer()` | 3313 | Creates combat stats table (damage, results, crit stacks, debuff resists) |
|
||
| `combatRecordPlayer_Use()` | 3438 | Creates action usage summary table |
|
||
| `revenueRecordPlayer()` | 3478 | Creates revenue table with drop/use/balance/profit |
|
||
| `newWindowRecordPlayer()` | 3605 | Renders mid-battle stats in popup window |
|
||
|
||
### IndexedDB & Stats
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `openDB()` | 3672 | Open/create IndexedDB |
|
||
| `storeBattleRecords()` | 3695 | Save battle record |
|
||
| `openBattleRecords()` | 3711 | Open stats window with filters |
|
||
| `getBattleRecordsRender()` | 3799 | Query/filter/aggregate battle records |
|
||
| `exportIndexedDB()` | 3851 | Full DB export |
|
||
| `importIndexedDB()` | 3873 | DB import with merge support |
|
||
| `filterData()` | 3908 | Apply filters to records |
|
||
| `generateAggregate()` | 3918 | Aggregate data by day (Total/Average) |
|
||
| `createFilter()` | 4028 | Build filter UI |
|
||
| `getFilters()` | 4080 | Read filter values from UI |
|
||
| `renderDynamicTable()` | 4107 | Render stats table with color thresholds |
|
||
|
||
### Settings & Schema
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `renderSettings()` | 5094 | Open/close settings panel |
|
||
| `switchTab()` | 5265 | Switch between battle/stats tabs |
|
||
| `renderBattleTab()` | 5288 | Battle settings tab with keybinds + full config editor |
|
||
| `renderStatsTab()` | 5381 | Stats settings tab with DB export/import |
|
||
| `renderSchema()` | 4189 | Schema-driven form renderer |
|
||
| `renderField()` | 4989 | Field dispatcher to type-specific renderers |
|
||
| `resolveSchema()` | 4994 | Resolve discriminator-based schemas |
|
||
| `createEmptyObject()` | 5000 | Generate default object from schema |
|
||
| `getUniqueId()` | 5030 | Generate unique DOM IDs |
|
||
| `mergeCfg()` | 5034 | Merge stored config with defaults, run patches/migrations |
|
||
|
||
### Configuration
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `getBattleMode()` | 4173 | Resolve active battle mode key (e.g., "OneHanded_Arena" → "OneHanded_General") |
|
||
|
||
### Keybinding
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `onKeyDown()` | 1746 | Global keydown handler |
|
||
| `toggleActive()` | 1782 | Toggle auto-battle on/off |
|
||
|
||
### I18N
|
||
| Function | Line | Purpose |
|
||
|----------|------|---------|
|
||
| `initDoI18n()` | 5491 | Merge built-in I18N with external `jpxI18N` override |
|
||
| `t()` | 5505 | Translation function with dot-path lookup and templating |
|
||
|
||
### Modules (singleton pattern)
|
||
| Module | Line | Purpose |
|
||
|--------|------|---------|
|
||
| `jpxPanelManager()` | 5526 | Control widget creation, content update, state dispatch |
|
||
| `jpxMarket()` | 5632 | Market price fetching, caching, default prices |
|
||
| `jpxUtils()` | 5797 | 25 utility functions: throttle, time formatting, sorting, type checks, DOM helpers, XHR, key capture, HV class parsing, record factories |
|
||
|
||
### Utility Functions (jpxUtils)
|
||
| Function | Purpose |
|
||
|----------|---------|
|
||
| `throttle(fn, ms, trailing)` | Rate-limit function calls |
|
||
| `secondsToTime(s, ms)` | Convert seconds to HH:MM:SS |
|
||
| `daysSince(dateStr)` | Days since UTC date |
|
||
| `getSortedArray(arr, fn, asc)` | Sort array by computed value |
|
||
| `lowerFirst(str)` | Lowercase first character |
|
||
| `titleCase(str)` | Title-case with camelCase splitting |
|
||
| `sentenceCase(str)` | Sentence-case |
|
||
| `matchAny(str, ...regexps)` | Test multiple regexes, return first match |
|
||
| `parseValue(val)` | Parse string to number if numeric |
|
||
| `isEmpty(obj)` | Check if object has no own properties |
|
||
| `inRange(value, [min, max])` | Range check |
|
||
| `getValueByPath(obj, keys)` | Dot-path object access |
|
||
| `deepMerge(target, source)` | Recursive object merge |
|
||
| `getSortedKeys(order, keys)` | Sort keys by predefined order |
|
||
| `inc(obj, key, step)` | Increment object property |
|
||
| `createButton(container, opts)` | Create styled button with toast feedback |
|
||
| `createToast(content, dur)` | Show toast notification |
|
||
| `stringifyLimited(obj, level)` | Pretty-print JSON with depth limit |
|
||
| `toRegExp(input)` | Parse regex string or create literal regex |
|
||
| `captureKeyCombo(onComplete, onAbort)` | Listen for single key combo |
|
||
| `formatKeyCombo(input, sep)` | Format key combo object/string for display |
|
||
| `parseHVClasses(container)` | Decode HentaiVerse obfuscated class names |
|
||
| `createTimeRecords()` | Factory for empty time records |
|
||
| `createCombatRecords()` | Factory for empty combat records |
|
||
| `createRevenueRecords()` | Factory for empty revenue records |
|
||
| `xhrGet(urlArray, interval)` | Staggered XHR requests with Promise.allSettled |
|
||
|
||
---
|
||
|
||
## Summary Statistics
|
||
|
||
- **Total lines**: 6,215
|
||
- **Named functions**: ~45
|
||
- **Utility functions**: 25
|
||
- **Regular expressions**: ~55 defined in `regExp` object
|
||
- **Supported fighting styles**: 10 (5 physical + 5 mage variants)
|
||
- **Supported battle types**: 6 + "General" fallback
|
||
- **Total battle mode combinations**: 60
|
||
- **Action types**: 10
|
||
- **General condition keys**: 25
|
||
- **Target condition keys**: 12
|
||
- **Priority rules**: 6
|
||
- **IndexedDB object store**: 1 (battleRecords)
|
||
- **localStorage keys**: ~20+
|
||
- **Singleton modules**: 3 (PanelManager, Market, Utils)
|
||
- **Default market prices**: ~80 items
|
||
- **Monster boss types**: 30+ across 6 tiers
|
||
- **I18N entries**: ~150+
|