88 lines
6.4 KiB
JavaScript
88 lines
6.4 KiB
JavaScript
/* 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 ? '' : `<em>GAYA ${esc(style(sk).name)}</em>`;
|
||
return `<p class="vs-perk" aria-label="Pasif ${esc(d.perk.name)}. Kelemahan ${esc(d.flaw.name)}."><span class="up">▲ ${esc(d.perk.name)}</span><span class="dn">▼ ${esc(d.flaw.name)}</span>${tag}</p>`;
|
||
}
|
||
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) => `<div class="pas-row ${cls}"><b>${mark} ${esc(name)}</b><span>${esc(desc)}</span>${fx ? `<small>${esc(fx)}</small>` : ''}</div>`;
|
||
const pills = STYLE_ORDER.filter(k => styles()[k]).map(k => `<button class="pill ${k === sk ? 'on' : ''}" data-style="${k}">${esc(styles()[k].name)}</button>`).join('');
|
||
return `<h5>Pasif dan kelemahan</h5><div class="pas">${row('up', '▲', d.perk.name, d.perk.desc, d.perk.fx)}${row('dn', '▼', d.flaw.name, d.flaw.desc, d.flaw.fx)}</div>`
|
||
+ `<h5>Gaya bertarung</h5><div class="pills">${pills}</div><p class="meta-hint pas-style"><b>${esc(sv.name)}</b> — ${esc(sv.desc)}${sv.fx ? `<small>${esc(sv.fx)}</small>` : ''}</p>`;
|
||
}
|
||
|
||
window.Passives = { KEYS, STYLE_ORDER, DEFAULT_STYLE, info, style, styleKey, merge, effects, describe, styleView, randomStyle, vsHtml, decorateVS, lockerHtml };
|
||
})();
|