// casegen/devices.js — телефони та ноутбуки фігурантів, PIN/паролі, вміст const { chance, pick, shuffle, ri, md5 } = require('./rng'); const { genPassword } = require('./rng'); function setupDevices(ctx) { const { r, citizens, killer, herring, victim, accomplice, caseType, templateId, district, add, addPuzzle, byId, E, method, fm } = ctx; const HIST_COMMON = ['погода на тиждень', 'розклад маршруток', 'рецепт як у їдальні', 'дешеві авіаквитки', 'чому болить спина зранку', 'серіали за вечір список', 'як позбутися мурах', 'гороскоп терези']; const CHAT_COMMON = ['«Купи хліба» — «Вже йду»', '«Ти де?» — «Затримуюсь, не чекайте»', '«Завтра зміна з восьмої» — «Прийду»', '«Мама дзвонила?» — «Подзвоню ввечері»']; const histFor = who => { if (who === killer) return caseType === 'kidnap' ? pick(r, [['навісні замки які надійніші', 'скільки людина може без їжі'], ['чи пишуть камери вночі', 'зняти готівку без банкомата']]) : method.id === 'poison' ? [['барбітурати дозування', 'через скільки діє снодійне'], ['алкоголь і снодійне сумісність', 'чи виявляє розтин отруту']][ri(r, 0, 1)] : pick(r, [['чи бачать камери вночі', 'як поводитись на допиті'], ['квитки в один бік дешево', 'строк давності тяжких злочинів']]); if (who === herring) return { 'Борг': ['мікропозика без відмови', 'як швидко заробити'], 'Ревнощі': ['як повернути кохану людину', 'чому мене не помічають'], 'Сусідська війна': ['зразок скарги на сусіда', 'куди скаржитись на шум'], 'Розбите авто': ['вартість ремонту крила', 'як стягнути збитки з винуватця'] }[fm.label]; if (accomplice && who === accomplice) return ['чи можна відкликати свідчення', 'адвокат недорого консультація']; return { jealousy: ['готелі подобово поруч'], debt: ['шаблон розписки про борг'], blackmail: ['анонімна пошта створити'], witness: ['куди повідомити про крадіжку'], stalker: ['як зафіксувати переслідування'], inherit: ['зразок заповіту'], oldcrime: ['строк давності злочинів'], mistaken: ['як змінити номер телефону'], business: ['митні правила дрібні партії'], serial: ['самооборона курси'], ransom: ['страхування життя умови'], k_ransom: ['страхування життя умови'], k_obsession: ['як зафіксувати переслідування'], k_debt: ['шаблон розписки про борг'] }[templateId] || []; }; const usedDevSlots = new Set(); const devFigures = [[killer, 'k'], [herring, 'h']]; if (accomplice) devFigures.push([accomplice, 'a']); if (caseType === 'murder' && !['jealousy', 'witness', 'blackmail'].includes(templateId)) devFigures.push([victim, 'v']); for (const [who, suf] of devFigures) { const isPhone = chance(r, 0.5); const taken = sl => usedDevSlots.has(who.home + '|' + sl) || Object.values(E).some(e => e.at.type === 'unit' && e.at.unit === who.home && e.at.slot === sl); const slot = shuffle(r, district.homeSlots).find(sl => !taken(sl)) || 'комора'; usedDevSlots.add(who.home + '|' + slot); let pz; if (isPhone) { const pin = String(who.birthYear); pz = addPuzzle({ id: 'pz_dev_' + suf, kind: 'pin', hash: md5(pin), answer: pin, prompt: `Вилучений телефон (${who.name}): заблоковано 4-значним PIN.`, hint: 'Люди ставлять те, що не забудуть. Рік народження власника є в реєстрі мешканців.' }); } else { const pw = genPassword(r, who); pz = addPuzzle({ id: 'pz_dev_' + suf, kind: 'md5', hash: md5(pw.answer), answer: pw.answer, style: pw.style, prompt: `Вилучений ноутбук (${who.name}): вхід за паролем. Хеш (md5): ${md5(pw.answer)}.`, hint: (pw.personal ? 'Особистий пароль — щось про власника, латиницею (див. реєстр): ' : 'Пароль випадковий — лише словниковий перебір: ') + pw.hint }); } add({ id: 'dev_' + suf, side: 'field', at: { type: 'unit', unit: who.home, slot }, title: `${isPhone ? 'Телефон' : 'Ноутбук'}: ${who.name}`, tag: 'слід', detail: `${isPhone ? 'Телефон' : 'Ноутбук'} у житлі ${who.name}. Вилучено для лабораторії: ${isPhone ? 'заблокований PIN-кодом' : 'вхід за паролем'} — далі справа аналітика.` }); const parts = []; if (chance(r, 0.3)) { const lines = shuffle(r, [...histFor(who), ...shuffle(r, HIST_COMMON).slice(0, 3)]).slice(0, 4); parts.push('Історія браузера: ' + lines.map(l => `«${l}»`).join(', ') + '.'); } if (chance(r, 0.3)) { const mateC = who.spouse ? byId(who.spouse) : pick(r, citizens.filter(o => o !== who && o.id !== victim.id)); parts.push(`Останні чати: побут із ${mateC.name}. ` + pick(r, CHAT_COMMON) + (who === killer ? ` І одне непрочитане того вечора: «Подзвони, як будеш удома» — без відповіді.` : '')); } add({ id: 'dev_' + suf + '_data', side: 'analyst', at: { type: 'lab' }, requires: ['dev_' + suf], puzzle: pz, title: `Вміст пристрою: ${who.name}`, tag: 'слід', detail: parts.length ? parts.join(' ') : 'Чисто: банк, погода, пасьянс. Або власник обережний, або йому справді нічого ховати.' }); } } module.exports = { setupDevices };