/* ============ gacha-ui.js — banners por generación y animación de tirada ============
* La lógica (probabilidades, pity, shiny, repetidos) está en systems/gacha.js.
*/
G.GachaUI = (() => {
const $ = G.UI.$;
let busy = false, skip = false;
let gen = 1;
function init() {
$('pull-10').onclick = () => pull(10, 't1');
$('pull-10b').onclick = () => pull(10, 't10');
$('pull-100').onclick = () => pull(100, 't1');
$('pull-100b').onclick = () => pull(100, 't10');
$('pull-close').onclick = close;
// Saltar: se acaba la tragaperras y salen todas las cartas de golpe (también con Esc).
$('pull-skip').onclick = skipNow;
addEventListener('keydown', e => { if (e.key === 'Escape' && busy && !$('pull-fx').classList.contains('hidden')) skipNow(); });
const tabs = $('gacha-gens');
G.Gacha.BANNERS.forEach(b => {
const t = document.createElement('button');
t.className = 'gen-tab';
t.dataset.gen = b.gen;
t.innerHTML = `${b.name}Gen ${b.gen}`;
t.onclick = () => { gen = b.gen; refresh(); };
tabs.appendChild(t);
});
}
function open() {
G.UI.show('scr-gacha', true);
refresh();
}
function refresh() {
const s = G.DB.save;
const banner = G.Gacha.BANNERS[gen - 1];
document.querySelectorAll('#gacha-gens .gen-tab').forEach(t => t.classList.toggle('on', +t.dataset.gen === gen));
const all = G.DEX.filter(p => p.gen === gen);
const owned = all.filter(p => s.owned[p.dex]).length;
const shinies = all.filter(p => s.shiny && s.shiny[p.dex]).length;
const [c1, c2, c3] = banner.color;
$('banner').style.background =
`radial-gradient(circle at 80% 30%, rgba(255,210,63,.3), transparent 45%),
radial-gradient(circle at 15% 80%, rgba(196,123,255,.3), transparent 50%),
linear-gradient(135deg, ${c1}, ${c2} 55%, ${c3})`;
$('banner-title').textContent = banner.name + ' Misterioso';
$('banner-desc').textContent = `Los ${all.length} Pokémon de la generación ${gen}. ¡Los legendarios esperan!`;
$('gacha-owned').innerHTML = `Tienes ${owned} de ${all.length}` +
(shinies ? ` · ${G.Icons.html('shiny', 12)} ${shinies} shiny` : '') +
(G.Gacha.hasIris(s, gen) ? ` · ${G.Icons.html('iris', 12)} ${G.T('Amuleto Iris')}` : '');
const feat = $('gacha-feat');
feat.innerHTML = '';
for (const dex of G.Gacha.featured(gen)) {
feat.appendChild(G.UI.spriteCanvas(dex, 170, 190, { lively: true, scale: 2, dir: 1 }));
}
const pity = (s.pity && s.pity[gen]) || 0;
$('gacha-pity').textContent = `Tiradas sin legendaria en ${banner.name}: ${pity} / ${G.Gacha.PITY_5}`;
$('gacha-rates').innerHTML = '
Probabilidades
' + G.Gacha.RATES.slice().reverse().map(([r, w]) => `
${G.UI.stars(r, 10)} ${G.Gacha.RARITY_NAME[r]}
${w}% · ${G.Gacha.pool(r, gen).length}
`).join('') +
`${G.Icons.html('shiny', 12)} Shiny${G.Gacha.hasIris(s, gen) ? G.Icons.html('iris', 12) + ' 2%' : '1%'}
· La tirada ×10 garantiza al menos una Épica.
· A las ${G.Gacha.PITY_5} tiradas sin Legendaria en un banner, la siguiente lo es.
· Los repetidos dan Pokémonedas. Los shiny no se repiten.
· Los tickets se consiguen jugando: algunos Pokémon los sueltan al caer, y
los jefes y los legendarios de las grietas, a veces, tickets ×10.
`;
const t = s.tickets;
$('cost-10').innerHTML = `${G.Icons.html('ticket', 18)} 10`;
$('cost-10b').innerHTML = `${G.Icons.html('ticket10', 18)} 1`;
$('cost-100').innerHTML = `${G.Icons.html('ticket', 18)} 100`;
$('cost-100b').innerHTML = `${G.Icons.html('ticket10', 18)} 10`;
$('gacha-tickets').innerHTML = `Tienes ${G.Icons.html('ticket', 18)} ${t.t1} tickets y ${G.Icons.html('ticket10', 18)} ${t.t10} tickets ×10`;
$('pull-10').disabled = !G.Gacha.costOf(s, 10, 't1');
$('pull-10b').disabled = !G.Gacha.costOf(s, 10, 't10');
$('pull-100').disabled = !G.Gacha.costOf(s, 100, 't1');
$('pull-100b').disabled = !G.Gacha.costOf(s, 100, 't10');
G.UI.refreshCoins();
}
const wait = ms => new Promise(r => setTimeout(r, ms));
async function pull(n, use = 't1') {
if (busy) return;
const res = G.Gacha.pull(G.DB.save, n, gen, use);
if (!res) { G.Audio.sfx('error'); G.UI.toast('No tienes tickets suficientes: consíguelos jugando'); return; }
G.Progress.checkAch();
G.DB.commit(true);
busy = true; skip = false;
const best = Math.max(...res.map(r => r.rarity));
const fx = $('pull-fx'), slot = $('slot'), out = $('pull-results');
out.innerHTML = '';
out.classList.toggle('many', n === 100);
$('pull-close').classList.add('hidden');
$('pull-skip').classList.remove('hidden');
slot.style.display = '';
fx.classList.remove('hidden');
// La tragaperras (ui/slots.js) y luego las cartas.
// La ×100 gira 10 veces: una por cada ×10, enseñando lo mejor de cada una.
const rank = r => (r.shiny ? 100 : 0) + r.rarity * 10 + (r.isNew ? 1 : 0);
const spins = n === 100 ? Array.from({ length: 10 }, (_, k) => res.slice(k * 10, k * 10 + 10).reduce((a, b) => (rank(b) > rank(a) ? b : a))) : res;
await G.Slots.play(slot, spins, n);
G.Audio.sfx('burst', { rarity: best });
slot.style.display = 'none';
// En la ×100, un resumen arriba y las cartas de mejor a peor.
let cards = res;
if (n === 100) {
cards = res.slice().sort((a, b) => rank(b) - rank(a));
const news = res.filter(r => r.isNew).length, sh = res.filter(r => r.shiny).length, coins = res.reduce((a, r) => a + r.refund, 0);
out.insertAdjacentHTML('beforeend', `${G.I18n.f('Nuevos: {x}', news)} · ${G.Icons.html('shiny', 14)} ${G.I18n.f('Shiny: {x}', sh)}
· +${coins.toLocaleString(G.I18n.locale)}
`);
}
for (const r of cards) {
const mon = G.DEX_BY[r.dex];
const c = document.createElement('div');
c.className = 'pcard r' + r.rarity + (r.shiny ? ' shiny' : '');
c.style.setProperty('--rc', r.shiny ? '#9ae6ff' : G.Gacha.RARITY_COLOR[r.rarity]);
c.innerHTML = `${G.UI.stars(r.rarity, 24)}
`;
c.appendChild(G.UI.spriteCanvas(r.dex, 132, 120, { lively: r.rarity >= 4 || r.shiny, scale: 2, shiny: r.shiny }));
const tag = r.isNew
? `${r.shiny ? '¡SHINY NUEVO!' : '¡NUEVO!'}`
: `Repetido · +${r.refund}`;
// Nuevo: con qué naturaleza y habilidad te ha tocado (la oculta, en dorado).
const ab = r.isNew ? G.Mons.abilityOf(r.dex) : null;
const who = ab ? `${G.Mons.name(G.Mons.natureOf(r.dex).n)} · ${G.Mons.abilityName(ab.id)}
` : '';
c.insertAdjacentHTML('beforeend', `${r.shiny ? G.Icons.html('shiny', 12) + ' ' : ''}${mon.name}${who}${tag}
`);
out.appendChild(c);
if (skip) continue;
if (n < 100 || r.rarity >= 4 || r.shiny) G.Audio.sfx('reveal', { rarity: r.rarity });
await wait(n === 100 ? 30 : n === 10 ? 150 : 0);
}
$('pull-skip').classList.add('hidden');
// La última que faltaba de la generación: su Amuleto Iris.
if (res.iris) {
const name = G.T(G.Gacha.BANNERS[res.iris - 1].name);
out.insertAdjacentHTML('beforeend', `${G.Icons.html('iris', 36)}
${G.I18n.f('¡Amuleto Iris de {x}!', name)}
${G.I18n.f('Has completado la Pokédex de {x}: en su banner, los shiny salen el doble (2 %). Lo tienes en la Mochila.', name)}
`);
G.Audio.sfx('shinyGet');
}
$('pull-close').classList.remove('hidden');
busy = false;
}
function skipNow() {
if (skip) return;
skip = true;
G.Slots.skip();
$('pull-skip').classList.add('hidden');
}
function close() {
$('pull-fx').classList.add('hidden');
refresh();
}
return { init, open, get open_() { return !$('pull-fx').classList.contains('hidden'); } };
})();