474 lines
22 KiB
JavaScript
474 lines
22 KiB
JavaScript
/* ============================================================
|
||
* ui.js — HUD, portraits, sim panel, buy catalog, build bar,
|
||
* pie menus, toasts, career picker
|
||
* ============================================================ */
|
||
'use strict';
|
||
|
||
/* ---------------- toasts ---------------- */
|
||
function toast(msg, cls = '') {
|
||
AudioSys.sfx(cls === 'bad' ? 'error' : 'toast');
|
||
const box = document.getElementById('toasts');
|
||
const el = document.createElement('div');
|
||
el.className = 'toast ' + cls;
|
||
el.innerHTML = msg;
|
||
box.appendChild(el);
|
||
while (box.children.length > 4) box.removeChild(box.firstChild);
|
||
setTimeout(() => { el.style.opacity = '0'; el.style.transition = 'opacity .5s'; }, 4200);
|
||
setTimeout(() => el.remove(), 4800);
|
||
}
|
||
function toastBill(amount) {
|
||
AudioSys.sfx('bill');
|
||
const box = document.getElementById('toasts');
|
||
const el = document.createElement('div');
|
||
el.className = 'toast bad';
|
||
el.innerHTML = `📬 Bills due: <b>${fmtMoney(amount)}</b> <span class="envelope" id="payBillsBtn">PAY</span>`;
|
||
box.appendChild(el);
|
||
document.getElementById('payBillsBtn').onclick = () => {
|
||
if (G.funds >= amount) { G.funds -= amount; G.billsPaid = true; G.mailBillsDue = false;
|
||
toast(`✅ Bills paid: ${fmtMoney(amount)}`); el.remove(); Bus.emit('fundsChanged'); }
|
||
else toast('❌ Not enough money for the bills!', 'bad');
|
||
};
|
||
}
|
||
|
||
/* ---------------- HUD ---------------- */
|
||
function updateHud() {
|
||
document.getElementById('fundsVal').textContent = Math.floor(G.funds).toLocaleString('en-US');
|
||
const t = G.time;
|
||
document.getElementById('clockTime').textContent =
|
||
`${Math.floor(t.hour)}:${String(Math.floor((t.hourFloat % 1) * 60)).padStart(2, '0')}` +
|
||
` ${t.hour >= 12 ? 'PM' : 'AM'}`;
|
||
document.getElementById('clockDay').textContent = `${DAY_NAMES[(t.day - 1) % 7]}, Day ${t.day}`;
|
||
}
|
||
|
||
/* ---------------- portraits ---------------- */
|
||
const portraitEls = new Map();
|
||
function rebuildPortraits() {
|
||
const row = document.getElementById('portraitRow');
|
||
row.innerHTML = '';
|
||
portraitEls.clear();
|
||
for (const s of G.sims.filter(s => !s.isVisitor)) {
|
||
const d = document.createElement('div');
|
||
d.className = 'portrait' + (s.selected ? ' selected' : '');
|
||
const cv = document.createElement('canvas');
|
||
cv.width = 70; cv.height = 62;
|
||
d.appendChild(cv);
|
||
const nm = document.createElement('div'); nm.className = 'pname'; nm.textContent = s.name.split(' ')[0];
|
||
d.appendChild(nm);
|
||
const pb = document.createElement('div'); pb.className = 'plumbob'; pb.textContent = '🔷';
|
||
d.appendChild(pb);
|
||
const mb = document.createElement('div'); mb.className = 'moodbar';
|
||
const mfill = document.createElement('div'); mb.appendChild(mfill);
|
||
d.appendChild(mb);
|
||
d.onclick = () => { selectSim(s); };
|
||
row.appendChild(d);
|
||
portraitEls.set(s.id, { root:d, cv, plumbob:pb, mfill });
|
||
}
|
||
refreshPortraits();
|
||
}
|
||
function refreshPortraits() {
|
||
for (const s of G.sims.filter(s => !s.isVisitor)) {
|
||
const pe = portraitEls.get(s.id);
|
||
if (!pe) continue;
|
||
pe.root.classList.toggle('selected', !!s.selected);
|
||
const nmEl = pe.root.querySelector('.pname');
|
||
if (nmEl) nmEl.textContent = s.name.split(' ')[0] + (s.sickUntil && G.time.absMin < s.sickUntil ? ' 🤢' : '');
|
||
pe.plumbob.textContent = s.moodScore() > 60 ? '🟢' : s.moodScore() > 32 ? '🟡' : '🔴';
|
||
const m = s.moodScore();
|
||
pe.mfill.style.width = m + '%';
|
||
pe.mfill.style.background = m > 60 ? '#3ddc55' : m > 32 ? '#ffd23e' : '#ff4040';
|
||
drawSimToCanvas(pe.cv, s, { facing:0, scale: Math.min(70/90, 62/130) + .18, groundPad: 4 });
|
||
}
|
||
}
|
||
|
||
/* ---------------- sim selection ---------------- */
|
||
function selectSim(s) {
|
||
for (const o of G.sims) o.selected = false;
|
||
if (s) s.selected = true;
|
||
G.selectedSim = s;
|
||
refreshPortraits();
|
||
updateSimPanel();
|
||
}
|
||
|
||
/* ---------------- sim side panel ---------------- */
|
||
function updateSimPanel() {
|
||
const panel = document.getElementById('simPanel');
|
||
const s = G.selectedSim;
|
||
if (!s || G.mode === 'cas') { panel.classList.add('hidden'); return; }
|
||
panel.classList.remove('hidden');
|
||
document.getElementById('simPanelName').textContent = s.name + (s.isVisitor ? ' (visiting)' : '');
|
||
const m = s.moodScore();
|
||
document.getElementById('simPanelMood').textContent =
|
||
'Mood: ' + (m > 75 ? 'Elated 😄' : m > 55 ? 'Happy 🙂' : m > 35 ? 'Uneasy 😕' : m > 18 ? 'Miserable 😣' : 'Desperate 😫');
|
||
drawSimToCanvas(document.getElementById('simPortrait'), s, { facing:0, scale:1, groundPad:6 });
|
||
|
||
const body = document.getElementById('simPanelBody');
|
||
const tab = panel.dataset.tab || 'needs';
|
||
let html = '';
|
||
if (tab === 'needs') {
|
||
for (const k in NEEDS) {
|
||
const meta = NEEDS[k];
|
||
const v = clamp(s.needs[k], 0, 100);
|
||
const col = v > 55 ? '#43c15a' : v > 28 ? '#e8a33d' : '#e05252';
|
||
html += `<div class="needRow"><div class="nlabel"><span>${meta.icon} ${meta.label}</span><span>${Math.round(v)}</span></div>
|
||
<div class="nbar"><div style="width:${v}%;background:${col}"></div></div></div>`;
|
||
}
|
||
} else if (tab === 'wants') {
|
||
const asp = ASPIRATIONS[s.aspiration];
|
||
const lvl = Math.floor(G.aspirationPoints / 500);
|
||
const prog = G.aspirationPoints % 500;
|
||
html += `<div class="relCard"><div class="rname"><span>${asp.icon} ${asp.name}</span><span>Lvl ${lvl}</span></div>
|
||
<div class="nbar"><div style="width:${prog / 5}%;background:#c95ad9"></div></div>
|
||
<div style="font-size:11px;color:#9fb4ea;margin-top:4px">${prog}/500 to next level</div></div>`;
|
||
if (!s.wants || !s.wants.length) html += '<i>No whims right now…</i>';
|
||
for (const w of (s.wants || [])) {
|
||
const t = w.tpl;
|
||
const pct = t.count ? Math.round(w.progress / t.count * 100) : (t.amount ? Math.min(100, Math.round(w.bank / t.amount * 100)) : 0);
|
||
html += `<div class="relCard"><div class="rname"><span>${t.icon} ${t.label}</span><span>+${t.reward}</span></div>
|
||
${t.count || t.amount ? `<div class="relBar"><div style="width:${pct}%;background:#43c15a"></div></div>` : ''}
|
||
</div>`;
|
||
}
|
||
html += `<div style="font-size:11px;color:#9fb4ea">Whims are guided by the ${asp.name} aspiration. Fulfil them for aspiration points!</div>`;
|
||
} else if (tab === 'skills') {
|
||
for (const sk of SKILLS) {
|
||
const lvl = Math.floor(s.skills[sk.id]);
|
||
let pips = '';
|
||
for (let i = 0; i < 10; i++) pips += i < lvl ? '●' : '·';
|
||
html += `<div class="skillRow"><span>${sk.icon} ${sk.name}</span><span class="pips" style="color:#ffd23e">${pips}</span><b>${lvl}</b></div>`;
|
||
}
|
||
} else if (tab === 'rels') {
|
||
const others = G.sims.filter(o => o !== s);
|
||
if (!others.length) html += '<i>No other sims around yet.<br>Try the phone → Invite Neighbor!</i>';
|
||
for (const o of others) {
|
||
const r = s.getRel(o);
|
||
const ltrCol = r.ltr >= 50 ? '#43c15a' : r.ltr <= -25 ? '#e05252' : '#7f9fd9';
|
||
const strCol = r.str >= 40 ? '#43c15a' : r.str <= -20 ? '#e05252' : '#c9a24a';
|
||
const badge = r.ltr >= 75 ? '💞' : r.ltr >= 50 ? '🤝' : r.ltr <= -40 ? '⚔️' : '';
|
||
html += `<div class="relCard"><div class="rname"><span>${o.name} ${badge}</span><span>${Math.round(r.ltr)}</span></div>
|
||
<div class="relBar"><div style="width:${(r.ltr + 100) / 2}%;background:${ltrCol}"></div></div>
|
||
<div class="relBar"><div style="width:${(r.str + 100) / 2}%;background:${strCol}"></div></div>
|
||
</div>`;
|
||
}
|
||
} else if (tab === 'career') {
|
||
if (s.atWork) html += `<div class="careerLine">🚗 Currently <b>at work</b>.</div>`;
|
||
if (!s.job) {
|
||
html += `<div class="careerLine">❌ Unemployed.<br>Use a <b>computer</b> → Find a Job.</div>`;
|
||
} else {
|
||
const c = CAREERS.find(c => c.id === s.job.track);
|
||
const rank = c.ranks[s.job.rank];
|
||
const perf = s.job.perf ?? 50;
|
||
html += `<div class="careerLine">${c.icon} <b>${rank.title}</b><br>
|
||
${c.trackName} · Level ${s.job.rank + 1}/10<br>
|
||
💰 ${fmtMoney(rank.salary)}/day · 🕘 ${rank.hours[0]}:00–${rank.hours[1]}:00<br>
|
||
Off: ${rank.offDays.map(d => DAY_NAMES[d]).join(', ')}</div>
|
||
<div class="needRow"><div class="nlabel"><span>📈 Performance</span><span>${Math.round(perf)}</span></div>
|
||
<div class="nbar"><div style="width:${perf}%;background:${perf > 66 ? '#43c15a' : perf > 33 ? '#e8a33d' : '#e05252'}"></div></div></div>
|
||
<div class="careerLine"><b>Next level needs:</b><br>${
|
||
Object.entries(rank.req).map(([k, v]) => `${SKILLS.find(x => x.id === k)?.icon || ''} ${SKILLS.find(x => x.id === k)?.name}: ${v}`).join('<br>') || '— just keep performance up!'
|
||
}</div>`;
|
||
}
|
||
html += `<hr><div class="bioLine">⭐ Aspiration points: ${Math.round(G.aspirationPoints)}</div>`;
|
||
} else if (tab === 'bio') {
|
||
const zod = ['Capricorn','Aquarius','Pisces','Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius'][s.id % 12];
|
||
html += `<div class="bioLine">🧑 Name: <b>${s.name}</b></div>
|
||
<div class="bioLine">${s.gender === 'm' ? '👨 Male' : '👩 Female'} · ${s.ageStage === 'elder' ? '🧓 Elder' : s.ageStage === 'child' ? '🧒 Child' : s.ageStage === 'baby' ? '👶 Baby' : '🧍 Adult'}</div>
|
||
<div class="bioLine">♒ Zodiac sign: ${zod}</div>
|
||
<div class="bioLine">✨ Aspiration: <b>${ASPIRATIONS[s.aspiration].icon} ${ASPIRATIONS[s.aspiration].name}</b><br>
|
||
<small style="color:#9fb4ea">${ASPIRATIONS[s.aspiration].desc}</small></div>
|
||
<div class="bioLine" style="margin-top:8px"><b>Personality</b></div>` +
|
||
TRAITS.map(tr => {
|
||
const labels = { neat:'Neat', outgoing:'Outgoing', active:'Active', playful:'Playful', nice:'Nice' };
|
||
return `<div class="skillRow"><span>${labels[tr]}</span><span class="pips" style="color:#ffd23e">${
|
||
'●'.repeat(s.traits[tr]) + '·'.repeat(10 - s.traits[tr])}</span></div>`;
|
||
}).join('');
|
||
// life milestones diary
|
||
const mem = (s.memories || []).slice(0, 8);
|
||
html += `<div class="bioLine" style="margin-top:8px"><b>📜 Memories</b></div>` +
|
||
(mem.length
|
||
? `<div style="max-height:130px;overflow:auto">` + mem.map(m =>
|
||
`<div class="bioLine">Day ${m.day} ${m.icon} ${m.text}</div>`).join('') + `</div>`
|
||
: `<div class="bioLine" style="color:#9aa">No memories yet — go live a little!</div>`);
|
||
if (s.novelChapters > 0) html += `<div class="bioLine">✍️ Writing a novel — chapter ${s.novelChapters}/10</div>`;
|
||
if ((s.paintings || []).length) html += `<div class="bioLine">🖼️ ${s.paintings.length} painting(s) ready to sell</div>`;
|
||
}
|
||
body.innerHTML = html;
|
||
}
|
||
|
||
/* ---------------- career chance cards ---------------- */
|
||
Bus.on('chanceCard', () => {
|
||
const p = G.pendingChance; if (!p) return;
|
||
const sim = G.simById(p.simId);
|
||
if (!sim || !sim.job) { G.pendingChance = null; setSpeed(1); return; }
|
||
const card = p.card;
|
||
let el = document.getElementById('chanceCard');
|
||
if (!el) { el = document.createElement('div'); el.id = 'chanceCard'; document.body.appendChild(el); }
|
||
const cname = (CAREERS.find(c => c.id === sim.job.track) || {}).name || 'Work';
|
||
el.innerHTML = `<div class="cc-box">
|
||
<div class="cc-head">💼 Career Opportunity — ${cname}</div>
|
||
<div class="cc-q">${card.q}</div>
|
||
<div class="cc-btns">${card.a.map((a, i) =>
|
||
`<button data-i="${i}">${a.icon || ''} ${a.label}</button>`).join('')}</div>
|
||
<div class="cc-sub">Time is paused while ${sim.name.split(' ')[0]} decides…</div>
|
||
</div>`;
|
||
el.classList.remove('hidden');
|
||
el.querySelectorAll('button').forEach(b => b.onclick = () => {
|
||
const a = card.a[+b.dataset.i];
|
||
if (a.fx && a.fx.dice != null) {
|
||
const win = chance(a.fx.dice);
|
||
applyChanceFx(sim, win ? a.fx.win : a.fx.lose);
|
||
toast(win ? `🎯 Bold move! It paid off for ${sim.name}.` : `😬 That backfired on ${sim.name}…`, win ? 'good' : 'bad');
|
||
} else {
|
||
applyChanceFx(sim, a.fx || {});
|
||
toast(`${a.icon || ''} ${sim.name.split(' ')[0]} chose: ${a.label}`, '');
|
||
}
|
||
AudioSys.sfx('click');
|
||
el.classList.add('hidden');
|
||
G.pendingChance = null;
|
||
setSpeed(G.prevSpeedBeforeCard || 1);
|
||
});
|
||
});
|
||
|
||
/* tab clicks */
|
||
document.querySelectorAll('#simPanelTabs button').forEach(b => {
|
||
b.onclick = () => {
|
||
document.querySelectorAll('#simPanelTabs button').forEach(x => x.classList.remove('active'));
|
||
b.classList.add('active');
|
||
document.getElementById('simPanel').dataset.tab = b.dataset.tab;
|
||
updateSimPanel();
|
||
};
|
||
});
|
||
document.getElementById('simPanelClose').onclick = () => document.getElementById('simPanel').classList.add('hidden');
|
||
|
||
/* ---------------- BUY drawer ---------------- */
|
||
let buyThumbCache = new Map();
|
||
function thumbFor(defId) {
|
||
if (buyThumbCache.has(defId)) return buyThumbCache.get(defId).cloneNode ?
|
||
(() => { const c = document.createElement('canvas'); c.width = 84; c.height = 64;
|
||
c.getContext('2d').drawImage(buyThumbCache.get(defId), 0, 0); return c; })() : null;
|
||
const src = document.createElement('canvas'); src.width = 168; src.height = 128;
|
||
const c = src.getContext('2d');
|
||
const def = OBJECTS[defId];
|
||
c.save(); c.translate(84, 96); c.scale(.95, .95);
|
||
const fake = { id: 3, defId, x:0, y:0, rot:0, w:def.w, h:def.h, dirty:.2, usedBy:false };
|
||
const painter = PAINTERS[def.shape];
|
||
// emulate drawObject's local space
|
||
if (painter) painter(c, fake, 1.0);
|
||
else { c.fillStyle = '#caa'; c.fillRect(-14, -30, 28, 30); }
|
||
c.restore();
|
||
buyThumbCache.set(defId, src);
|
||
const out = document.createElement('canvas'); out.width = 84; out.height = 64;
|
||
out.getContext('2d').drawImage(src, 0, 0, 168, 128, 0, 0, 84, 64);
|
||
return out;
|
||
}
|
||
function openBuyDrawer() {
|
||
const drawer = document.getElementById('buyDrawer');
|
||
drawer.classList.remove('hidden');
|
||
const tabs = document.getElementById('buyTabs');
|
||
tabs.innerHTML = '';
|
||
let curCat = drawer.dataset.cat || 'seating';
|
||
for (const cat of BUY_CATS) {
|
||
const b = document.createElement('button');
|
||
b.textContent = cat.icon + ' ' + cat.label;
|
||
b.className = cat.id === curCat ? 'active' : '';
|
||
b.onclick = () => { drawer.dataset.cat = cat.id; openBuyDrawer(); };
|
||
tabs.appendChild(b);
|
||
}
|
||
const grid = document.getElementById('buyGrid');
|
||
grid.innerHTML = '';
|
||
for (const id in OBJECTS) {
|
||
const def = OBJECTS[id];
|
||
if (def.cat !== curCat) continue;
|
||
const card = document.createElement('div');
|
||
card.className = 'buyItem' + (G.buySel === id ? ' sel' : '');
|
||
card.appendChild(thumbFor(id));
|
||
const bn = document.createElement('div'); bn.className = 'bn'; bn.textContent = def.name;
|
||
const bp = document.createElement('div'); bp.className = 'bp'; bp.textContent = fmtMoney(def.price);
|
||
card.appendChild(bn); card.appendChild(bp);
|
||
card.onclick = () => { G.buySel = id; G.buyRot = 0; openBuyDrawer(); setHint(`Placing ${def.name} (${fmtMoney(def.price)}) — click a tile · R rotate · Esc cancel`); };
|
||
grid.appendChild(card);
|
||
}
|
||
}
|
||
function closeBuyDrawer() {
|
||
document.getElementById('buyDrawer').classList.add('hidden');
|
||
}
|
||
function setHint(txt) { document.getElementById('buyHint').innerHTML = txt; }
|
||
|
||
/* ---------------- BUILD bar ---------------- */
|
||
function openBuildBar() {
|
||
document.getElementById('buildBar').classList.remove('hidden');
|
||
const sw = document.getElementById('floorSwatches');
|
||
if (!sw.children.length) {
|
||
FLOORS.forEach((f, i) => {
|
||
if (f.outdoor) return;
|
||
const s = document.createElement('div');
|
||
s.className = 'swatch' + (i === G.floorSel ? ' sel' : '');
|
||
s.style.background = f.c1;
|
||
s.title = f.id;
|
||
s.onclick = () => { G.floorSel = i; openBuildBar(); };
|
||
sw.appendChild(s);
|
||
});
|
||
} else {
|
||
[...sw.children].forEach((el, i) => el.classList.toggle('sel', i === G.floorSel));
|
||
}
|
||
// wall color swatches
|
||
const ws = document.getElementById('wallSwatches');
|
||
if (!ws.children.length) {
|
||
const lbl = document.createElement('span');
|
||
lbl.style.cssText = 'font-size:11px;color:#9fb4ea;margin:0 2px;';
|
||
lbl.textContent = '🧱';
|
||
ws.appendChild(lbl);
|
||
WALL_COLORS.forEach((c, i) => {
|
||
const s = document.createElement('div');
|
||
s.className = 'swatch' + (c === G.wallColor ? ' sel' : '');
|
||
s.style.background = c;
|
||
s.onclick = () => { G.wallColor = c; openBuildBar(); };
|
||
ws.appendChild(s);
|
||
});
|
||
} else {
|
||
let ci = 0;
|
||
[...ws.children].forEach(el => {
|
||
if (!el.style.background) return; // label
|
||
el.classList.toggle('sel', WALL_COLORS[ci] === G.wallColor);
|
||
ci++;
|
||
});
|
||
}
|
||
document.querySelectorAll('#buildBar [data-tool]').forEach(b =>
|
||
b.classList.toggle('active', b.dataset.tool === G.buildTool));
|
||
document.getElementById('buildHint').innerHTML =
|
||
`Wall §70/segment · Door §250 · Window §180 · Floor §12/tile · Removing refunds 50% — <b>${{wall:'Drag across edges to build',door:'Click a wall segment',window:'Click a wall segment',floor:'Drag to paint floor',delWall:'Click walls/doors/windows to remove'}[G.buildTool]||''}</b>`;
|
||
}
|
||
function closeBuildBar() { document.getElementById('buildBar').classList.add('hidden'); }
|
||
|
||
/* build tool buttons */
|
||
document.querySelectorAll('#buildBar [data-tool]').forEach(b => {
|
||
b.onclick = () => { G.buildTool = b.dataset.tool; openBuildBar(); };
|
||
});
|
||
|
||
/* ---------------- PIE MENU ---------------- */
|
||
function showPie(px, py, entries, title = '') {
|
||
const pie = document.getElementById('pieMenu');
|
||
pie.innerHTML = '';
|
||
if (title) {
|
||
const t = document.createElement('div');
|
||
t.style.cssText = 'padding:4px 12px;font-weight:800;color:#ffd23e;font-size:13px;';
|
||
t.textContent = title;
|
||
pie.appendChild(t);
|
||
pie.appendChild(document.createElement('hr'));
|
||
}
|
||
for (const en of entries) {
|
||
if (en === '-') { pie.appendChild(document.createElement('hr')); continue; }
|
||
const d = document.createElement('div');
|
||
d.className = 'pi' + (en.disabled ? ' dis' : '');
|
||
d.innerHTML = `<span>${en.icon || ''}</span><span>${en.label}</span>` +
|
||
(en.price != null ? `<span class="price">${en.price < 0 ? '+' : ''}${fmtMoney(Math.abs(en.price)).slice(0)}</span>` : '');
|
||
if (!en.disabled) d.onclick = () => { hidePie(); AudioSys.sfx('click'); en.fn(); };
|
||
pie.appendChild(d);
|
||
}
|
||
pie.classList.remove('hidden');
|
||
// keep on-screen
|
||
const r = pie.getBoundingClientRect();
|
||
pie.style.left = clamp(px, 6, window.innerWidth - r.width - 8) + 'px';
|
||
pie.style.top = clamp(py, 6, window.innerHeight - r.height - 8) + 'px';
|
||
}
|
||
function hidePie() { document.getElementById('pieMenu').classList.add('hidden'); }
|
||
window.addEventListener('mousedown', (e) => {
|
||
const pie = document.getElementById('pieMenu');
|
||
if (!pie.classList.contains('hidden') && !pie.contains(e.target)) hidePie();
|
||
});
|
||
|
||
/* ---------------- interactions pie for an object ---------------- */
|
||
function objectInteractions(obj) {
|
||
const def = OBJECTS[obj.defId];
|
||
const entries = [];
|
||
if (obj.broken) {
|
||
entries.push({
|
||
label: 'Repair', icon: '🔧',
|
||
disabled: (G.selectedSim?.skills.mechanical || 0) < 1,
|
||
fn: () => {
|
||
const s = G.selectedSim;
|
||
if (!s || !s.atHome) { toast('Pick a sim at home first!', 'bad'); return; }
|
||
const mech = s.skills.mechanical || 0;
|
||
commandUse(s, obj, { id:'repair', label:'Repair', icon:'🔧', special:'repair',
|
||
pose:'stand', dur: Math.max(14, 50 - mech * 4) });
|
||
},
|
||
});
|
||
return entries;
|
||
}
|
||
for (const inter of def.interactions || []) {
|
||
if (inter.requiresDirty && obj.dirty <= .2) continue;
|
||
if (inter.requiresFull && obj.dirty < .5) continue;
|
||
if (inter.special === 'findJob' && G.selectedSim?.job) continue;
|
||
if (inter.special === 'tryBaby' && !canTryForBaby(G.selectedSim)) continue;
|
||
if (inter.babyOnly && G.selectedSim?.ageStage !== 'baby') continue;
|
||
if (inter.childOnly && G.selectedSim?.ageStage !== 'child') continue;
|
||
entries.push({
|
||
label: inter.label, icon: inter.icon,
|
||
disabled: !!(inter.cost && G.funds < inter.cost),
|
||
fn: () => {
|
||
const s = G.selectedSim;
|
||
if (!s || !s.atHome) { toast('Pick a sim at home first!', 'bad'); return; }
|
||
commandUse(s, obj, inter);
|
||
},
|
||
});
|
||
}
|
||
// sinks grow a Wash Dishes action when there are dirty dishes around
|
||
if (obj.defId === 'sink' && dishTotal() > 0) {
|
||
entries.push({
|
||
label: 'Wash Dishes', icon: '🧼',
|
||
fn: () => {
|
||
const s = G.selectedSim;
|
||
if (!s || !s.atHome) { toast('Pick a sim at home first!', 'bad'); return; }
|
||
commandUse(s, obj, { id:'wash', label:'Wash Dishes', icon:'🧼', special:'wash', pose:'stand', dur:40 });
|
||
},
|
||
});
|
||
}
|
||
return entries;
|
||
}
|
||
let keyIsShift = false;
|
||
|
||
/* ---------------- career picker (computer) ---------------- */
|
||
function jobMenuOpenFor(sim) { return G._jobMenuSim === sim && !document.getElementById('pieMenu').classList.contains('hidden'); }
|
||
function openJobPicker(sim, px, py) {
|
||
G._jobMenuSim = sim;
|
||
const entries = [];
|
||
for (const c of CAREERS) {
|
||
const r0 = c.ranks[0];
|
||
entries.push({
|
||
label: `${c.trackName} — ${r0.title}`, icon: c.icon,
|
||
fn: () => {
|
||
G._jobMenuSim = null;
|
||
CareerSys.hire(sim, c.id);
|
||
if (sim.action?.special === 'findJob') sim.action.finish();
|
||
updateSimPanel();
|
||
},
|
||
});
|
||
}
|
||
entries.push('-', { label: 'Never mind', icon: '↩️', fn: () => {
|
||
G._jobMenuSim = null;
|
||
if (sim.action?.special === 'findJob') sim.action.finish();
|
||
}});
|
||
showPie(px, py, entries, '📋 Choose a career track');
|
||
}
|
||
|
||
/* ---------------- help / mute ---------------- */
|
||
document.getElementById('btnHelp').onclick = () =>
|
||
document.getElementById('helpOverlay').classList.remove('hidden');
|
||
document.getElementById('helpClose').onclick = () =>
|
||
document.getElementById('helpOverlay').classList.add('hidden');
|
||
document.getElementById('btnHood').onclick = function () {
|
||
if (typeof enterHood === 'function') G.mode === 'hood' ? exitHood() : enterHood();
|
||
};
|
||
document.getElementById('btnMute').onclick = function () { AudioSys.muted = !AudioSys.muted;
|
||
this.classList.toggle('active', !AudioSys.muted);
|
||
this.textContent = AudioSys.muted ? '🔇' : '🔊';
|
||
};
|
||
|
||
/* ---------------- speed buttons ---------------- */
|
||
document.querySelectorAll('.speed-btn').forEach(b => {
|
||
b.onclick = () => setSpeed(+b.dataset.speed);
|
||
});
|
||
function setSpeed(v) {
|
||
G.speed = v;
|
||
document.querySelectorAll('.speed-btn').forEach(b =>
|
||
b.classList.toggle('active', +b.dataset.speed === v));
|
||
}
|