v0.15.3 - Scorer now accounts for weapon damage, elemental damage, and procs

Parser fix: parseEquipHTML now captures the direct-child divs of the
tooltip that were previously skipped:
  - Proc lines: 'Penetrated Armor: 21.7% chance' -> stats['Proc Penetrated Armor']
  - Base damage: '+708 Piercing Damage' -> stats['Weapon Damage']
  - Elemental lines: '+25 Fire Damage' / '+30 Elec Strike' -> stats['Elemental Fire']

Scorer (gearScoreWeapon + analyze_gear.py):
  - Weapon Damage x 0.30 (base damage matters)
  - Elemental damage x 0.25 per element
  - Proc bonuses by type: CC procs (stun/freeze) x0.45, armor break
    x0.35, DoT x0.25, generic x0.15
  - All multiplied by the same quality multiplier afterward

So an elemental weapon with a strike proc now gets its due — the
short-margin stat winner may no longer win once damage+proc count.
Note: re-scrape the armory/store after updating so the new fields
get captured (old entries lack Weapon Damage/Proc keys).
This commit is contained in:
GaboGG 2026-08-03 16:00:59 -04:00
parent b069d42fc8
commit f993014a46
7 changed files with 182 additions and 6 deletions

View file

@ -67,6 +67,20 @@ def score_weapon(item):
s += get_num(stats.get('Block',0)) * 0.5
s += get_num(stats.get('Agility',0)) * 0.5
s -= get_num(stats.get('burden',0)) * 0.5
# Base weapon damage + elemental damage
s += get_num(stats.get('Weapon Damage',0)) * 0.30
for el in ['Fire','Cold','Elec','Wind','Holy','Dark','Ethereal','Elemental']:
s += get_num(stats.get('Elemental ' + el,0)) * 0.25
# Proc lines
for k, v in stats.items():
if k.startswith('Proc '):
pct = get_num(v)
pname = k.replace('Proc ','').lower()
if 'penetrated armor' in pname or 'armor' in pname: s += pct * 0.35
elif any(x in pname for x in ['stun','freeze','slow','paraly']): s += pct * 0.45
elif any(x in pname for x in ['bleeding','wound','poison','burn','shock']): s += pct * 0.25
elif 'weaken' in pname or 'impair' in pname: s += pct * 0.25
else: s += pct * 0.15
# Quality multiplier: higher tiers have better base damage
# q6=Magnificent, q5=Exquisite, q4=Superior, q3=Average, q2=Fair, q1=Crude

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.15.2
// @version 0.15.3
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*
@ -17,7 +17,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.15.2';
const VERSION = '0.15.3';
const CFG = {
// — Battle automation
@ -3512,6 +3512,41 @@ function parseEquipHTML(htmlStr) {
const ex = eq.querySelector('.ex');
if (ex) ex.querySelectorAll(':scope > div').forEach(parseRow);
// Direct-child rows of .eq — weapon damage + proc lines.
// e.g. <div><span>Penetrated Armor</span>: <span>21.7% chance</span></div>
// <div title="Base: 134">+<span>708 Piercing Damage</span></div>
Array.from(eq.children).forEach(child => {
if (child.classList && (child.classList.contains('eqt') || child.classList.contains('eqr')
|| child.classList.contains('eqc') || child.classList.contains('ex') || child.classList.contains('ep'))) return;
const text = (child.textContent || '').trim();
if (!text) return;
// Proc line: "Name: 21.7% chance"
const procM = text.match(/^([A-Za-z][A-Za-z ]+):\s*([\d.]+%?\s*(?:chance|procs?)?)/);
if (procM && child.querySelector('span')) {
stats['Proc ' + procM[1].trim()] = procM[2].trim();
return;
}
// Weapon damage line: "+708 Piercing Damage"
const dmgM = text.match(/^\+([\d,]+)\s*([A-Za-z]+)\s*Damage$/);
if (dmgM) {
stats['Weapon Damage'] = parseInt(dmgM[1].replace(/,/g, ''));
stats['Damage Type'] = dmgM[2];
const title = child.getAttribute('title') || '';
const baseM = title.match(/Base:\s*(\d+)/i);
if (baseM) stats['Weapon Damage Base'] = parseInt(baseM[1]);
return;
}
// Elemental damage lines: "+25 Fire Damage" or "+30 Elec Damage"
const elemM = text.match(/^\+([\d,]+)\s*(Fire|Cold|Elec|Wind|Holy|Dark|Ethereal|Elemental)\s*(?:Damage|Strike)$/);
if (elemM) {
stats['Elemental ' + elemM[2]] = parseInt(elemM[1].replace(/,/g, ''));
return;
}
});
// Extra stat groups (.ep)
eq.querySelectorAll('.ep').forEach(g => {
g.querySelectorAll(':scope > div:not(:first-child)').forEach(parseRow);
@ -3829,6 +3864,25 @@ function gearScoreWeapon(item) {
s += _num(st['Block']) * 0.5;
s += _num(st['Agility']) * 0.5;
s -= _num(st['burden']) * 0.5;
// Base weapon damage (e.g. "+708 Piercing Damage") — scales with quality
s += _num(st['Weapon Damage']) * 0.30;
// Elemental damage lines (e.g. "+25 Fire Damage", "+30 Elec Strike")
['Fire', 'Cold', 'Elec', 'Wind', 'Holy', 'Dark', 'Ethereal', 'Elemental'].forEach(el => {
s += _num(st['Elemental ' + el]) * 0.25;
});
// Proc lines: elemental/status procs add value (e.g. "Proc Penetrated Armor: 21.7%")
let procBonus = 0;
Object.keys(st).forEach(k => {
if (!k.startsWith('Proc ')) return;
const pct = _num(st[k]);
const pname = k.replace('Proc ', '').toLowerCase();
if (pname.includes('penetrated armor') || pname.includes('armor')) procBonus += pct * 0.35;
else if (pname.includes('stun') || pname.includes('freeze') || pname.includes('slow') || pname.includes('paraly')) procBonus += pct * 0.45;
else if (pname.includes('bleeding') || pname.includes('wound') || pname.includes('poison') || pname.includes('burn') || pname.includes('shock')) procBonus += pct * 0.25;
else if (pname.includes('weaken') || pname.includes('impair')) procBonus += pct * 0.25;
else procBonus += pct * 0.15; // generic proc value
});
s += procBonus;
// Quality multiplier (q6=Magnificent, q5=Exquisite, q4=Superior)
s *= 0.8 + (quality * 0.12);
// Weapon type bonus

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.15.2
// @version 0.15.3
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*
@ -17,7 +17,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.15.2';
const VERSION = '0.15.3';
const CFG = {
// — Battle automation
@ -3512,6 +3512,41 @@ function parseEquipHTML(htmlStr) {
const ex = eq.querySelector('.ex');
if (ex) ex.querySelectorAll(':scope > div').forEach(parseRow);
// Direct-child rows of .eq — weapon damage + proc lines.
// e.g. <div><span>Penetrated Armor</span>: <span>21.7% chance</span></div>
// <div title="Base: 134">+<span>708 Piercing Damage</span></div>
Array.from(eq.children).forEach(child => {
if (child.classList && (child.classList.contains('eqt') || child.classList.contains('eqr')
|| child.classList.contains('eqc') || child.classList.contains('ex') || child.classList.contains('ep'))) return;
const text = (child.textContent || '').trim();
if (!text) return;
// Proc line: "Name: 21.7% chance"
const procM = text.match(/^([A-Za-z][A-Za-z ]+):\s*([\d.]+%?\s*(?:chance|procs?)?)/);
if (procM && child.querySelector('span')) {
stats['Proc ' + procM[1].trim()] = procM[2].trim();
return;
}
// Weapon damage line: "+708 Piercing Damage"
const dmgM = text.match(/^\+([\d,]+)\s*([A-Za-z]+)\s*Damage$/);
if (dmgM) {
stats['Weapon Damage'] = parseInt(dmgM[1].replace(/,/g, ''));
stats['Damage Type'] = dmgM[2];
const title = child.getAttribute('title') || '';
const baseM = title.match(/Base:\s*(\d+)/i);
if (baseM) stats['Weapon Damage Base'] = parseInt(baseM[1]);
return;
}
// Elemental damage lines: "+25 Fire Damage" or "+30 Elec Damage"
const elemM = text.match(/^\+([\d,]+)\s*(Fire|Cold|Elec|Wind|Holy|Dark|Ethereal|Elemental)\s*(?:Damage|Strike)$/);
if (elemM) {
stats['Elemental ' + elemM[2]] = parseInt(elemM[1].replace(/,/g, ''));
return;
}
});
// Extra stat groups (.ep)
eq.querySelectorAll('.ep').forEach(g => {
g.querySelectorAll(':scope > div:not(:first-child)').forEach(parseRow);
@ -3829,6 +3864,25 @@ function gearScoreWeapon(item) {
s += _num(st['Block']) * 0.5;
s += _num(st['Agility']) * 0.5;
s -= _num(st['burden']) * 0.5;
// Base weapon damage (e.g. "+708 Piercing Damage") — scales with quality
s += _num(st['Weapon Damage']) * 0.30;
// Elemental damage lines (e.g. "+25 Fire Damage", "+30 Elec Strike")
['Fire', 'Cold', 'Elec', 'Wind', 'Holy', 'Dark', 'Ethereal', 'Elemental'].forEach(el => {
s += _num(st['Elemental ' + el]) * 0.25;
});
// Proc lines: elemental/status procs add value (e.g. "Proc Penetrated Armor: 21.7%")
let procBonus = 0;
Object.keys(st).forEach(k => {
if (!k.startsWith('Proc ')) return;
const pct = _num(st[k]);
const pname = k.replace('Proc ', '').toLowerCase();
if (pname.includes('penetrated armor') || pname.includes('armor')) procBonus += pct * 0.35;
else if (pname.includes('stun') || pname.includes('freeze') || pname.includes('slow') || pname.includes('paraly')) procBonus += pct * 0.45;
else if (pname.includes('bleeding') || pname.includes('wound') || pname.includes('poison') || pname.includes('burn') || pname.includes('shock')) procBonus += pct * 0.25;
else if (pname.includes('weaken') || pname.includes('impair')) procBonus += pct * 0.25;
else procBonus += pct * 0.15; // generic proc value
});
s += procBonus;
// Quality multiplier (q6=Magnificent, q5=Exquisite, q4=Superior)
s *= 0.8 + (quality * 0.12);
// Weapon type bonus

View file

@ -2,7 +2,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.15.2';
const VERSION = '0.15.3';
const CFG = {
// — Battle automation

View file

@ -45,6 +45,25 @@ function gearScoreWeapon(item) {
s += _num(st['Block']) * 0.5;
s += _num(st['Agility']) * 0.5;
s -= _num(st['burden']) * 0.5;
// Base weapon damage (e.g. "+708 Piercing Damage") — scales with quality
s += _num(st['Weapon Damage']) * 0.30;
// Elemental damage lines (e.g. "+25 Fire Damage", "+30 Elec Strike")
['Fire', 'Cold', 'Elec', 'Wind', 'Holy', 'Dark', 'Ethereal', 'Elemental'].forEach(el => {
s += _num(st['Elemental ' + el]) * 0.25;
});
// Proc lines: elemental/status procs add value (e.g. "Proc Penetrated Armor: 21.7%")
let procBonus = 0;
Object.keys(st).forEach(k => {
if (!k.startsWith('Proc ')) return;
const pct = _num(st[k]);
const pname = k.replace('Proc ', '').toLowerCase();
if (pname.includes('penetrated armor') || pname.includes('armor')) procBonus += pct * 0.35;
else if (pname.includes('stun') || pname.includes('freeze') || pname.includes('slow') || pname.includes('paraly')) procBonus += pct * 0.45;
else if (pname.includes('bleeding') || pname.includes('wound') || pname.includes('poison') || pname.includes('burn') || pname.includes('shock')) procBonus += pct * 0.25;
else if (pname.includes('weaken') || pname.includes('impair')) procBonus += pct * 0.25;
else procBonus += pct * 0.15; // generic proc value
});
s += procBonus;
// Quality multiplier (q6=Magnificent, q5=Exquisite, q4=Superior)
s *= 0.8 + (quality * 0.12);
// Weapon type bonus

View file

@ -117,6 +117,41 @@ function parseEquipHTML(htmlStr) {
const ex = eq.querySelector('.ex');
if (ex) ex.querySelectorAll(':scope > div').forEach(parseRow);
// Direct-child rows of .eq — weapon damage + proc lines.
// e.g. <div><span>Penetrated Armor</span>: <span>21.7% chance</span></div>
// <div title="Base: 134">+<span>708 Piercing Damage</span></div>
Array.from(eq.children).forEach(child => {
if (child.classList && (child.classList.contains('eqt') || child.classList.contains('eqr')
|| child.classList.contains('eqc') || child.classList.contains('ex') || child.classList.contains('ep'))) return;
const text = (child.textContent || '').trim();
if (!text) return;
// Proc line: "Name: 21.7% chance"
const procM = text.match(/^([A-Za-z][A-Za-z ]+):\s*([\d.]+%?\s*(?:chance|procs?)?)/);
if (procM && child.querySelector('span')) {
stats['Proc ' + procM[1].trim()] = procM[2].trim();
return;
}
// Weapon damage line: "+708 Piercing Damage"
const dmgM = text.match(/^\+([\d,]+)\s*([A-Za-z]+)\s*Damage$/);
if (dmgM) {
stats['Weapon Damage'] = parseInt(dmgM[1].replace(/,/g, ''));
stats['Damage Type'] = dmgM[2];
const title = child.getAttribute('title') || '';
const baseM = title.match(/Base:\s*(\d+)/i);
if (baseM) stats['Weapon Damage Base'] = parseInt(baseM[1]);
return;
}
// Elemental damage lines: "+25 Fire Damage" or "+30 Elec Damage"
const elemM = text.match(/^\+([\d,]+)\s*(Fire|Cold|Elec|Wind|Holy|Dark|Ethereal|Elemental)\s*(?:Damage|Strike)$/);
if (elemM) {
stats['Elemental ' + elemM[2]] = parseInt(elemM[1].replace(/,/g, ''));
return;
}
});
// Extra stat groups (.ep)
eq.querySelectorAll('.ep').forEach(g => {
g.querySelectorAll(':scope > div:not(:first-child)').forEach(parseRow);

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.15.2
// @version 0.15.3
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*