/* Helper pasif, kelemahan, dan gaya bertarung (RENCANA 3.2 dan 3.3). Data: content/passives.js (window.PASSIVES, window.STYLES). Berkas ini tidak menyentuh mesin: game.js memanggil Passives.merge(id, gaya) saat pertandingan dimulai lalu membaca angkanya lewat passiveMod(actor, key, base). Di sini juga teks angka yang tampil ke pemain (menu Karakter, layar VS, daftar gerakan), disusun otomatis dari `mods` agar selalu sama dengan yang dipakai mesin. */ (() => { 'use strict'; // Kunci yang dikenal. up = 'good' bila angka naik menguntungkan pemilik (damage keluar, kecepatan, ...), 'bad' bila naik merugikan (damage masuk). add = kunci penambah. const KEYS = { dmgOut: { label: 'Damage', short: 'Damage', up: 'good' }, dmgBasic: { label: 'Damage pukulan dasar', short: 'Pukulan dasar', up: 'good' }, dmgSkill: { label: 'Damage skill dan ultimate', short: 'Skill dan ultimate', up: 'good' }, dmgIn: { label: 'Damage yang diterima', short: 'Damage diterima', up: 'bad' }, walk: { label: 'Kecepatan jalan', short: 'Jalan', up: 'good' }, cd: { label: 'Kecepatan pulih cooldown', short: 'Pulih cooldown', up: 'good' }, cdSkill: { label: 'Kecepatan pulih skill', short: 'Pulih skill', up: 'good' }, cdUlt: { label: 'Kecepatan pulih ultimate', short: 'Pulih ultimate', up: 'good' }, parry: { label: 'Jendela parry', short: 'Parry', up: 'good' }, modeDur: { label: 'Durasi mode', short: 'Durasi mode', up: 'good' }, regen: { label: 'Pemulihan HP', short: 'Pulih HP', up: 'good', add: true, unit: ' HP/dtk' } }; const STYLE_ORDER = ['agresif', 'seimbang', 'bertahan'], DEFAULT_STYLE = 'seimbang'; const esc = t => String(t ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); const styles = () => window.STYLES || {}; const styleKey = k => (styles()[k] ? k : DEFAULT_STYLE); const info = id => { const p = window.PASSIVES?.[id]; return p ? { id, name: p.name, desc: p.desc, flaw: p.flaw || { name: '', desc: '' }, mods: p.mods || {} } : null; }; const style = k => { const key = styleKey(k), s = styles()[key] || { name: 'SEIMBANG', desc: '', mods: {} }; return { key, name: s.name, desc: s.desc, mods: s.mods || {} }; }; // Gabungan angka perk + kelemahan karakter dan gaya yang dipilih: pengali dikalikan, kunci penambah dijumlahkan. Hanya kunci yang dikenal. function merge(id, styleName) { const out = {}; for (const src of [window.PASSIVES?.[id]?.mods, styles()[styleKey(styleName)]?.mods]) { if (!src) continue; for (const [k, v] of Object.entries(src)) { const def = KEYS[k]; if (!def || !Number.isFinite(v)) continue; out[k] = def.add ? (out[k] || 0) + v : (out[k] ?? 1) * v; } } return out; } // Satu butir efek sebagai teks: "Damage yang diterima -8%", "Pemulihan HP +0,5 HP/dtk". Koma desimal sesuai bahasa Indonesia. function effectText(k, v, short) { const def = KEYS[k]; if (!def) return ''; const label = short ? def.short || def.label : def.label; if (def.add) return `${label} +${String(Math.round(v * 10) / 10).replace('.', ',')}${def.unit || ''}`; const pct = Math.round((v - 1) * 100); return pct ? `${label} ${pct > 0 ? '+' : '−'}${Math.abs(pct)}%` : ''; } // Pisahkan efek menguntungkan (good) dan merugikan (bad) dari arah angkanya. function effects(mods, short) { const good = [], bad = []; for (const [k, v] of Object.entries(mods || {})) { const def = KEYS[k]; if (!def) continue; const delta = def.add ? v : v - 1, text = effectText(k, v, short); if (!text || !delta) continue; ((delta > 0) === (def.up === 'good') ? good : bad).push(text); } return { good, bad }; } // Perk dan kelemahan untuk ditampilkan: nama, cerita, dan angka otomatis. function describe(id, short) { const p = info(id); if (!p) return null; const fx = effects(p.mods, short); return { id, perk: { name: p.name, desc: p.desc, fx: fx.good.join(', ') }, flaw: { name: p.flaw.name, desc: p.flaw.desc, fx: fx.bad.join(', ') } }; } function styleView(k, short) { const s = style(k), fx = effects(s.mods, short); return { key: s.key, name: s.name, desc: s.desc, good: fx.good, bad: fx.bad, fx: [...fx.good, ...fx.bad].join(', ') }; } // Gaya acak untuk CPU. `rand` (0..1) dari pengacak kosmetik penelepon; bukan random() mesin. const randomStyle = rand => STYLE_ORDER[Math.min(STYLE_ORDER.length - 1, Math.floor((typeof rand === 'function' ? rand() : Math.random()) * STYLE_ORDER.length))]; // ---- layar VS: satu baris kecil per sisi (perk, kelemahan, dan gaya bila bukan Seimbang) ---- function vsHtml(id, styleName) { const d = describe(id); if (!d) return ''; const sk = styleKey(styleName), tag = sk === DEFAULT_STYLE ? '' : `GAYA ${esc(style(sk).name)}`; return `
▲ ${esc(d.perk.name)}▼ ${esc(d.flaw.name)}${tag}
`; } function decorateVS(el, vs = {}) { try { const sides = [['.vs-side.l .vs-info', vs.player, vs.styles?.player], ['.vs-side.r .vs-info', vs.enemy, vs.styles?.enemy]]; for (const [sel, id, st] of sides) { const box = el.querySelector(sel); if (box && !box.querySelector('.vs-perk')) box.insertAdjacentHTML('beforeend', vsHtml(id, st)); } } catch (_) { /* hiasan saja; layar VS tetap tampil */ } } // ---- menu Karakter (meta.js renderLocker): blok Pasif dan blok Gaya bertarung ---- function lockerHtml(id, styleName) { const d = describe(id); if (!d) return ''; const sk = styleKey(styleName), sv = styleView(sk); const row = (cls, mark, name, desc, fx) => `