2PlayerGame/detective-game/casegen/junk.js
2026-08-15 14:07:39 +03:00

100 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// casegen/junk.js — фейкові сліди, дрібниці, плітки
const { pick, shuffle, chance } = require('./rng');
const N = require('./names');
function setupJunk(ctx) {
const { r, citizens, victim, killer, herring, caseType, district, add, E, g, witnessFrom, dna2 } = ctx;
const nb = () => witnessFrom(citizens);
const dogOwner = citizens.find(c => c.dog && c.id !== victim.id);
const junkTexts = shuffle(r, [
{ t: 'Чек із бару', d: 'Два еспресо і коньяк, 21:47. На звороті олівцем — номер телефону без імені. Половина цифр затерта.' },
{ t: 'Стара запальничка', d: `Бензинова, з гравіюванням «${pick(r, ['С.', 'М.', 'В.', 'Т.'])}». Давно не заправлялась.` },
{ t: 'Флаєр', d: '«Ремонт взуття за годину». Усі відривні номери відірвані — популярний швець.' },
{ t: 'Порожня пляшка', d: 'З-під дешевого вина, ще липка. Поруч — два пластикові стаканчики.' },
{ t: 'Список покупок', d: 'Молоко, батарейки, свічки, скотч. Почерк квапливий.' },
{ t: 'Одна рукавичка', d: 'Шкіряна, права, добротна. Ліва, вочевидь, досі гріє чиюсь кишеню.' },
{ t: 'Прострочена квитанція', d: `Комуналка на ім'я «${nb().name}», третє нагадування. Хтось не любить платити вчасно.` },
{ t: 'Записник із адресами', d: `Половина сторінок вирвана. Серед уцілілих записів — «${nb().name}» і закреслений номер.` },
{ t: 'Білети в кіно', d: 'Два білети двомісячної давності, сеанс 21:30, місця поруч. І обгортка від шоколаду.' },
{ t: 'Іржавий ключ', d: 'Не підходить до жодних дверей поблизу. Хтось носив його «про всяк випадок» роками.' },
{ t: 'Оголошення про пса', d: dogOwner ? `«Загубився пес на кличку ${dogOwner.dog}. Винагорода.» Судячи з дати — пес давно вдома.` : '«Загубився пес. Винагорода.» Без прикмет — оптимістично.' },
{ t: 'Стос старих газет', d: 'В одній обведено оголошення про оренду квартири. Олівець аж продавив папір.' },
{ t: 'Візитка таксі', d: 'Від руки написано час: «23:15». Ні дати, ні імені.' },
{ t: 'Розбитий парасоль', d: 'І чек на новий — того ж дня, з маркету. Практична людина.' },
{ t: 'Гребінець із монограмою', d: 'Кістяний, старий, доглянутий. Літера затерта до нечитабельності.' },
{ t: 'Театральна програмка', d: 'Гастролі обласного театру, третій ряд. На полях — сердечко олівцем.' },
{ t: 'Ключниця з трьома ключами', d: 'Жоден не підписаний. Хтось досі відчиняє двері навпомацки.' },
{ t: 'Обгортка від валідолу', d: 'Свіжа. Комусь тут нещодавно було недобре — або дуже нервово.' },
{ t: 'Дитячий малюнок', d: 'Будинок, сонце і три постаті. Складений учетверо і затертий на згинах — його носили з собою.' },
{ t: 'Лотерейний білет', d: 'Не виграшний. Закреслений хрест-навхрест із таким натиском, що порвався папір.' },
{ t: 'Окуляри з тріснутим склом', d: 'Сильні діоптрії. Власник без них як без рук — але по них не повернувся.' },
{ t: 'Розклад електричок', d: 'Обведено останній нічний рейс. Двічі.' },
{ t: 'Порожній футляр від скрипки', d: 'Оксамит витертий. Або інструмент продали, або носять у ньому щось інше.' },
{ t: 'Пакетик насіння', d: 'Чорнобривці. Строк придатності вийшов три роки тому. Хтось усе не наважиться посадити.' },
{ t: 'Квитанція з ломбарду', d: `Здано «годинник чол. позолоч.». Викуп — до кінця місяця. Сума немала.` },
{ t: 'Зламана рація', d: 'Дитяча, з наліпкою блискавки. Друга з пари десь чекає на звʼязок.' },
{ t: 'Листівка без марки', d: '«Вітаю з ювілеєм!» — написано, підписано, не надіслано. Вже роки як.' },
{ t: 'Шматок пакувального шпагату', d: 'Свіжий зріз, гострим ножем. Таким перевʼязують коробки. Або щось інше.' },
{ t: 'Кишеньковий календарик', d: 'Позаминулий рік. Пʼятниці обведені. Усі до однієї.' },
{ t: 'Металева фляжка', d: 'Порожня, з увігнутим боком. Пахне не чаєм.' }
]);
const junkSpots = [];
for (const b of district.buildings) for (const sp of (b.spots || [])) junkSpots.push({ type: 'spot', building: b.id, spot: sp });
const chosen = shuffle(r, junkSpots).slice(0, 8);
const usedSlots = new Set(Object.values(E).filter(e => e.at.type === 'unit').map(e => e.at.unit + '|' + e.at.slot));
for (const [unit, n] of [[victim.home, 2], [killer.home, 1], [herring.home, 1]]) {
let placed = 0;
for (const slot of shuffle(r, district.homeSlots)) {
if (placed >= n) break;
if (usedSlots.has(unit + '|' + slot)) continue;
usedSlots.add(unit + '|' + slot); chosen.push({ type: 'unit', unit, slot }); placed++;
}
}
chosen.forEach((at, i) => {
const j = junkTexts[i % junkTexts.length];
add({ id: 'junk_' + i, side: 'field', at, title: j.t, tag: 'дрібниця', detail: j.d });
});
// Плітки
const GOSSIP = [
() => `Кажуть, ${nb().first} ${nb().last} і ${nb().first} не вітаються після сварки за паркування.`,
() => `${nb().name} нібито у великих боргах за комуналку. Але то таке, люди всяке кажуть.`,
() => `Подейкують, ${nb().name} шукає нову роботу — на старій щось не склалося.`,
() => `${nb().name} щовечора в барі. Щовечора!`,
() => `Бачили, як ${nb().name} кричить у телефон просто посеред вулиці. Аж луна йшла.`,
() => `У «Каштані» хтось заливає сусідів знизу, і всі чомусь думають на ${nb().name}.`,
() => 'На складі маркету вночі інколи світиться. Сторож каже — миші. Ага, миші з ліхтариком.',
() => `${nb().name} нібито бачили на набережній о третій ночі. Безсоння, кажуть.`,
() => 'У гуртожитку знову міняли замки — хтось утретє загубив ключі.',
() => `Подейкують, ${nb().name} збирає гроші на авто. Або на переїзд. Або на весілля. Версії різняться.`,
() => `${nb().name} виносить сміття о п'ятій ранку. Щодня. Хто взагалі стільки смітить?`,
() => { const p = nb(); return `Кажуть, ${p.name} писа${g(p, 'в', 'ла')} скарги на пів району. Пише красиво, з посиланнями на закони.`; },
() => `У ${nb().first} з ${nb().first} щось було. Або є. Або буде. Двір слідкує.`,
() => `${nb().name} тримає на балконі голубів. Сусіди знизу мають окрему думку про це.`,
() => 'На набережній вечорами хтось грає на трубі. Гарно грає. Сумно.',
() => { const p = nb(); return `${p.name} нібито ви${g(p, 'грав', 'грала')} у лотерею і мовчить. Мовчить підозріло добре.`; },
() => { const p = nb(); return `Бачили, як ${p.name} годує всіх котів району. Клички ${g(p, 'дав', 'дала')} кожному. Веде список.`; },
() => 'У клініці кажуть, що нічний охоронець спить. Охоронець каже, що медитує.',
() => { const p = nb(); return `${p.name} посвари${g(p, 'вся', 'лася')} з усім будинком через антену на даху. Війна триває.`; },
() => `Подейкують, ${nb().name} ходить до бару, але не п'є — сидить і слухає чужі розмови.`,
() => 'В автомайстерні беруть «за терміновість» удвічі. Терміново тепер усе.',
() => `${nb().name} щонеділі кудись їздить першим автобусом. Питати незручно, а цікаво.`,
() => 'Хтось ночами переставляє лавку в парку. Комунальники здалися.'
];
for (const c of citizens) {
if (c.id === victim.id) { c.gossip = []; continue; }
c.gossip = [pick(r, GOSSIP)()];
if (chance(r, 0.4)) c.gossip.push(pick(r, GOSSIP)());
}
const wit = nb();
wit.gossip.push(caseType === 'kidnap'
? `Пізно того вечора хтось у ${killer.coat} тягнув щось важке в бік проїзду. ${g(wit, 'Подумав', 'Подумала')} — килим. А тепер думаю — не килим.`
: `Пізно того вечора хтось у ${killer.coat} швидко йшов від місця, де потім знайшли тіло. Може, здалося.`);
nb().gossip.push(`У квартирі ${killer.home} останнім часом світло горить до третьої ночі. Комусь не спиться.`);
if (dna2 && dna2.why === 'brawl')
nb().gossip.push(`Удень перед тим бачили, як ${dna2.owner.name} ${g(dna2.owner, 'зчепився', 'зчепилася')} з ${victim.first} ${victim.last} у барі${dna2.reason}. Розборонили швидко, обоє розійшлися своїми ногами.`);
}
module.exports = { setupJunk };