feat(i18n): 补全暗黑2全量词缀汉化、修复语序倒装、充能技能负数与属性词条解析 (Issue #122)
This commit is contained in:
parent
cef219155a
commit
ca55cd29d8
|
|
@ -0,0 +1,183 @@
|
||||||
|
import { spawn } from 'child_process'
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runBrowserAudit() {
|
||||||
|
console.log('1. Starting Google Chrome headless...')
|
||||||
|
const chromeProc = spawn('/usr/bin/google-chrome', [
|
||||||
|
'--headless=new',
|
||||||
|
'--remote-debugging-port=9222',
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-gpu',
|
||||||
|
'--disable-extensions',
|
||||||
|
'http://localhost:8080/boss.html',
|
||||||
|
])
|
||||||
|
|
||||||
|
// Wait for Chrome to be ready
|
||||||
|
let wsUrl = null
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
await sleep(300)
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://127.0.0.1:9222/json/list')
|
||||||
|
const pages = await res.json()
|
||||||
|
const page = pages.find(p => p.type === 'page' && p.url.includes('boss.html'))
|
||||||
|
if (page && page.webSocketDebuggerUrl) {
|
||||||
|
wsUrl = page.webSocketDebuggerUrl
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// keep trying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wsUrl) {
|
||||||
|
chromeProc.kill()
|
||||||
|
throw new Error('Failed to connect to Chrome headless CDP within 10s')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('2. Connected to Chrome CDP on page:', wsUrl)
|
||||||
|
|
||||||
|
const ws = new WebSocket(wsUrl)
|
||||||
|
await new Promise(resolve => ws.onopen = resolve)
|
||||||
|
|
||||||
|
let idCounter = 1
|
||||||
|
function sendCommand(method, params = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = idCounter++
|
||||||
|
const handler = event => {
|
||||||
|
const msg = JSON.parse(event.data)
|
||||||
|
if (msg.id === id) {
|
||||||
|
ws.removeEventListener('message', handler)
|
||||||
|
if (msg.error) reject(msg.error)
|
||||||
|
else resolve(msg.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws.addEventListener('message', handler)
|
||||||
|
ws.send(JSON.stringify({ id, method, params }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable Runtime
|
||||||
|
await sendCommand('Runtime.enable')
|
||||||
|
|
||||||
|
// Wait 1.5s for initial drop tables to load
|
||||||
|
await sleep(1500)
|
||||||
|
|
||||||
|
console.log('3. Triggering Chinese language switch and 10x Boss Kill in browser DOM...')
|
||||||
|
|
||||||
|
const evalResult = await sendCommand('Runtime.evaluate', {
|
||||||
|
expression: `(async () => {
|
||||||
|
// 1. Click Chinese language pill
|
||||||
|
const zhPill = document.querySelector('.lang-pill[data-lang="zh"]');
|
||||||
|
if (zhPill) zhPill.click();
|
||||||
|
|
||||||
|
// 2. Click 10x kill button
|
||||||
|
const kill10Btn = document.getElementById('btn-kill-10');
|
||||||
|
if (kill10Btn) kill10Btn.click();
|
||||||
|
|
||||||
|
// Wait a short moment for DOM rendering
|
||||||
|
await new Promise(r => setTimeout(r, 800));
|
||||||
|
|
||||||
|
// 3. Extract items
|
||||||
|
const cards = Array.from(document.querySelectorAll('#loot-items-grid .d2-item-card'));
|
||||||
|
return cards.map(c => {
|
||||||
|
const title = c.querySelector('.item-title')?.textContent?.trim() || '';
|
||||||
|
const subTitle = c.querySelector('.item-subtitle')?.textContent?.trim() || '';
|
||||||
|
const quality = c.querySelector('.item-quality-pill')?.textContent?.trim() || '';
|
||||||
|
const ilvl = c.querySelector('.item-ilvl-pill')?.textContent?.trim() || '';
|
||||||
|
const stats = Array.from(c.querySelectorAll('.item-base-stats .base-stat-item')).map(s => s.textContent.trim());
|
||||||
|
const affixes = Array.from(c.querySelectorAll('.item-affix-lines .tooltip-stat-line')).map(a => a.textContent.trim());
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
subTitle,
|
||||||
|
quality,
|
||||||
|
ilvl,
|
||||||
|
stats,
|
||||||
|
affixes
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})()`,
|
||||||
|
awaitPromise: true,
|
||||||
|
returnByValue: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('4. Evaluation completed. Closing Chrome...')
|
||||||
|
ws.close()
|
||||||
|
chromeProc.kill()
|
||||||
|
|
||||||
|
const items = evalResult.result.value
|
||||||
|
console.log(`\nSuccessfully captured ${items.length} items from http://localhost:8080/boss.html!\n`)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
runBrowserAudit()
|
||||||
|
.then(items => {
|
||||||
|
console.log('================================================================');
|
||||||
|
console.log(`=== AUDITING ALL ${items.length} ITEMS GENERATED FROM 10x BOSS KILL ===`);
|
||||||
|
console.log('================================================================\n');
|
||||||
|
|
||||||
|
let errorsFound = 0;
|
||||||
|
const traditionalChars = ['屬', '無', '紅', '綠', '變', '爛', '誕', '誠', '雲', '犧', '嚇', '層', '靜', '換', '亂', '節', '甦'];
|
||||||
|
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
console.log(`[#${index + 1}] [${item.quality}] ${item.title} (底材: ${item.subTitle || '无'}, ${item.ilvl})`);
|
||||||
|
if (item.stats.length > 0) {
|
||||||
|
console.log(` 基础属性: ${item.stats.join(' | ')}`);
|
||||||
|
}
|
||||||
|
if (item.affixes.length > 0) {
|
||||||
|
console.log(` 词条属性:`);
|
||||||
|
item.affixes.forEach(aff => console.log(` * ${aff}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 1: Negative numbers in charged spells
|
||||||
|
item.affixes.forEach(aff => {
|
||||||
|
if (aff.includes('次充能') && (aff.includes('等级 -') || aff.includes('(-'))) {
|
||||||
|
console.error(` ❌ ERROR: Negative value in charged spell: "${aff}"`);
|
||||||
|
errorsFound++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check 2: Missing English affix translations (e.g. English letters followed by 之 or preceded by 之)
|
||||||
|
const englishAffixMatch = item.title.match(/[a-zA-Z]+之|之[a-zA-Z]+/);
|
||||||
|
if (englishAffixMatch) {
|
||||||
|
console.error(` ❌ ERROR: Untranslated English affix in title: "${item.title}"`);
|
||||||
|
errorsFound++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 3: Word order anomaly: base name followed by '之' at the end of title
|
||||||
|
if (item.subTitle && item.title.endsWith(item.subTitle + '之')) {
|
||||||
|
console.error(` ❌ ERROR: Word order inversion (base followed by suffix particle): "${item.title}"`);
|
||||||
|
errorsFound++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 4: Traditional Chinese characters
|
||||||
|
const allText = item.title + ' ' + item.subTitle + ' ' + item.affixes.join(' ');
|
||||||
|
traditionalChars.forEach(ch => {
|
||||||
|
if (allText.includes(ch)) {
|
||||||
|
console.error(` ❌ ERROR: Traditional Chinese character '${ch}' found in: "${allText}"`);
|
||||||
|
errorsFound++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('================================================================');
|
||||||
|
if (errorsFound === 0) {
|
||||||
|
console.log(`🎉 AUDIT PASSED! All ${items.length} items checked with ZERO errors!`);
|
||||||
|
console.log(`✅ All names in correct Chinese grammar word order`);
|
||||||
|
console.log(`✅ 100% Affix translations matched without fallback artifacts`);
|
||||||
|
console.log(`✅ Charged spell positive charges and positive skill levels verified`);
|
||||||
|
console.log(`✅ Normalized Simplified Chinese characters verified`);
|
||||||
|
} else {
|
||||||
|
console.error(`⚠️ AUDIT FOUND ${errorsFound} ERRORS!`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('================================================================\n');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Audit execution failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,607 @@
|
||||||
|
import { getEmbeddedDropTables } from '../src/game/embedded-drop-tables.ts'
|
||||||
|
import { SKILL_NAMES_ZH } from '../src/game/tooltip-i18n.ts'
|
||||||
|
|
||||||
|
const dt = getEmbeddedDropTables()
|
||||||
|
const prefixes = Array.from(new Set(dt.magicAffixes.prefixes.all.map(p => p.name))).sort()
|
||||||
|
const suffixes = Array.from(new Set(dt.magicAffixes.suffixes.all.map(s => s.name))).sort()
|
||||||
|
|
||||||
|
// Complete 252 prefixes
|
||||||
|
const prefixMap: Record<string, string> = {
|
||||||
|
"Accursed": "诅咒之",
|
||||||
|
"Acrobat's": "杂技之",
|
||||||
|
"Amber": "琥珀之",
|
||||||
|
"Ambergris": "龙涎香之",
|
||||||
|
"Amethyst": "紫水晶之",
|
||||||
|
"Angel's": "天使之",
|
||||||
|
"Antimagic": "防魔之",
|
||||||
|
"Arcadian": "阿卡迪亚之",
|
||||||
|
"Arch-Angel's": "大天使之",
|
||||||
|
"Archer's": "弓箭手之",
|
||||||
|
"Arcing": "电弧之",
|
||||||
|
"Argent": "白银之",
|
||||||
|
"Artificer's": "工匠之",
|
||||||
|
"Assamic": "阿萨姆之",
|
||||||
|
"Astral": "星界之",
|
||||||
|
"Athlete": "运动之",
|
||||||
|
"Athlete's": "运动之",
|
||||||
|
"Aureolin": "钴黄之",
|
||||||
|
"Azure": "天青之",
|
||||||
|
"Bahamut's": "巴哈姆特之",
|
||||||
|
"Beryl": "绿柱石之",
|
||||||
|
"Berserker's": "狂战士之",
|
||||||
|
"Bishop's": "主教之",
|
||||||
|
"Blanched": "漂白之",
|
||||||
|
"Blank": "虚无之",
|
||||||
|
"Blazing": "炽热之",
|
||||||
|
"Blessed": "祝福之",
|
||||||
|
"Blighting": "枯萎之",
|
||||||
|
"Bloody": "血腥之",
|
||||||
|
"Bone": "白骨之",
|
||||||
|
"Boreal": "极北之",
|
||||||
|
"Bowyer's": "弓箭手之",
|
||||||
|
"Bright": "光明之",
|
||||||
|
"Bronze": "青铜之",
|
||||||
|
"Brown": "棕褐之",
|
||||||
|
"Brutal": "残忍之",
|
||||||
|
"Burgundy": "红棕之",
|
||||||
|
"Burly": "魁梧之",
|
||||||
|
"Burning": "燃烧之",
|
||||||
|
"Buzzing": "嗡鸣之",
|
||||||
|
"Calling": "呼唤之",
|
||||||
|
"Camphor": "樟脑之",
|
||||||
|
"Captain's": "领袖之",
|
||||||
|
"Carbuncle": "痈肿之",
|
||||||
|
"Caretaker's": "看顾者之",
|
||||||
|
"Carmine": "洋红之",
|
||||||
|
"Celestial": "天界之",
|
||||||
|
"Charged": "充能之",
|
||||||
|
"Chestnut": "栗色之",
|
||||||
|
"Chilling": "严寒之",
|
||||||
|
"Chromatic": "炫彩之",
|
||||||
|
"Cinnabar": "朱砂之",
|
||||||
|
"Cobalt": "钴蓝之",
|
||||||
|
"Commander's": "指挥官之",
|
||||||
|
"Communal": "公社之",
|
||||||
|
"Compact": "紧密之",
|
||||||
|
"Consecrated": "奉献之",
|
||||||
|
"Coral": "珊瑚之",
|
||||||
|
"Corosive": "腐蚀之",
|
||||||
|
"Crimson": "枣红之",
|
||||||
|
"Cruel": "残忍之",
|
||||||
|
"Cunning": "狡黠之",
|
||||||
|
"Deadly": "致命之",
|
||||||
|
"Dense": "致密之",
|
||||||
|
"Devious": "迂回之",
|
||||||
|
"Diamond": "钻石之",
|
||||||
|
"Divine": "神圣之",
|
||||||
|
"Dragon's": "龙之",
|
||||||
|
"Drake's": "德尔克之",
|
||||||
|
"Dun": "黑褐之",
|
||||||
|
"Eagleeye": "雕眼之",
|
||||||
|
"Earth": "大地之",
|
||||||
|
"Eburin": "象牙之",
|
||||||
|
"Echoing": "回音之",
|
||||||
|
"Elysian": "极乐之",
|
||||||
|
"Ember": "余烬之",
|
||||||
|
"Emerald": "绿宝石之",
|
||||||
|
"Entrapping": "陷阱之",
|
||||||
|
"Envenomed": "腐烂之",
|
||||||
|
"Eocene": "始新世之",
|
||||||
|
"Expert's": "专家之",
|
||||||
|
"Falconeye": "猎鹰之眼",
|
||||||
|
"Fanatic": "狂热之",
|
||||||
|
"Felicitous": "吉庆之",
|
||||||
|
"Feral": "野性之",
|
||||||
|
"Ferocious": "凶猛之",
|
||||||
|
"Fine": "精良之",
|
||||||
|
"Flaming": "烈焰之",
|
||||||
|
"Fletcher's": "制箭者之",
|
||||||
|
"Fool's": "愚人之",
|
||||||
|
"Forked": "分叉之",
|
||||||
|
"Fortified": "防御强化之",
|
||||||
|
"Fortuitous": "偶然之",
|
||||||
|
"Freezing": "冰冻之",
|
||||||
|
"Fungal": "真菌之",
|
||||||
|
"Furious": "狂怒之",
|
||||||
|
"Gaea's": "大地之",
|
||||||
|
"Gaia": "大地之",
|
||||||
|
"Garnet": "石榴石之",
|
||||||
|
"Glacial": "极冰之",
|
||||||
|
"Glimmering": "微光之",
|
||||||
|
"Glorious": "光荣之",
|
||||||
|
"Glowing": "发光之",
|
||||||
|
"Godly": "神圣之",
|
||||||
|
"Gold": "金之",
|
||||||
|
"Golemlord": "魔像领主之",
|
||||||
|
"Golemlord's": "魔像领主之",
|
||||||
|
"Grandmaster's": "宗师之",
|
||||||
|
"Graverobber's": "盗墓者之",
|
||||||
|
"Great Wyrm's": "大巨龙之",
|
||||||
|
"Gritty": "坚韧之",
|
||||||
|
"Guardian's": "护卫者之",
|
||||||
|
"Gymnastic": "体操之",
|
||||||
|
"Gymnast's": "体操之",
|
||||||
|
"Hallowed": "神圣之",
|
||||||
|
"Harpoonist's": "标枪之",
|
||||||
|
"Hawk Branded": "鹰之烙印之",
|
||||||
|
"Hawkeye": "鹰眼之",
|
||||||
|
"Hexing": "诅咒之",
|
||||||
|
"Hibernal": "极寒之",
|
||||||
|
"Hierophant's": "教皇之",
|
||||||
|
"Holy": "神圣之",
|
||||||
|
"Howling": "咆哮之",
|
||||||
|
"Iron": "铁之",
|
||||||
|
"Ivory": "象牙之",
|
||||||
|
"Jacinth": "风信子石之",
|
||||||
|
"Jack's": "随从之",
|
||||||
|
"Jade": "翡翠之",
|
||||||
|
"Jagged": "锯齿之",
|
||||||
|
"Jester's": "弄臣之",
|
||||||
|
"Jeweler's": "珠宝匠之",
|
||||||
|
"Joker's": "小丑之",
|
||||||
|
"Kenshi's": "剑士之",
|
||||||
|
"Keeper's": "守护者之",
|
||||||
|
"King's": "国王之",
|
||||||
|
"Knave's": "盗贼之",
|
||||||
|
"Knight's": "骑士之",
|
||||||
|
"Lancer's": "长矛手之",
|
||||||
|
"Lapis": "青金石之",
|
||||||
|
"Lapis Lazuli": "青金石之",
|
||||||
|
"Lion Branded": "狮子烙印之",
|
||||||
|
"Lizard's": "蜥蜴之",
|
||||||
|
"Lord's": "领主之",
|
||||||
|
"Loud": "响亮之",
|
||||||
|
"Lucky": "幸运之",
|
||||||
|
"Magekiller's": "法师杀手之",
|
||||||
|
"Maiden's": "处女之",
|
||||||
|
"Maroon": "褐红之",
|
||||||
|
"Marshal": "元帅之",
|
||||||
|
"Marshal's": "元帅之",
|
||||||
|
"Massive": "稳重之",
|
||||||
|
"Master's": "大师之",
|
||||||
|
"Mechanist's": "机械师之",
|
||||||
|
"Mentalist": "通灵之",
|
||||||
|
"Mentalist's": "通灵之",
|
||||||
|
"Merciless": "绝情之",
|
||||||
|
"Meteoric": "陨铁之",
|
||||||
|
"Miocene": "中新世之",
|
||||||
|
"Mnemonic": "记忆之",
|
||||||
|
"Monk's": "僧人之",
|
||||||
|
"Nature": "自然之",
|
||||||
|
"Nature's": "自然之",
|
||||||
|
"Necromancer": "死灵法师之",
|
||||||
|
"Necromancer's": "死灵法师之",
|
||||||
|
"Nickel": "白镍之",
|
||||||
|
"Noxious": "有害之",
|
||||||
|
"Null": "无效之",
|
||||||
|
"Ocher": "赭黄之",
|
||||||
|
"Oligocene": "渐新世之",
|
||||||
|
"Paleocene": "忠诚信使之",
|
||||||
|
"Paradox": "悖论之",
|
||||||
|
"Patriarch's": "先知之",
|
||||||
|
"Pearl": "珍珠之",
|
||||||
|
"Pestilent": "瘟疫之",
|
||||||
|
"Platinum": "白金之",
|
||||||
|
"Powered": "能量之",
|
||||||
|
"Preserver": "保护者之",
|
||||||
|
"Preserver's": "保护者之",
|
||||||
|
"Priest's": "祭司之",
|
||||||
|
"Prismatic": "彩虹之",
|
||||||
|
"Psychic": "心灵之",
|
||||||
|
"Pure": "纯净之",
|
||||||
|
"Raging": "暴怒之",
|
||||||
|
"Rainbow": "彩虹之",
|
||||||
|
"Realgar": "雄黄之",
|
||||||
|
"Red": "红之",
|
||||||
|
"Resonant": "共鸣之",
|
||||||
|
"Robineye": "知更鸟眼之",
|
||||||
|
"Rose Branded": "玫瑰烙印之",
|
||||||
|
"Ruby": "红宝石之",
|
||||||
|
"Rugged": "凹凸之",
|
||||||
|
"Russet": "铁锈之",
|
||||||
|
"Rusty": "生锈之",
|
||||||
|
"Sacred": "圣洁之",
|
||||||
|
"Saintly": "崇高之",
|
||||||
|
"Sanguinary": "鲜血之",
|
||||||
|
"Sapphire": "蓝宝石之",
|
||||||
|
"Savage": "野蛮之",
|
||||||
|
"Scarlet": "猩红之",
|
||||||
|
"Scintillating": "闪烁之",
|
||||||
|
"Scorching": "灼烧之",
|
||||||
|
"Screaming": "尖叫之",
|
||||||
|
"Sensei's": "老师之",
|
||||||
|
"Septic": "腐臭之",
|
||||||
|
"Serpent's": "海蛇之",
|
||||||
|
"Serrated": "锯齿之",
|
||||||
|
"Shadow": "阴影之",
|
||||||
|
"Shaman's": "萨满之",
|
||||||
|
"Sharp": "尖锐之",
|
||||||
|
"Shimmering": "微光之",
|
||||||
|
"Shivering": "寒颤之",
|
||||||
|
"Shocking": "雷霆之",
|
||||||
|
"Shogukusha's": "刺客之",
|
||||||
|
"Shouting": "咆哮之",
|
||||||
|
"Silver": "银之",
|
||||||
|
"Slayer's": "屠杀者之",
|
||||||
|
"Smoldering": "焖燃之",
|
||||||
|
"Smoking": "烟雾之",
|
||||||
|
"Snake's": "蛇之",
|
||||||
|
"Snowflake": "雪花之",
|
||||||
|
"Soldier's": "士兵之",
|
||||||
|
"Sounding": "鸣响之",
|
||||||
|
"Sparking": "火花之",
|
||||||
|
"Sparroweye": "麻雀眼之",
|
||||||
|
"Spearmaiden's": "标枪女郎之",
|
||||||
|
"Spiritual": "精神之",
|
||||||
|
"Stalwart": "健壮之",
|
||||||
|
"Static": "静电之",
|
||||||
|
"Steel": "钢之",
|
||||||
|
"Stout": "坚固之",
|
||||||
|
"Strange": "怪异之",
|
||||||
|
"Strong": "强壮之",
|
||||||
|
"Sturdy": "结实之",
|
||||||
|
"Summoner's": "召唤者之",
|
||||||
|
"Tangerine": "橙黄之",
|
||||||
|
"Terra's": "大地之",
|
||||||
|
"Thin": "轻薄之",
|
||||||
|
"Tin": "白铁之",
|
||||||
|
"Tireless": "不倦之",
|
||||||
|
"Topaz": "黄宝石之",
|
||||||
|
"Toxic": "剧毒之",
|
||||||
|
"Trainer's": "驯兽师之",
|
||||||
|
"Trickster's": "戏法师之",
|
||||||
|
"Triumphant": "得胜之",
|
||||||
|
"Trump": "胜者之",
|
||||||
|
"Turquoise": "绿松石之",
|
||||||
|
"Unearthly": "超凡之",
|
||||||
|
"Valkyrie's": "女武神之",
|
||||||
|
"Venomous": "剧毒之",
|
||||||
|
"Vermillion": "朱砂之",
|
||||||
|
"Veteran's": "老兵之",
|
||||||
|
"Vicious": "恶毒之",
|
||||||
|
"Victorious": "胜利之",
|
||||||
|
"Vigorous": "强健之",
|
||||||
|
"Viridian": "铭绿之",
|
||||||
|
"Visionary": "梦幻之",
|
||||||
|
"Vodoun": "伏都之",
|
||||||
|
"Volcanic": "火山之",
|
||||||
|
"Vulpine": "奸诈之",
|
||||||
|
"Wailing": "哀嚎之",
|
||||||
|
"Warden": "看守者之",
|
||||||
|
"Warder's": "监视者之",
|
||||||
|
"Warrior's": "战士之",
|
||||||
|
"Weird": "怪异之",
|
||||||
|
"Witch-hunter's": "猎巫者之",
|
||||||
|
"Wyrm's": "维特之",
|
||||||
|
"Yelling": "呐喊之",
|
||||||
|
"Zircon": "锆石之"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete 288 suffixes
|
||||||
|
const suffixBaseMap: Record<string, string> = {
|
||||||
|
"Health": "健康之",
|
||||||
|
"Acceleration": "加速之",
|
||||||
|
"Anima": "生灵之",
|
||||||
|
"Balance": "平衡之",
|
||||||
|
"Equilibrium": "均势之",
|
||||||
|
"Stability": "安定之",
|
||||||
|
"Chance": "几率之",
|
||||||
|
"Greed": "贪欲之",
|
||||||
|
"Wealth": "财富之",
|
||||||
|
"Light": "光明之",
|
||||||
|
"Radiance": "光辉之",
|
||||||
|
"the Sun": "太阳之",
|
||||||
|
"Nirvana": "涅槃之",
|
||||||
|
"Vileness": "卑劣之",
|
||||||
|
"the Colosuss": "巨神之",
|
||||||
|
"the Jackal": "胡狼之",
|
||||||
|
"Protection": "守护之",
|
||||||
|
"Absorption": "吸收之",
|
||||||
|
"Life": "生命之",
|
||||||
|
"Warding": "防避之",
|
||||||
|
"the Sentinel": "步哨之",
|
||||||
|
"Guarding": "守卫之",
|
||||||
|
"Negation": "否定之",
|
||||||
|
"Piercing": "刺穿之",
|
||||||
|
"Bashing": "重击之",
|
||||||
|
"Puncturing": "爆破之",
|
||||||
|
"Thorns": "荆棘之",
|
||||||
|
"Spikes": "尖刺之",
|
||||||
|
"Readiness": "准备之",
|
||||||
|
"Alacrity": "迅捷之",
|
||||||
|
"Swiftness": "疾速之",
|
||||||
|
"Quickness": "快速之",
|
||||||
|
"Blocking": "格挡之",
|
||||||
|
"Deflecting": "偏向之",
|
||||||
|
"the Apprentice": "学徒之",
|
||||||
|
"the Magus": "法师之",
|
||||||
|
"Frost": "冰霜之",
|
||||||
|
"the Glacier": "冰川之",
|
||||||
|
"Flame": "火焰之",
|
||||||
|
"Fire": "烈火之",
|
||||||
|
"Burning": "燃烧之",
|
||||||
|
"Lightning": "闪电之",
|
||||||
|
"Thunder": "雷霆之",
|
||||||
|
"Shock": "电击之",
|
||||||
|
"Spark": "火花之",
|
||||||
|
"Static": "静电之",
|
||||||
|
"Craftsmanship": "工匠之",
|
||||||
|
"Quality": "品质之",
|
||||||
|
"Maiming": "残废之",
|
||||||
|
"Slaying": "杀戮之",
|
||||||
|
"Gore": "鲜血之",
|
||||||
|
"Carnage": "大屠杀之",
|
||||||
|
"Slaughter": "屠杀之",
|
||||||
|
"Butchery": "屠戮之",
|
||||||
|
"Evisceration": "剖腹之",
|
||||||
|
"Destruction": "毁灭之",
|
||||||
|
"Worth": "价值之",
|
||||||
|
"Measure": "度量之",
|
||||||
|
"Excellence": "卓越之",
|
||||||
|
"Performance": "精良之",
|
||||||
|
"Joyfulness": "欢欣之",
|
||||||
|
"Bliss": "至福之",
|
||||||
|
"Blight": "枯萎之",
|
||||||
|
"Venom": "毒素之",
|
||||||
|
"Pestilence": "瘟疫之",
|
||||||
|
"Anthrax": "炭疽之",
|
||||||
|
"Frostbite": "冻疮之",
|
||||||
|
"the Leech": "水蛭之",
|
||||||
|
"the Bat": "蝙蝠之",
|
||||||
|
"the Vampire": "吸血鬼之",
|
||||||
|
"the Lamprey": "八目鳗之",
|
||||||
|
"the Wraith": "幽灵之",
|
||||||
|
"the Ghoul": "食尸鬼之",
|
||||||
|
"the Locust": "蝗虫之",
|
||||||
|
"Daring": "果敢之",
|
||||||
|
"Truth": "真理之",
|
||||||
|
"Honor": "荣耀之",
|
||||||
|
"Avarice": "贪婪之",
|
||||||
|
"Luck": "幸运之",
|
||||||
|
"Good Luck": "好运之",
|
||||||
|
"Prosperity": "繁荣之",
|
||||||
|
"Fortune": "财富之",
|
||||||
|
"Perfection": "完美之",
|
||||||
|
"Enlightenment": "启迪之",
|
||||||
|
"Knowledge": "知识之",
|
||||||
|
"Substinence": "生计之",
|
||||||
|
"Vita": "活力之",
|
||||||
|
"Spirit": "精神之",
|
||||||
|
"Hope": "希望之",
|
||||||
|
"Freedom": "自由之",
|
||||||
|
"Atlus": "阿特拉斯之",
|
||||||
|
"Virility": "刚毅之",
|
||||||
|
"Traveling": "旅行之",
|
||||||
|
"Inertia": "抗性之",
|
||||||
|
"Self-Repair": "自我修复之",
|
||||||
|
"Fast Repair": "快速修复之",
|
||||||
|
"Ages": "万古之",
|
||||||
|
"Replenishing": "充盈之",
|
||||||
|
"Propogation": "繁衍之",
|
||||||
|
"the Kraken": "海妖之",
|
||||||
|
"Memory": "记忆之",
|
||||||
|
"the Elephant": "大象之",
|
||||||
|
"Power": "力量之",
|
||||||
|
"Grace": "优雅之",
|
||||||
|
"Grace and Power": "优雅与力量之",
|
||||||
|
"the Yeti": "雪人之",
|
||||||
|
"the Phoenix": "凤凰之",
|
||||||
|
"the Efreeti": "火灵之",
|
||||||
|
"the Cobra": "眼镜蛇之",
|
||||||
|
"the Elements": "元素之",
|
||||||
|
"the Icicle": "冰柱之",
|
||||||
|
"Winter": "严冬之",
|
||||||
|
"Frigidity": "寒冷之",
|
||||||
|
"Incineration": "焚化之",
|
||||||
|
"Passion": "激情之",
|
||||||
|
"Storms": "风暴之",
|
||||||
|
"Ennui": "厌倦之",
|
||||||
|
"Ire": "愤怒之",
|
||||||
|
"Wrath": "盛怒之",
|
||||||
|
"Transcendence": "卓越之",
|
||||||
|
"Envy": "嫉妒之",
|
||||||
|
"Fervor": "狂热之",
|
||||||
|
"Speed": "速度之",
|
||||||
|
"Haste": "急速之",
|
||||||
|
"Dexterity": "敏捷之",
|
||||||
|
"Skill": "技巧之",
|
||||||
|
"Accuracy": "准确之",
|
||||||
|
"Precision": "精确之",
|
||||||
|
"Sniping": "狙击之",
|
||||||
|
"the Bear": "巨熊之",
|
||||||
|
"Strength": "力量之",
|
||||||
|
"Might": "强力之",
|
||||||
|
"the Ox": "公牛之",
|
||||||
|
"the Giant": "巨人之",
|
||||||
|
"the Titan": "泰坦之",
|
||||||
|
"the Fox": "狐狸之",
|
||||||
|
"the Wolf": "苍狼之",
|
||||||
|
"the Tiger": "猛虎之",
|
||||||
|
"the Mammoth": "猛犸之",
|
||||||
|
"the Colossus": "巨神之",
|
||||||
|
"the Squid": "乌贼之",
|
||||||
|
"the Whale": "巨鲸之",
|
||||||
|
"Defiance": "蔑视之",
|
||||||
|
"Fumigation": "熏蒸之",
|
||||||
|
"Ease": "轻便之",
|
||||||
|
"Simplicity": "简朴之",
|
||||||
|
"Dawn": "黎明之",
|
||||||
|
"Sunlight": "阳光之",
|
||||||
|
"Energy": "能量之",
|
||||||
|
"Mind": "心灵之",
|
||||||
|
"Brilliance": "辉煌之",
|
||||||
|
"Sorcery": "巫术之",
|
||||||
|
"Wizardry": "法术之",
|
||||||
|
"Warmth": "温暖之",
|
||||||
|
"Remedy": "救治之",
|
||||||
|
"Amelioration": "改善之",
|
||||||
|
"Regeneration": "再生之",
|
||||||
|
"Regrowth": "新生之",
|
||||||
|
"Revivification": "复苏之",
|
||||||
|
"Spikers": "尖刺之",
|
||||||
|
"Passing": "消逝之",
|
||||||
|
"Stamina": "耐力之",
|
||||||
|
"Pacing": "步行之",
|
||||||
|
"Striding": "跨步之",
|
||||||
|
"Health Everlasting": "永恒健康之",
|
||||||
|
"Life Everlasting": "永恒生命之",
|
||||||
|
"Amianthus": "石棉之",
|
||||||
|
"Fire Quenching": "灭火之",
|
||||||
|
"Incombustibility": "不燃之",
|
||||||
|
"Coolness": "清凉之",
|
||||||
|
"Faith": "信仰之",
|
||||||
|
"Resistance": "抗性之",
|
||||||
|
"Insulation": "绝缘之",
|
||||||
|
"Grounding": "接地之",
|
||||||
|
"the Dynamo": "发电机之",
|
||||||
|
"Stoicism": "坚忍之",
|
||||||
|
"Warming": "保温之",
|
||||||
|
"Thawing": "解冻之",
|
||||||
|
"the Dunes": "沙丘之",
|
||||||
|
"the Sirocco": "热风之",
|
||||||
|
"Desire": "渴望之",
|
||||||
|
"Razors": "剃刀之",
|
||||||
|
"Swords": "剑刃之",
|
||||||
|
"Malice": "恶意之",
|
||||||
|
"the Mind": "心灵之",
|
||||||
|
"Telekinesis": "心灵传动之",
|
||||||
|
"Teleportation": "传送之",
|
||||||
|
"Teleport Shield": "传送护盾之",
|
||||||
|
"Inner Sight": "内视之",
|
||||||
|
"Slow Missiles": "慢速箭之",
|
||||||
|
"Damage Amplification": "伤害加深之",
|
||||||
|
"Amplify Damage": "伤害加深之",
|
||||||
|
"Weaken": "削弱之",
|
||||||
|
"Dim Vision": "微暗灵视之",
|
||||||
|
"Iron Maiden": "攻击反噬之",
|
||||||
|
"Terror": "恐惧之",
|
||||||
|
"Confusion": "迷乱之",
|
||||||
|
"Life Tap": "偷取生命之",
|
||||||
|
"Attraction": "吸引之",
|
||||||
|
"Decrepification": "衰老之",
|
||||||
|
"Lower Resistance": "降低抗性之",
|
||||||
|
"Taunting": "嘲弄之",
|
||||||
|
"Howling": "狂嚎之",
|
||||||
|
"Shouting": "大叫之",
|
||||||
|
"Battle Cry": "刺耳尖叫之",
|
||||||
|
"Battle Orders": "战斗体制之",
|
||||||
|
"Battle Command": "战斗指挥之",
|
||||||
|
"War Cry": "战鸣之",
|
||||||
|
"Stunning": "击晕之",
|
||||||
|
"Concentration": "专注之",
|
||||||
|
"Grim Ward": "残骸清理之",
|
||||||
|
"Item Finding": "寻找物品之",
|
||||||
|
"Potion Finding": "寻找药剂之",
|
||||||
|
"Holy Bolts": "圣光弹之",
|
||||||
|
"Blessed Hammers": "祝福之槌之",
|
||||||
|
"Conversion": "转化之",
|
||||||
|
"Sacrifice": "牺牲之",
|
||||||
|
"Zeal": "热诚之",
|
||||||
|
"Vengeance": "复仇之",
|
||||||
|
"Fist of the Heavens": "天堂之拳之",
|
||||||
|
"Static Field": "静电场之",
|
||||||
|
"Nova": "新星之",
|
||||||
|
"Novas": "新星之",
|
||||||
|
"Nova Shield": "新星护盾之",
|
||||||
|
"Frost Novas": "冰霜新星之",
|
||||||
|
"Frost Shield": "冰霜护盾之",
|
||||||
|
"Ice Blast": "冰风暴之",
|
||||||
|
"Ice Blasts": "冰风暴之",
|
||||||
|
"Icebolt": "冰弹之",
|
||||||
|
"Ice Bolts": "冰弹之",
|
||||||
|
"Glacial Spike": "冰尖柱之",
|
||||||
|
"Glacial Spikes": "冰尖柱之",
|
||||||
|
"Blizzard": "暴风雪之",
|
||||||
|
"Blizzards": "暴风雪之",
|
||||||
|
"Frozen Orb": "冰封球之",
|
||||||
|
"Frozen Orbs": "冰封球之",
|
||||||
|
"Frozen Armor": "冰封甲之",
|
||||||
|
"Shiver Armor": "碎冰甲之",
|
||||||
|
"Chilling Armor": "寒冰甲之",
|
||||||
|
"Charged Bolts": "充能弹之",
|
||||||
|
"Charged Shield": "充能护盾之",
|
||||||
|
"Charged Strike": "充能一击之",
|
||||||
|
"Lightning Strike": "雷电一击之",
|
||||||
|
"Lightning Javelin": "闪电标枪之",
|
||||||
|
"Lightning Fury": "闪电之怒之",
|
||||||
|
"Chain Lightning": "连锁闪电之",
|
||||||
|
"Thunder Storm": "雷暴之",
|
||||||
|
"Energy Shield": "能量护盾之",
|
||||||
|
"Hydras": "九头海蛇之",
|
||||||
|
"Hydra Shield": "九头蛇护盾之",
|
||||||
|
"Firebolt": "火弹之",
|
||||||
|
"Firebolts": "火弹之",
|
||||||
|
"Fire Bolts": "火弹之",
|
||||||
|
"Fire Ball": "火球之",
|
||||||
|
"Fire Balls": "火球之",
|
||||||
|
"Fire Wall": "火墙之",
|
||||||
|
"Fire Walls": "火墙之",
|
||||||
|
"Enchant": "强化之",
|
||||||
|
"Enchantment": "强化之",
|
||||||
|
"Meteor": "陨石之",
|
||||||
|
"Meteors": "陨石之",
|
||||||
|
"Blaze": "炽烈之径之",
|
||||||
|
"Blazing": "炽烈之径之",
|
||||||
|
"Firestorms": "火风暴之",
|
||||||
|
"Molten Boulders": "熔岩巨石之",
|
||||||
|
"Eruption": "火山喷发之",
|
||||||
|
"Cyclone Armor": "飓风装甲之",
|
||||||
|
"Twister": "龙卷风之",
|
||||||
|
"Volcano": "火山之",
|
||||||
|
"Tornado": "暴风之",
|
||||||
|
"Armageddon": "末日风暴之",
|
||||||
|
"Hurricane": "飓风之",
|
||||||
|
"Teeth": "牙之",
|
||||||
|
"Bone Armor": "白骨装甲之",
|
||||||
|
"Poison Dagger": "毒匕首之",
|
||||||
|
"Corpse Explosions": "尸体爆炸之",
|
||||||
|
"Bone Walls": "骨墙之",
|
||||||
|
"Poison Explosion": "毒爆之",
|
||||||
|
"Bone Spears": "骨矛之",
|
||||||
|
"Bone Imprisonment": "骨牢之",
|
||||||
|
"Poison Novas": "剧毒新星之",
|
||||||
|
"Bone Spirits": "白骨之魂之",
|
||||||
|
"Clay Golem Summoning": "黏土魔像之",
|
||||||
|
"Blood Golem Summoning": "鲜血魔像之",
|
||||||
|
"Iron Golem Creation": "钢铁魔像之",
|
||||||
|
"Fire Golem Summoning": "火焰魔像之",
|
||||||
|
"Raise Skeletons": "复生骷髅之",
|
||||||
|
"Raise Skeletal Mages": "骷髅法师之",
|
||||||
|
"Magic Arrows": "魔法箭之",
|
||||||
|
"Fire Arrows": "火箭之",
|
||||||
|
"Cold Arrows": "冰箭之",
|
||||||
|
"Multiple Shot": "多重箭之",
|
||||||
|
"Exploding Arrows": "爆裂箭之",
|
||||||
|
"Ice Arrows": "冻结箭之",
|
||||||
|
"Guided Arrows": "追踪箭之",
|
||||||
|
"Immolating Arrows": "牺牲之箭之",
|
||||||
|
"Freezing Arrows": "急冻箭之",
|
||||||
|
"Power Strike": "威力一击之",
|
||||||
|
"Poison Jab": "毒枪之",
|
||||||
|
"Jabbing": "戳刺之",
|
||||||
|
"Impaling Strike": "刺爆之",
|
||||||
|
"Fending": "击退之",
|
||||||
|
"Plague Jab": "瘟疫标枪之"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build full suffix map
|
||||||
|
const suffixMap: Record<string, string> = {}
|
||||||
|
for (const [k, v] of Object.entries(suffixBaseMap)) {
|
||||||
|
suffixMap['of ' + k] = v
|
||||||
|
suffixMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
const missingPre = prefixes.filter(p => !prefixMap[p])
|
||||||
|
const missingSuf = suffixes.filter(s => !suffixMap[s] && !suffixMap['of ' + s])
|
||||||
|
|
||||||
|
console.log('Validating coverage...')
|
||||||
|
console.log('Prefix coverage: ' + (prefixes.length - missingPre.length) + ' / ' + prefixes.length)
|
||||||
|
if (missingPre.length > 0) console.log('Missing prefixes:', missingPre)
|
||||||
|
|
||||||
|
console.log('Suffix coverage: ' + (suffixes.length - missingSuf.length) + ' / ' + suffixes.length)
|
||||||
|
if (missingSuf.length > 0) console.log('Missing suffixes:', missingSuf)
|
||||||
|
|
||||||
|
if (missingPre.length === 0 && missingSuf.length === 0) {
|
||||||
|
console.log('SUCCESS! 100% Affix Coverage achieved for all 252 prefixes and 288 suffixes.')
|
||||||
|
}
|
||||||
|
|
@ -144,305 +144,18 @@ const RUNE_INFO: Record<string, { num: number; nameEn: string; lvl: number; weap
|
||||||
* - formatPropertyCode(code, min, max, par)
|
* - formatPropertyCode(code, min, max, par)
|
||||||
* - formatPropertyCode(code, value)
|
* - formatPropertyCode(code, value)
|
||||||
*/
|
*/
|
||||||
const CANONICAL_SKILLS: readonly [number, string, string, string][] = [
|
import {
|
||||||
[0, "Attack", "Attack", ""],
|
CANONICAL_SKILLS,
|
||||||
[1, "Kick", "Kick", ""],
|
resolveSkillInfo,
|
||||||
[2, "Throw", "Throw", ""],
|
CANONICAL_SKILL_TABS,
|
||||||
[3, "Unsummon", "Unsummon", ""],
|
CANONICAL_REANIMATE_MONSTERS,
|
||||||
[4, "Left Hand Throw", "Left Hand Throw", ""],
|
} from './skills-data.ts'
|
||||||
[5, "Left Hand Swing", "Left Hand Swing", ""],
|
|
||||||
[6, "Magic Arrow", "Magic Arrow", "ama"],
|
|
||||||
[7, "Fire Arrow", "Fire Arrow", "ama"],
|
|
||||||
[8, "Inner Sight", "Inner Sight", "ama"],
|
|
||||||
[9, "Critical Strike", "Critical Strike", "ama"],
|
|
||||||
[10, "Jab", "Jab", "ama"],
|
|
||||||
[11, "Cold Arrow", "Cold Arrow", "ama"],
|
|
||||||
[12, "Multiple Shot", "Multiple Shot", "ama"],
|
|
||||||
[13, "Dodge", "Dodge", "ama"],
|
|
||||||
[14, "Power Strike", "Power Strike", "ama"],
|
|
||||||
[15, "Poison Javelin", "Poison Javelin", "ama"],
|
|
||||||
[16, "Exploding Arrow", "Exploding Arrow", "ama"],
|
|
||||||
[17, "Slow Missiles", "Slow Missiles", "ama"],
|
|
||||||
[18, "Avoid", "Avoid", "ama"],
|
|
||||||
[19, "Impale", "Impale", "ama"],
|
|
||||||
[20, "Lightning Bolt", "Lightning Bolt", "ama"],
|
|
||||||
[21, "Ice Arrow", "Ice Arrow", "ama"],
|
|
||||||
[22, "Guided Arrow", "Guided Arrow", "ama"],
|
|
||||||
[23, "Penetrate", "Penetrate", "ama"],
|
|
||||||
[24, "Charged Strike", "Charged Strike", "ama"],
|
|
||||||
[25, "Plague Javelin", "Plague Javelin", "ama"],
|
|
||||||
[26, "Strafe", "Strafe", "ama"],
|
|
||||||
[27, "Immolation Arrow", "Immolation Arrow", "ama"],
|
|
||||||
[28, "Dopplezon", "Decoy", "ama"],
|
|
||||||
[29, "Evade", "Evade", "ama"],
|
|
||||||
[30, "Fend", "Fend", "ama"],
|
|
||||||
[31, "Freezing Arrow", "Freezing Arrow", "ama"],
|
|
||||||
[32, "Valkyrie", "Valkyrie", "ama"],
|
|
||||||
[33, "Pierce", "Pierce", "ama"],
|
|
||||||
[34, "Lightning Strike", "Lightning Strike", "ama"],
|
|
||||||
[35, "Lightning Fury", "Lightning Fury", "ama"],
|
|
||||||
[36, "Fire Bolt", "Fire Bolt", "sor"],
|
|
||||||
[37, "Warmth", "Warmth", "sor"],
|
|
||||||
[38, "Charged Bolt", "Charged Bolt", "sor"],
|
|
||||||
[39, "Ice Bolt", "Ice Bolt", "sor"],
|
|
||||||
[40, "Frozen Armor", "Frozen Armor", "sor"],
|
|
||||||
[41, "Inferno", "Inferno", "sor"],
|
|
||||||
[42, "Static Field", "Static Field", "sor"],
|
|
||||||
[43, "Telekinesis", "Telekinesis", "sor"],
|
|
||||||
[44, "Frost Nova", "Frost Nova", "sor"],
|
|
||||||
[45, "Ice Blast", "Ice Blast", "sor"],
|
|
||||||
[46, "Blaze", "Blaze", "sor"],
|
|
||||||
[47, "Fire Ball", "Fire Ball", "sor"],
|
|
||||||
[48, "Nova", "Nova", "sor"],
|
|
||||||
[49, "Lightning", "Lightning", "sor"],
|
|
||||||
[50, "Shiver Armor", "Shiver Armor", "sor"],
|
|
||||||
[51, "Fire Wall", "Fire Wall", "sor"],
|
|
||||||
[52, "Enchant", "Enchant", "sor"],
|
|
||||||
[53, "Chain Lightning", "Chain Lightning", "sor"],
|
|
||||||
[54, "Teleport", "Teleport", "sor"],
|
|
||||||
[55, "Glacial Spike", "Glacial Spike", "sor"],
|
|
||||||
[56, "Meteor", "Meteor", "sor"],
|
|
||||||
[57, "Thunder Storm", "Thunder Storm", "sor"],
|
|
||||||
[58, "Energy Shield", "Energy Shield", "sor"],
|
|
||||||
[59, "Blizzard", "Blizzard", "sor"],
|
|
||||||
[60, "Chilling Armor", "Chilling Armor", "sor"],
|
|
||||||
[61, "Fire Mastery", "Fire Mastery", "sor"],
|
|
||||||
[62, "Hydra", "Hydra", "sor"],
|
|
||||||
[63, "Lightning Mastery", "Lightning Mastery", "sor"],
|
|
||||||
[64, "Frozen Orb", "Frozen Orb", "sor"],
|
|
||||||
[65, "Cold Mastery", "Cold Mastery", "sor"],
|
|
||||||
[66, "Amplify Damage", "Amplify Damage", "nec"],
|
|
||||||
[67, "Teeth", "Teeth", "nec"],
|
|
||||||
[68, "Bone Armor", "Bone Armor", "nec"],
|
|
||||||
[69, "Skeleton Mastery", "Skeleton Mastery", "nec"],
|
|
||||||
[70, "Raise Skeleton", "Raise Skeleton", "nec"],
|
|
||||||
[71, "Dim Vision", "Dim Vision", "nec"],
|
|
||||||
[72, "Weaken", "Weaken", "nec"],
|
|
||||||
[73, "Poison Dagger", "Poison Dagger", "nec"],
|
|
||||||
[74, "Corpse Explosion", "Corpse Explosion", "nec"],
|
|
||||||
[75, "Clay Golem", "Clay Golem", "nec"],
|
|
||||||
[76, "Iron Maiden", "Iron Maiden", "nec"],
|
|
||||||
[77, "Terror", "Terror", "nec"],
|
|
||||||
[78, "Bone Wall", "Bone Wall", "nec"],
|
|
||||||
[79, "Golem Mastery", "Golem Mastery", "nec"],
|
|
||||||
[80, "Raise Skeletal Mage", "Raise Skeletal Mage", "nec"],
|
|
||||||
[81, "Confuse", "Confuse", "nec"],
|
|
||||||
[82, "Life Tap", "Life Tap", "nec"],
|
|
||||||
[83, "Poison Explosion", "Poison Explosion", "nec"],
|
|
||||||
[84, "Bone Spear", "Bone Spear", "nec"],
|
|
||||||
[85, "BloodGolem", "Blood Golem", "nec"],
|
|
||||||
[86, "Attract", "Attract", "nec"],
|
|
||||||
[87, "Decrepify", "Decrepify", "nec"],
|
|
||||||
[88, "Bone Prison", "Bone Prison", "nec"],
|
|
||||||
[89, "Summon Resist", "Summon Resist", "nec"],
|
|
||||||
[90, "IronGolem", "Iron Golem", "nec"],
|
|
||||||
[91, "Lower Resist", "Lower Resist", "nec"],
|
|
||||||
[92, "Poison Nova", "Poison Nova", "nec"],
|
|
||||||
[93, "Bone Spirit", "Bone Spirit", "nec"],
|
|
||||||
[94, "FireGolem", "Fire Golem", "nec"],
|
|
||||||
[95, "Revive", "Revive", "nec"],
|
|
||||||
[96, "Sacrifice", "Sacrifice", "pal"],
|
|
||||||
[97, "Smite", "Smite", "pal"],
|
|
||||||
[98, "Might", "Might", "pal"],
|
|
||||||
[99, "Prayer", "Prayer", "pal"],
|
|
||||||
[100, "Resist Fire", "Resist Fire", "pal"],
|
|
||||||
[101, "Holy Bolt", "Holy Bolt", "pal"],
|
|
||||||
[102, "Holy Fire", "Holy Fire", "pal"],
|
|
||||||
[103, "Thorns", "Thorns", "pal"],
|
|
||||||
[104, "Defiance", "Defiance", "pal"],
|
|
||||||
[105, "Resist Cold", "Resist Cold", "pal"],
|
|
||||||
[106, "Zeal", "Zeal", "pal"],
|
|
||||||
[107, "Charge", "Charge", "pal"],
|
|
||||||
[108, "Blessed Aim", "Blessed Aim", "pal"],
|
|
||||||
[109, "Cleansing", "Cleansing", "pal"],
|
|
||||||
[110, "Resist Lightning", "Resist Lightning", "pal"],
|
|
||||||
[111, "Vengeance", "Vengeance", "pal"],
|
|
||||||
[112, "Blessed Hammer", "Blessed Hammer", "pal"],
|
|
||||||
[113, "Concentration", "Concentration", "pal"],
|
|
||||||
[114, "Holy Freeze", "Holy Freeze", "pal"],
|
|
||||||
[115, "Vigor", "Vigor", "pal"],
|
|
||||||
[116, "Conversion", "Conversion", "pal"],
|
|
||||||
[117, "Holy Shield", "Holy Shield", "pal"],
|
|
||||||
[118, "Holy Shock", "Holy Shock", "pal"],
|
|
||||||
[119, "Sanctuary", "Sanctuary", "pal"],
|
|
||||||
[120, "Meditation", "Meditation", "pal"],
|
|
||||||
[121, "Fist of the Heavens", "Fist of the Heavens", "pal"],
|
|
||||||
[122, "Fanaticism", "Fanaticism", "pal"],
|
|
||||||
[123, "Conviction", "Conviction", "pal"],
|
|
||||||
[124, "Redemption", "Redemption", "pal"],
|
|
||||||
[125, "Salvation", "Salvation", "pal"],
|
|
||||||
[126, "Bash", "Bash", "bar"],
|
|
||||||
[127, "Sword Mastery", "Sword Mastery", "bar"],
|
|
||||||
[128, "Axe Mastery", "Axe Mastery", "bar"],
|
|
||||||
[129, "Mace Mastery", "Mace Mastery", "bar"],
|
|
||||||
[130, "Howl", "Howl", "bar"],
|
|
||||||
[131, "Find Potion", "Find Potion", "bar"],
|
|
||||||
[132, "Leap", "Leap", "bar"],
|
|
||||||
[133, "Double Swing", "Double Swing", "bar"],
|
|
||||||
[134, "Pole Arm Mastery", "Pole Arm Mastery", "bar"],
|
|
||||||
[135, "Throwing Mastery", "Throwing Mastery", "bar"],
|
|
||||||
[136, "Spear Mastery", "Spear Mastery", "bar"],
|
|
||||||
[137, "Taunt", "Taunt", "bar"],
|
|
||||||
[138, "Shout", "Shout", "bar"],
|
|
||||||
[139, "Stun", "Stun", "bar"],
|
|
||||||
[140, "Double Throw", "Double Throw", "bar"],
|
|
||||||
[141, "Increased Stamina", "Increased Stamina", "bar"],
|
|
||||||
[142, "Find Item", "Find Item", "bar"],
|
|
||||||
[143, "Leap Attack", "Leap Attack", "bar"],
|
|
||||||
[144, "Concentrate", "Concentrate", "bar"],
|
|
||||||
[145, "Iron Skin", "Iron Skin", "bar"],
|
|
||||||
[146, "Battle Cry", "Battle Cry", "bar"],
|
|
||||||
[147, "Frenzy", "Frenzy", "bar"],
|
|
||||||
[148, "Increased Speed", "Increased Speed", "bar"],
|
|
||||||
[149, "Battle Orders", "Battle Orders", "bar"],
|
|
||||||
[150, "Grim Ward", "Grim Ward", "bar"],
|
|
||||||
[151, "Whirlwind", "Whirlwind", "bar"],
|
|
||||||
[152, "Berserk", "Berserk", "bar"],
|
|
||||||
[153, "Natural Resistance", "Natural Resistance", "bar"],
|
|
||||||
[154, "War Cry", "War Cry", "bar"],
|
|
||||||
[155, "Battle Command", "Battle Command", "bar"],
|
|
||||||
[197, "DiabWall", "Firestorm", ""],
|
|
||||||
[217, "Scroll of Identify", "Scroll of Identify", ""],
|
|
||||||
[218, "Book of Identify", "Tome of Identify", ""],
|
|
||||||
[219, "Scroll of Townportal", "Scroll of Townportal", ""],
|
|
||||||
[220, "Book of Townportal", "Tome of Townportal", ""],
|
|
||||||
[221, "Raven", "Raven", "dru"],
|
|
||||||
[222, "Plague Poppy", "Poison Creeper", "dru"],
|
|
||||||
[223, "Wearwolf", "Werewolf", "dru"],
|
|
||||||
[224, "Shape Shifting", "Lycanthropy", "dru"],
|
|
||||||
[225, "Firestorm", "Firestorm", "dru"],
|
|
||||||
[226, "Oak Sage", "Oak Sage", "dru"],
|
|
||||||
[227, "Summon Spirit Wolf", "Summon Spirit Wolf", "dru"],
|
|
||||||
[228, "Wearbear", "Werebear", "dru"],
|
|
||||||
[229, "Molten Boulder", "Molten Boulder", "dru"],
|
|
||||||
[230, "Arctic Blast", "Arctic Blast", "dru"],
|
|
||||||
[231, "Cycle of Life", "Carrion Vine", "dru"],
|
|
||||||
[232, "Feral Rage", "Feral Rage", "dru"],
|
|
||||||
[233, "Maul", "Maul", "dru"],
|
|
||||||
[234, "Eruption", "Fissure", "dru"],
|
|
||||||
[235, "Cyclone Armor", "Cyclone Armor", "dru"],
|
|
||||||
[236, "Heart of Wolverine", "Heart of Wolverine", "dru"],
|
|
||||||
[237, "Summon Fenris", "Summon Dire Wolf", "dru"],
|
|
||||||
[238, "Rabies", "Rabies", "dru"],
|
|
||||||
[239, "Fire Claws", "Fire Claws", "dru"],
|
|
||||||
[240, "Twister", "Twister", "dru"],
|
|
||||||
[241, "Vines", "Solar Creeper", "dru"],
|
|
||||||
[242, "Hunger", "Hunger", "dru"],
|
|
||||||
[243, "Shock Wave", "Shock Wave", "dru"],
|
|
||||||
[244, "Volcano", "Volcano", "dru"],
|
|
||||||
[245, "Tornado", "Tornado", "dru"],
|
|
||||||
[246, "Spirit of Barbs", "Spirit of Barbs", "dru"],
|
|
||||||
[247, "Summon Grizzly", "Summon Grizzly", "dru"],
|
|
||||||
[248, "Fury", "Fury", "dru"],
|
|
||||||
[249, "Armageddon", "Armageddon", "dru"],
|
|
||||||
[250, "Hurricane", "Hurricane", "dru"],
|
|
||||||
[251, "Fire Trauma", "Fire Blast", "ass"],
|
|
||||||
[252, "Claw Mastery", "Claw Mastery", "ass"],
|
|
||||||
[253, "Psychic Hammer", "Psychic Hammer", "ass"],
|
|
||||||
[254, "Tiger Strike", "Tiger Strike", "ass"],
|
|
||||||
[255, "Dragon Talon", "Dragon Talon", "ass"],
|
|
||||||
[256, "Shock Field", "Shock Web", "ass"],
|
|
||||||
[257, "Blade Sentinel", "Blade Sentinel", "ass"],
|
|
||||||
[258, "Quickness", "Burst of Speed", "ass"],
|
|
||||||
[259, "Fists of Fire", "Fists of Fire", "ass"],
|
|
||||||
[260, "Dragon Claw", "Dragon Claw", "ass"],
|
|
||||||
[261, "Charged Bolt Sentry", "Charged Bolt Sentry", "ass"],
|
|
||||||
[262, "Wake of Fire Sentry", "Wake of Fire", "ass"],
|
|
||||||
[263, "Weapon Block", "Weapon Block", "ass"],
|
|
||||||
[264, "Cloak of Shadows", "Cloak of Shadows", "ass"],
|
|
||||||
[265, "Cobra Strike", "Cobra Strike", "ass"],
|
|
||||||
[266, "Blade Fury", "Blade Fury", "ass"],
|
|
||||||
[267, "Fade", "Fade", "ass"],
|
|
||||||
[268, "Shadow Warrior", "Shadow Warrior", "ass"],
|
|
||||||
[269, "Claws of Thunder", "Claws of Thunder", "ass"],
|
|
||||||
[270, "Dragon Tail", "Dragon Tail", "ass"],
|
|
||||||
[271, "Lightning Sentry", "Lightning Sentry", "ass"],
|
|
||||||
[272, "Inferno Sentry", "Wake of Inferno", "ass"],
|
|
||||||
[273, "Mind Blast", "Mind Blast", "ass"],
|
|
||||||
[274, "Blades of Ice", "Blades of Ice", "ass"],
|
|
||||||
[275, "Dragon Flight", "Dragon Flight", "ass"],
|
|
||||||
[276, "Death Sentry", "Death Sentry", "ass"],
|
|
||||||
[277, "Blade Shield", "Blade Shield", "ass"],
|
|
||||||
[278, "Venom", "Venom", "ass"],
|
|
||||||
[279, "Shadow Master", "Shadow Master", "ass"],
|
|
||||||
[280, "Royal Strike", "Phoenix Strike", "ass"],
|
|
||||||
[350, "Delerium Change", "Delirium", ""],
|
|
||||||
]
|
|
||||||
|
|
||||||
const CLASS_ONLY_LABELS: Record<string, string> = {
|
export {
|
||||||
ama: '(Amazon Only)',
|
resolveSkillInfo,
|
||||||
sor: '(Sorceress Only)',
|
CANONICAL_SKILLS,
|
||||||
nec: '(Necromancer Only)',
|
CANONICAL_SKILL_TABS,
|
||||||
pal: '(Paladin Only)',
|
CANONICAL_REANIMATE_MONSTERS,
|
||||||
bar: '(Barbarian Only)',
|
|
||||||
dru: '(Druid Only)',
|
|
||||||
ass: '(Assassin Only)',
|
|
||||||
}
|
|
||||||
|
|
||||||
const SKILL_LOOKUP_BY_ID = new Map<number, { name: string; classOnly?: string }>()
|
|
||||||
const SKILL_LOOKUP_BY_NAME = new Map<string, { name: string; classOnly?: string }>()
|
|
||||||
|
|
||||||
for (const [id, rawName, displayName, cls] of CANONICAL_SKILLS) {
|
|
||||||
const classOnly = cls ? CLASS_ONLY_LABELS[cls] : undefined
|
|
||||||
const info = classOnly ? { name: displayName, classOnly } : { name: displayName }
|
|
||||||
SKILL_LOOKUP_BY_ID.set(id, info)
|
|
||||||
SKILL_LOOKUP_BY_NAME.set(rawName.toLowerCase(), info)
|
|
||||||
SKILL_LOOKUP_BY_NAME.set(displayName.toLowerCase(), info)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a skill parameter (numeric ID or internal name from Skills.txt)
|
|
||||||
* into its 1.13c canonical English display name and optional class restriction.
|
|
||||||
*/
|
|
||||||
export function resolveSkillInfo(par: string | number | undefined): { name: string; classOnly?: string } {
|
|
||||||
if (par === undefined || par === '') return { name: 'Skill' }
|
|
||||||
const str = String(par).trim()
|
|
||||||
const num = Number(str)
|
|
||||||
if (!Number.isNaN(num) && SKILL_LOOKUP_BY_ID.has(num)) {
|
|
||||||
return SKILL_LOOKUP_BY_ID.get(num)!
|
|
||||||
}
|
|
||||||
const byName = SKILL_LOOKUP_BY_NAME.get(str.toLowerCase())
|
|
||||||
if (byName) return byName
|
|
||||||
return { name: str }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Canonical 1.13c skilltab mapping (par 0..20 from CharStats.txt StrSkillTab1..3 + StrClassOnly).
|
|
||||||
*/
|
|
||||||
const CANONICAL_SKILL_TABS: Record<number, string> = {
|
|
||||||
0: 'Bow and Crossbow Skills (Amazon Only)',
|
|
||||||
1: 'Passive and Magic Skills (Amazon Only)',
|
|
||||||
2: 'Javelin and Spear Skills (Amazon Only)',
|
|
||||||
3: 'Fire Skills (Sorceress Only)',
|
|
||||||
4: 'Lightning Skills (Sorceress Only)',
|
|
||||||
5: 'Cold Skills (Sorceress Only)',
|
|
||||||
6: 'Curses (Necromancer Only)',
|
|
||||||
7: 'Poison and Bone Skills (Necromancer Only)',
|
|
||||||
8: 'Summoning Skills (Necromancer Only)',
|
|
||||||
9: 'Combat Skills (Paladin Only)',
|
|
||||||
10: 'Offensive Auras (Paladin Only)',
|
|
||||||
11: 'Defensive Auras (Paladin Only)',
|
|
||||||
12: 'Combat Skills (Barbarian Only)',
|
|
||||||
13: 'Masteries (Barbarian Only)',
|
|
||||||
14: 'Warcries (Barbarian Only)',
|
|
||||||
15: 'Summoning Skills (Druid Only)',
|
|
||||||
16: 'Shape Shifting Skills (Druid Only)',
|
|
||||||
17: 'Elemental Skills (Druid Only)',
|
|
||||||
18: 'Traps (Assassin Only)',
|
|
||||||
19: 'Shadow Disciplines (Assassin Only)',
|
|
||||||
20: 'Martial Arts (Assassin Only)',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Canonical 1.13c MonStats.txt hcIdx -> NameStr for reanimate property.
|
|
||||||
*/
|
|
||||||
const CANONICAL_REANIMATE_MONSTERS: Record<number, string> = {
|
|
||||||
0: 'Skeleton',
|
|
||||||
1: 'Returned',
|
|
||||||
2: 'Bone Warrior',
|
|
||||||
3: 'Burning Dead',
|
|
||||||
4: 'Horror',
|
|
||||||
5: 'Zombie',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPerLevelVal(rawEighths: number): string {
|
function formatPerLevelVal(rawEighths: number): string {
|
||||||
|
|
@ -502,7 +215,7 @@ export function formatPropertyCode(
|
||||||
|
|
||||||
const plusVal = effectiveVal >= 0 ? `+${effectiveVal}` : `${effectiveVal}`
|
const plusVal = effectiveVal >= 0 ? `+${effectiveVal}` : `${effectiveVal}`
|
||||||
|
|
||||||
switch (code.trim().toLowerCase()) {
|
switch (code.trim().toLowerCase().replace(/^\*/, '')) {
|
||||||
// --- Skills & Class Skills (descFunc 1, 13, 14, 16, 27, 28) ---
|
// --- Skills & Class Skills (descFunc 1, 13, 14, 16, 27, 28) ---
|
||||||
case 'allskills':
|
case 'allskills':
|
||||||
return `${plusVal} to All Skills`
|
return `${plusVal} to All Skills`
|
||||||
|
|
@ -590,8 +303,8 @@ export function formatPropertyCode(
|
||||||
}
|
}
|
||||||
case 'charged': {
|
case 'charged': {
|
||||||
const sk = resolveSkillInfo(par)
|
const sk = resolveSkillInfo(par)
|
||||||
const charges = min || effectiveVal || 10
|
const charges = Math.abs(min || effectiveVal || 10)
|
||||||
const slvl = max || 1
|
const slvl = Math.abs(max || 1)
|
||||||
return `Level ${slvl} ${sk.name} (${charges}/${charges} Charges)`
|
return `Level ${slvl} ${sk.name} (${charges}/${charges} Charges)`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,304 @@
|
||||||
|
/**
|
||||||
|
* Canonical 1.13c Skills Data & ID Lookup Table (Skills.txt).
|
||||||
|
*/
|
||||||
|
export const CANONICAL_SKILLS: readonly [number, string, string, string][] = [
|
||||||
|
[0, "Attack", "Attack", ""],
|
||||||
|
[1, "Kick", "Kick", ""],
|
||||||
|
[2, "Throw", "Throw", ""],
|
||||||
|
[3, "Unsummon", "Unsummon", ""],
|
||||||
|
[4, "Left Hand Throw", "Left Hand Throw", ""],
|
||||||
|
[5, "Left Hand Swing", "Left Hand Swing", ""],
|
||||||
|
[6, "Magic Arrow", "Magic Arrow", "ama"],
|
||||||
|
[7, "Fire Arrow", "Fire Arrow", "ama"],
|
||||||
|
[8, "Inner Sight", "Inner Sight", "ama"],
|
||||||
|
[9, "Critical Strike", "Critical Strike", "ama"],
|
||||||
|
[10, "Jab", "Jab", "ama"],
|
||||||
|
[11, "Cold Arrow", "Cold Arrow", "ama"],
|
||||||
|
[12, "Multiple Shot", "Multiple Shot", "ama"],
|
||||||
|
[13, "Dodge", "Dodge", "ama"],
|
||||||
|
[14, "Power Strike", "Power Strike", "ama"],
|
||||||
|
[15, "Poison Javelin", "Poison Javelin", "ama"],
|
||||||
|
[16, "Exploding Arrow", "Exploding Arrow", "ama"],
|
||||||
|
[17, "Slow Missiles", "Slow Missiles", "ama"],
|
||||||
|
[18, "Avoid", "Avoid", "ama"],
|
||||||
|
[19, "Impale", "Impale", "ama"],
|
||||||
|
[20, "Lightning Bolt", "Lightning Bolt", "ama"],
|
||||||
|
[21, "Ice Arrow", "Ice Arrow", "ama"],
|
||||||
|
[22, "Guided Arrow", "Guided Arrow", "ama"],
|
||||||
|
[23, "Penetrate", "Penetrate", "ama"],
|
||||||
|
[24, "Charged Strike", "Charged Strike", "ama"],
|
||||||
|
[25, "Plague Javelin", "Plague Javelin", "ama"],
|
||||||
|
[26, "Strafe", "Strafe", "ama"],
|
||||||
|
[27, "Immolation Arrow", "Immolation Arrow", "ama"],
|
||||||
|
[28, "Dopplezon", "Decoy", "ama"],
|
||||||
|
[29, "Evade", "Evade", "ama"],
|
||||||
|
[30, "Fend", "Fend", "ama"],
|
||||||
|
[31, "Freezing Arrow", "Freezing Arrow", "ama"],
|
||||||
|
[32, "Valkyrie", "Valkyrie", "ama"],
|
||||||
|
[33, "Pierce", "Pierce", "ama"],
|
||||||
|
[34, "Lightning Strike", "Lightning Strike", "ama"],
|
||||||
|
[35, "Lightning Fury", "Lightning Fury", "ama"],
|
||||||
|
[36, "Fire Bolt", "Fire Bolt", "sor"],
|
||||||
|
[37, "Warmth", "Warmth", "sor"],
|
||||||
|
[38, "Charged Bolt", "Charged Bolt", "sor"],
|
||||||
|
[39, "Ice Bolt", "Ice Bolt", "sor"],
|
||||||
|
[40, "Frozen Armor", "Frozen Armor", "sor"],
|
||||||
|
[41, "Inferno", "Inferno", "sor"],
|
||||||
|
[42, "Static Field", "Static Field", "sor"],
|
||||||
|
[43, "Telekinesis", "Telekinesis", "sor"],
|
||||||
|
[44, "Frost Nova", "Frost Nova", "sor"],
|
||||||
|
[45, "Ice Blast", "Ice Blast", "sor"],
|
||||||
|
[46, "Blaze", "Blaze", "sor"],
|
||||||
|
[47, "Fire Ball", "Fire Ball", "sor"],
|
||||||
|
[48, "Nova", "Nova", "sor"],
|
||||||
|
[49, "Lightning", "Lightning", "sor"],
|
||||||
|
[50, "Shiver Armor", "Shiver Armor", "sor"],
|
||||||
|
[51, "Fire Wall", "Fire Wall", "sor"],
|
||||||
|
[52, "Enchant", "Enchant", "sor"],
|
||||||
|
[53, "Chain Lightning", "Chain Lightning", "sor"],
|
||||||
|
[54, "Teleport", "Teleport", "sor"],
|
||||||
|
[55, "Glacial Spike", "Glacial Spike", "sor"],
|
||||||
|
[56, "Meteor", "Meteor", "sor"],
|
||||||
|
[57, "Thunder Storm", "Thunder Storm", "sor"],
|
||||||
|
[58, "Energy Shield", "Energy Shield", "sor"],
|
||||||
|
[59, "Blizzard", "Blizzard", "sor"],
|
||||||
|
[60, "Chilling Armor", "Chilling Armor", "sor"],
|
||||||
|
[61, "Fire Mastery", "Fire Mastery", "sor"],
|
||||||
|
[62, "Hydra", "Hydra", "sor"],
|
||||||
|
[63, "Lightning Mastery", "Lightning Mastery", "sor"],
|
||||||
|
[64, "Frozen Orb", "Frozen Orb", "sor"],
|
||||||
|
[65, "Cold Mastery", "Cold Mastery", "sor"],
|
||||||
|
[66, "Amplify Damage", "Amplify Damage", "nec"],
|
||||||
|
[67, "Teeth", "Teeth", "nec"],
|
||||||
|
[68, "Bone Armor", "Bone Armor", "nec"],
|
||||||
|
[69, "Skeleton Mastery", "Skeleton Mastery", "nec"],
|
||||||
|
[70, "Raise Skeleton", "Raise Skeleton", "nec"],
|
||||||
|
[71, "Dim Vision", "Dim Vision", "nec"],
|
||||||
|
[72, "Weaken", "Weaken", "nec"],
|
||||||
|
[73, "Poison Dagger", "Poison Dagger", "nec"],
|
||||||
|
[74, "Corpse Explosion", "Corpse Explosion", "nec"],
|
||||||
|
[75, "Clay Golem", "Clay Golem", "nec"],
|
||||||
|
[76, "Iron Maiden", "Iron Maiden", "nec"],
|
||||||
|
[77, "Terror", "Terror", "nec"],
|
||||||
|
[78, "Bone Wall", "Bone Wall", "nec"],
|
||||||
|
[79, "Golem Mastery", "Golem Mastery", "nec"],
|
||||||
|
[80, "Raise Skeletal Mage", "Raise Skeletal Mage", "nec"],
|
||||||
|
[81, "Confuse", "Confuse", "nec"],
|
||||||
|
[82, "Life Tap", "Life Tap", "nec"],
|
||||||
|
[83, "Poison Explosion", "Poison Explosion", "nec"],
|
||||||
|
[84, "Bone Spear", "Bone Spear", "nec"],
|
||||||
|
[85, "BloodGolem", "Blood Golem", "nec"],
|
||||||
|
[86, "Attract", "Attract", "nec"],
|
||||||
|
[87, "Decrepify", "Decrepify", "nec"],
|
||||||
|
[88, "Bone Prison", "Bone Prison", "nec"],
|
||||||
|
[89, "Summon Resist", "Summon Resist", "nec"],
|
||||||
|
[90, "IronGolem", "Iron Golem", "nec"],
|
||||||
|
[91, "Lower Resist", "Lower Resist", "nec"],
|
||||||
|
[92, "Poison Nova", "Poison Nova", "nec"],
|
||||||
|
[93, "Bone Spirit", "Bone Spirit", "nec"],
|
||||||
|
[94, "FireGolem", "Fire Golem", "nec"],
|
||||||
|
[95, "Revive", "Revive", "nec"],
|
||||||
|
[96, "Sacrifice", "Sacrifice", "pal"],
|
||||||
|
[97, "Smite", "Smite", "pal"],
|
||||||
|
[98, "Might", "Might", "pal"],
|
||||||
|
[99, "Prayer", "Prayer", "pal"],
|
||||||
|
[100, "Resist Fire", "Resist Fire", "pal"],
|
||||||
|
[101, "Holy Bolt", "Holy Bolt", "pal"],
|
||||||
|
[102, "Holy Fire", "Holy Fire", "pal"],
|
||||||
|
[103, "Thorns", "Thorns", "pal"],
|
||||||
|
[104, "Defiance", "Defiance", "pal"],
|
||||||
|
[105, "Resist Cold", "Resist Cold", "pal"],
|
||||||
|
[106, "Zeal", "Zeal", "pal"],
|
||||||
|
[107, "Charge", "Charge", "pal"],
|
||||||
|
[108, "Blessed Aim", "Blessed Aim", "pal"],
|
||||||
|
[109, "Cleansing", "Cleansing", "pal"],
|
||||||
|
[110, "Resist Lightning", "Resist Lightning", "pal"],
|
||||||
|
[111, "Vengeance", "Vengeance", "pal"],
|
||||||
|
[112, "Blessed Hammer", "Blessed Hammer", "pal"],
|
||||||
|
[113, "Concentration", "Concentration", "pal"],
|
||||||
|
[114, "Holy Freeze", "Holy Freeze", "pal"],
|
||||||
|
[115, "Vigor", "Vigor", "pal"],
|
||||||
|
[116, "Conversion", "Conversion", "pal"],
|
||||||
|
[117, "Holy Shield", "Holy Shield", "pal"],
|
||||||
|
[118, "Holy Shock", "Holy Shock", "pal"],
|
||||||
|
[119, "Sanctuary", "Sanctuary", "pal"],
|
||||||
|
[120, "Meditation", "Meditation", "pal"],
|
||||||
|
[121, "Fist of the Heavens", "Fist of the Heavens", "pal"],
|
||||||
|
[122, "Fanaticism", "Fanaticism", "pal"],
|
||||||
|
[123, "Conviction", "Conviction", "pal"],
|
||||||
|
[124, "Redemption", "Redemption", "pal"],
|
||||||
|
[125, "Salvation", "Salvation", "pal"],
|
||||||
|
[126, "Bash", "Bash", "bar"],
|
||||||
|
[127, "Sword Mastery", "Sword Mastery", "bar"],
|
||||||
|
[128, "Axe Mastery", "Axe Mastery", "bar"],
|
||||||
|
[129, "Mace Mastery", "Mace Mastery", "bar"],
|
||||||
|
[130, "Howl", "Howl", "bar"],
|
||||||
|
[131, "Find Potion", "Find Potion", "bar"],
|
||||||
|
[132, "Leap", "Leap", "bar"],
|
||||||
|
[133, "Double Swing", "Double Swing", "bar"],
|
||||||
|
[134, "Pole Arm Mastery", "Pole Arm Mastery", "bar"],
|
||||||
|
[135, "Throwing Mastery", "Throwing Mastery", "bar"],
|
||||||
|
[136, "Spear Mastery", "Spear Mastery", "bar"],
|
||||||
|
[137, "Taunt", "Taunt", "bar"],
|
||||||
|
[138, "Shout", "Shout", "bar"],
|
||||||
|
[139, "Stun", "Stun", "bar"],
|
||||||
|
[140, "Double Throw", "Double Throw", "bar"],
|
||||||
|
[141, "Increased Stamina", "Increased Stamina", "bar"],
|
||||||
|
[142, "Find Item", "Find Item", "bar"],
|
||||||
|
[143, "Leap Attack", "Leap Attack", "bar"],
|
||||||
|
[144, "Concentrate", "Concentrate", "bar"],
|
||||||
|
[145, "Iron Skin", "Iron Skin", "bar"],
|
||||||
|
[146, "Battle Cry", "Battle Cry", "bar"],
|
||||||
|
[147, "Frenzy", "Frenzy", "bar"],
|
||||||
|
[148, "Increased Speed", "Increased Speed", "bar"],
|
||||||
|
[149, "Battle Orders", "Battle Orders", "bar"],
|
||||||
|
[150, "Grim Ward", "Grim Ward", "bar"],
|
||||||
|
[151, "Whirlwind", "Whirlwind", "bar"],
|
||||||
|
[152, "Berserk", "Berserk", "bar"],
|
||||||
|
[153, "Natural Resistance", "Natural Resistance", "bar"],
|
||||||
|
[154, "War Cry", "War Cry", "bar"],
|
||||||
|
[155, "Battle Command", "Battle Command", "bar"],
|
||||||
|
[197, "DiabWall", "Firestorm", ""],
|
||||||
|
[217, "Scroll of Identify", "Scroll of Identify", ""],
|
||||||
|
[218, "Book of Identify", "Tome of Identify", ""],
|
||||||
|
[219, "Scroll of Townportal", "Scroll of Townportal", ""],
|
||||||
|
[220, "Book of Townportal", "Tome of Townportal", ""],
|
||||||
|
[221, "Raven", "Raven", "dru"],
|
||||||
|
[222, "Plague Poppy", "Poison Creeper", "dru"],
|
||||||
|
[223, "Wearwolf", "Werewolf", "dru"],
|
||||||
|
[224, "Shape Shifting", "Lycanthropy", "dru"],
|
||||||
|
[225, "Firestorm", "Firestorm", "dru"],
|
||||||
|
[226, "Oak Sage", "Oak Sage", "dru"],
|
||||||
|
[227, "Summon Spirit Wolf", "Summon Spirit Wolf", "dru"],
|
||||||
|
[228, "Wearbear", "Werebear", "dru"],
|
||||||
|
[229, "Molten Boulder", "Molten Boulder", "dru"],
|
||||||
|
[230, "Arctic Blast", "Arctic Blast", "dru"],
|
||||||
|
[231, "Cycle of Life", "Carrion Vine", "dru"],
|
||||||
|
[232, "Feral Rage", "Feral Rage", "dru"],
|
||||||
|
[233, "Maul", "Maul", "dru"],
|
||||||
|
[234, "Eruption", "Fissure", "dru"],
|
||||||
|
[235, "Cyclone Armor", "Cyclone Armor", "dru"],
|
||||||
|
[236, "Heart of Wolverine", "Heart of Wolverine", "dru"],
|
||||||
|
[237, "Summon Fenris", "Summon Dire Wolf", "dru"],
|
||||||
|
[238, "Rabies", "Rabies", "dru"],
|
||||||
|
[239, "Fire Claws", "Fire Claws", "dru"],
|
||||||
|
[240, "Twister", "Twister", "dru"],
|
||||||
|
[241, "Vines", "Solar Creeper", "dru"],
|
||||||
|
[242, "Hunger", "Hunger", "dru"],
|
||||||
|
[243, "Shock Wave", "Shock Wave", "dru"],
|
||||||
|
[244, "Volcano", "Volcano", "dru"],
|
||||||
|
[245, "Tornado", "Tornado", "dru"],
|
||||||
|
[246, "Spirit of Barbs", "Spirit of Barbs", "dru"],
|
||||||
|
[247, "Summon Grizzly", "Summon Grizzly", "dru"],
|
||||||
|
[248, "Fury", "Fury", "dru"],
|
||||||
|
[249, "Armageddon", "Armageddon", "dru"],
|
||||||
|
[250, "Hurricane", "Hurricane", "dru"],
|
||||||
|
[251, "Fire Trauma", "Fire Blast", "ass"],
|
||||||
|
[252, "Claw Mastery", "Claw Mastery", "ass"],
|
||||||
|
[253, "Psychic Hammer", "Psychic Hammer", "ass"],
|
||||||
|
[254, "Tiger Strike", "Tiger Strike", "ass"],
|
||||||
|
[255, "Dragon Talon", "Dragon Talon", "ass"],
|
||||||
|
[256, "Shock Field", "Shock Web", "ass"],
|
||||||
|
[257, "Blade Sentinel", "Blade Sentinel", "ass"],
|
||||||
|
[258, "Quickness", "Burst of Speed", "ass"],
|
||||||
|
[259, "Fists of Fire", "Fists of Fire", "ass"],
|
||||||
|
[260, "Dragon Claw", "Dragon Claw", "ass"],
|
||||||
|
[261, "Charged Bolt Sentry", "Charged Bolt Sentry", "ass"],
|
||||||
|
[262, "Wake of Fire Sentry", "Wake of Fire", "ass"],
|
||||||
|
[263, "Weapon Block", "Weapon Block", "ass"],
|
||||||
|
[264, "Cloak of Shadows", "Cloak of Shadows", "ass"],
|
||||||
|
[265, "Cobra Strike", "Cobra Strike", "ass"],
|
||||||
|
[266, "Blade Fury", "Blade Fury", "ass"],
|
||||||
|
[267, "Fade", "Fade", "ass"],
|
||||||
|
[268, "Shadow Warrior", "Shadow Warrior", "ass"],
|
||||||
|
[269, "Claws of Thunder", "Claws of Thunder", "ass"],
|
||||||
|
[270, "Dragon Tail", "Dragon Tail", "ass"],
|
||||||
|
[271, "Lightning Sentry", "Lightning Sentry", "ass"],
|
||||||
|
[272, "Inferno Sentry", "Wake of Inferno", "ass"],
|
||||||
|
[273, "Mind Blast", "Mind Blast", "ass"],
|
||||||
|
[274, "Blades of Ice", "Blades of Ice", "ass"],
|
||||||
|
[275, "Dragon Flight", "Dragon Flight", "ass"],
|
||||||
|
[276, "Death Sentry", "Death Sentry", "ass"],
|
||||||
|
[277, "Blade Shield", "Blade Shield", "ass"],
|
||||||
|
[278, "Venom", "Venom", "ass"],
|
||||||
|
[279, "Shadow Master", "Shadow Master", "ass"],
|
||||||
|
[280, "Royal Strike", "Phoenix Strike", "ass"],
|
||||||
|
[350, "Delerium Change", "Delirium", ""],
|
||||||
|
]
|
||||||
|
|
||||||
|
const CLASS_ONLY_LABELS: Record<string, string> = {
|
||||||
|
ama: '(Amazon Only)',
|
||||||
|
sor: '(Sorceress Only)',
|
||||||
|
nec: '(Necromancer Only)',
|
||||||
|
pal: '(Paladin Only)',
|
||||||
|
bar: '(Barbarian Only)',
|
||||||
|
dru: '(Druid Only)',
|
||||||
|
ass: '(Assassin Only)',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SKILL_LOOKUP_BY_ID = new Map<number, { name: string; classOnly?: string }>()
|
||||||
|
const SKILL_LOOKUP_BY_NAME = new Map<string, { name: string; classOnly?: string }>()
|
||||||
|
|
||||||
|
for (const [id, rawName, displayName, cls] of CANONICAL_SKILLS) {
|
||||||
|
const classOnly = cls ? CLASS_ONLY_LABELS[cls] : undefined
|
||||||
|
const info = classOnly ? { name: displayName, classOnly } : { name: displayName }
|
||||||
|
SKILL_LOOKUP_BY_ID.set(id, info)
|
||||||
|
SKILL_LOOKUP_BY_NAME.set(rawName.toLowerCase(), info)
|
||||||
|
SKILL_LOOKUP_BY_NAME.set(displayName.toLowerCase(), info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a skill parameter (numeric ID or internal name from Skills.txt)
|
||||||
|
* into its 1.13c canonical English display name and optional class restriction.
|
||||||
|
*/
|
||||||
|
export function resolveSkillInfo(par: string | number | undefined): { name: string; classOnly?: string } {
|
||||||
|
if (par === undefined || par === '') return { name: 'Skill' }
|
||||||
|
const str = String(par).trim()
|
||||||
|
const num = Number(str)
|
||||||
|
if (!Number.isNaN(num) && SKILL_LOOKUP_BY_ID.has(num)) {
|
||||||
|
return SKILL_LOOKUP_BY_ID.get(num)!
|
||||||
|
}
|
||||||
|
const byName = SKILL_LOOKUP_BY_NAME.get(str.toLowerCase())
|
||||||
|
if (byName) return byName
|
||||||
|
return { name: str }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical 1.13c skilltab mapping (par 0..20 from CharStats.txt StrSkillTab1..3 + StrClassOnly).
|
||||||
|
*/
|
||||||
|
export const CANONICAL_SKILL_TABS: Record<number, string> = {
|
||||||
|
0: 'Bow and Crossbow Skills (Amazon Only)',
|
||||||
|
1: 'Passive and Magic Skills (Amazon Only)',
|
||||||
|
2: 'Javelin and Spear Skills (Amazon Only)',
|
||||||
|
3: 'Fire Skills (Sorceress Only)',
|
||||||
|
4: 'Lightning Skills (Sorceress Only)',
|
||||||
|
5: 'Cold Skills (Sorceress Only)',
|
||||||
|
6: 'Curses (Necromancer Only)',
|
||||||
|
7: 'Poison and Bone Skills (Necromancer Only)',
|
||||||
|
8: 'Summoning Skills (Necromancer Only)',
|
||||||
|
9: 'Combat Skills (Paladin Only)',
|
||||||
|
10: 'Offensive Auras (Paladin Only)',
|
||||||
|
11: 'Defensive Auras (Paladin Only)',
|
||||||
|
12: 'Combat Skills (Barbarian Only)',
|
||||||
|
13: 'Masteries (Barbarian Only)',
|
||||||
|
14: 'Warcries (Barbarian Only)',
|
||||||
|
15: 'Summoning Skills (Druid Only)',
|
||||||
|
16: 'Shape Shifting Skills (Druid Only)',
|
||||||
|
17: 'Elemental Skills (Druid Only)',
|
||||||
|
18: 'Traps (Assassin Only)',
|
||||||
|
19: 'Shadow Disciplines (Assassin Only)',
|
||||||
|
20: 'Martial Arts (Assassin Only)',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical 1.13c MonStats.txt hcIdx -> NameStr for reanimate property.
|
||||||
|
*/
|
||||||
|
export const CANONICAL_REANIMATE_MONSTERS: Record<number, string> = {
|
||||||
|
0: 'Skeleton',
|
||||||
|
1: 'Returned',
|
||||||
|
2: 'Bone Warrior',
|
||||||
|
3: 'Burning Dead',
|
||||||
|
4: 'Horror',
|
||||||
|
5: 'Zombie',
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -82,10 +82,26 @@ describe('Issue #122: Equipment & Affix Chinese Localization (EN / ZH / Bilingua
|
||||||
expect(translateSkillName('Poison Creeper')).toBe('剧毒蔓藤')
|
expect(translateSkillName('Poison Creeper')).toBe('剧毒蔓藤')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('translates rare and magic names correctly', () => {
|
it('translates rare and magic names correctly with proper Chinese word order', () => {
|
||||||
expect(translateRareName('Beast Edge')).toBe('野兽之刃')
|
expect(translateRareName('Beast Edge')).toBe('野兽之刃')
|
||||||
expect(translateRareName('Shadow Brow')).toBe('阴影之额头')
|
expect(translateRareName('Shadow Brow')).toBe('阴影之额头')
|
||||||
|
// Prefix only
|
||||||
expect(translateMagicName('Sturdy Leather Armor', 'Leather Armor')).toBe('结实之皮甲')
|
expect(translateMagicName('Sturdy Leather Armor', 'Leather Armor')).toBe('结实之皮甲')
|
||||||
|
// Suffix only: modifier before noun
|
||||||
|
expect(translateMagicName('War Spear of Swiftness', 'War Spear')).toBe('疾速之巨战长矛')
|
||||||
|
// Both prefix & suffix: modifiers before noun
|
||||||
|
expect(translateMagicName('Savage Short Sword of the Giant', 'Short Sword')).toBe('野蛮之巨人之短剑')
|
||||||
|
// 100% Affix Coverage test for previously missing items
|
||||||
|
expect(translateMagicName("Harpoonist's Ceremonial Javelin", 'Ceremonial Javelin')).toBe('标枪之祭典标枪')
|
||||||
|
expect(translateMagicName('Trump Edge Bow', 'Edge Bow')).toBe('胜者之锋锐之弓')
|
||||||
|
expect(translateMagicName('Gritty Broad Axe', 'Broad Axe')).toBe('坚韧之阔斧')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('translates item names to normalized Simplified Chinese', () => {
|
||||||
|
expect(translateItemName('Flawless Ruby')).toBe('无瑕疵的红宝石')
|
||||||
|
expect(translateItemName('Flawless Emerald')).toBe('无瑕疵的绿宝石')
|
||||||
|
expect(translateItemName('The Humongous')).toBe('巨大无比')
|
||||||
|
expect(translateItemName('Steelclash')).toBe('作响的金属')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -93,8 +109,8 @@ describe('Issue #122: Equipment & Affix Chinese Localization (EN / ZH / Bilingua
|
||||||
it('formats skills in Chinese', () => {
|
it('formats skills in Chinese', () => {
|
||||||
expect(formatPropertyCodeI18n('allskills', 2, undefined, undefined, undefined, 'zh')).toBe('+2 所有技能')
|
expect(formatPropertyCodeI18n('allskills', 2, undefined, undefined, undefined, 'zh')).toBe('+2 所有技能')
|
||||||
expect(formatPropertyCodeI18n('ama', 1, undefined, undefined, undefined, 'zh')).toBe('+1 亚马逊技能等级')
|
expect(formatPropertyCodeI18n('ama', 1, undefined, undefined, undefined, 'zh')).toBe('+1 亚马逊技能等级')
|
||||||
expect(formatPropertyCodeI18n('skilltab', 2, undefined, undefined, 3, 'zh')).toBe('+2 火焰技能 (限法师使用)')
|
expect(formatPropertyCodeI18n('skill', 3, undefined, undefined, 'Teleport', 'zh')).toBe('+3 传送 (限法师使用)')
|
||||||
expect(formatPropertyCodeI18n('skill', 3, undefined, undefined, 'Teleport', 'zh')).toBe('+3 传送')
|
expect(formatPropertyCodeI18n('oskill', 1, undefined, undefined, 'Teleport', 'zh')).toBe('+1 传送')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('formats attributes & life/mana in Chinese', () => {
|
it('formats attributes & life/mana in Chinese', () => {
|
||||||
|
|
@ -117,6 +133,17 @@ describe('Issue #122: Equipment & Affix Chinese Localization (EN / ZH / Bilingua
|
||||||
expect(formatPropertyCodeI18n('hp/lvl', 12, undefined, undefined, undefined, 'zh')).toBe('+1.5 生命 (依角色等级提升)')
|
expect(formatPropertyCodeI18n('hp/lvl', 12, undefined, undefined, undefined, 'zh')).toBe('+1.5 生命 (依角色等级提升)')
|
||||||
expect(formatPropertyCodeI18n('rep-dur', 0, 0, undefined, 33, 'zh')).toBe('每 3 秒恢复 1 点耐久度')
|
expect(formatPropertyCodeI18n('rep-dur', 0, 0, undefined, 33, 'zh')).toBe('每 3 秒恢复 1 点耐久度')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('handles charged spell properties with MPQ negative encoding', () => {
|
||||||
|
// In 1.13c MPQ, charges and level are stored as negative numbers e.g. min: -20, max: -3
|
||||||
|
expect(formatPropertyCodeI18n('charged', -20, -20, -3, 'Enchant', 'zh')).toBe('等级 3 强化 (20/20 次充能)')
|
||||||
|
expect(formatPropertyCodeI18n('charged', -20, -20, -3, 'Enchant', 'en')).toBe('Level 3 Enchant (20/20 Charges)')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles asterisk-prefixed properties like *hp from UniqueItems.txt', () => {
|
||||||
|
expect(formatPropertyCodeI18n('*hp', -10, -10, -10, undefined, 'zh')).toBe('-10 生命')
|
||||||
|
expect(formatPropertyCodeI18n('*hp', -10, -10, -10, undefined, 'en')).toBe('-10 to Life')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('4. formatItemTooltip: EN, ZH, and Bilingual Modes', () => {
|
describe('4. formatItemTooltip: EN, ZH, and Bilingual Modes', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue