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, value)
|
||||
*/
|
||||
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", ""],
|
||||
]
|
||||
import {
|
||||
CANONICAL_SKILLS,
|
||||
resolveSkillInfo,
|
||||
CANONICAL_SKILL_TABS,
|
||||
CANONICAL_REANIMATE_MONSTERS,
|
||||
} from './skills-data.ts'
|
||||
|
||||
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).
|
||||
*/
|
||||
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',
|
||||
export {
|
||||
resolveSkillInfo,
|
||||
CANONICAL_SKILLS,
|
||||
CANONICAL_SKILL_TABS,
|
||||
CANONICAL_REANIMATE_MONSTERS,
|
||||
}
|
||||
|
||||
function formatPerLevelVal(rawEighths: number): string {
|
||||
|
|
@ -502,7 +215,7 @@ export function formatPropertyCode(
|
|||
|
||||
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) ---
|
||||
case 'allskills':
|
||||
return `${plusVal} to All Skills`
|
||||
|
|
@ -590,8 +303,8 @@ export function formatPropertyCode(
|
|||
}
|
||||
case 'charged': {
|
||||
const sk = resolveSkillInfo(par)
|
||||
const charges = min || effectiveVal || 10
|
||||
const slvl = max || 1
|
||||
const charges = Math.abs(min || effectiveVal || 10)
|
||||
const slvl = Math.abs(max || 1)
|
||||
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('剧毒蔓藤')
|
||||
})
|
||||
|
||||
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('Shadow Brow')).toBe('阴影之额头')
|
||||
// Prefix only
|
||||
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', () => {
|
||||
expect(formatPropertyCodeI18n('allskills', 2, undefined, undefined, undefined, 'zh')).toBe('+2 所有技能')
|
||||
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', () => {
|
||||
|
|
@ -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('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', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue