/**
 * GameplayKits — каталог готовых механик для Toolbox (задача 17, фаза T2).
 *
 * Каждый kit — это готовый кусок поведения, который автор вставляет одним кликом
 * из Тулбокса (вкладка «Готовые механики»). При вставке:
 *   - scripts с attachTo:'global'    → добавляются как глобальный скрипт игры;
 *   - scripts с attachTo:'on-target' → создаётся примитив-маркер + скрипт на нём;
 *   - prims[]                        → создаются примитивы на сцене (визуал кита).
 *
 * Все киты написаны НАМИ на белом-листе game-API (ScriptSandboxWorker) →
 * заведомо безопасны, исполняются в существующем sandbox (нет доступа к DOM/fetch).
 *
 * Фича-парность: тот же файл копируется в rublox-player/src/engine/ (киты — это
 * данные-скрипты, исполняются движком плеера так же).
 */

export const KIT_CATEGORIES = [
    { id: 'all', label: 'Все' },
    { id: 'movement', label: 'Движение' },
    { id: 'world', label: 'Мир' },
    { id: 'ui', label: 'Интерфейс' },
    { id: 'fx', label: 'Эффекты' },
    { id: 'npc', label: 'NPC и бой' },
    { id: 'economy', label: 'Экономика' },
];

export const GAMEPLAY_KITS = [
    {
        id: 'shift-to-run',
        name: 'Бег на Shift',
        desc: 'Игрок ускоряется в 1.8× при удержании Shift и возвращается к обычной скорости при отпускании.',
        icon: 'zap', category: 'movement',
        scripts: [{ attachTo: 'global', code:
`// Бег на Shift
game.onKey('shift', () => game.player.setSpeed(1.8));
game.onKeyUp('shift', () => game.player.setSpeed(1.0));` }],
    },
    {
        id: 'double-jump',
        name: 'Двойной прыжок',
        desc: 'Разрешает второй прыжок прямо в воздухе. Нажми Space ещё раз во время прыжка.',
        icon: 'arrow-up', category: 'movement',
        scripts: [{ attachTo: 'global', code:
`// Двойной прыжок: второй прыжок в воздухе по Space
game.player.setDoubleJump(true);
game.ui.set('dj', 'Двойной прыжок включён! Жми Space в воздухе.', { x: 50, y: 90, anchor: 'bottom', color: '#fff', size: 16 });
game.after(5, () => game.ui.set('dj', ''));` }],
    },
    {
        id: 'day-night-cycle',
        name: 'Смена дня и ночи',
        desc: 'Небо плавно переключается день → закат → ночь → день по кругу (использует Skybox задачи 16).',
        icon: 'cloud', category: 'world',
        scripts: [{ attachTo: 'global', code:
`// Авто-цикл дня и ночи
const phases = ['clear-summer-day', 'sunset', 'starry-night', 'clear-summer-day'];
let i = 0;
game.scene.setSkybox({ preset: phases[0] });
game.every(8, () => {
  i = (i + 1) % phases.length;
  game.scene.skybox.fadeTo({ preset: phases[i] }, 3);
});` }],
    },
    {
        id: 'currency-counter',
        name: 'Счётчик монет',
        desc: 'Счётчик монет в углу HUD. Другие механики шлют game.broadcast("coins", {add: N}) — счётчик обновляется.',
        icon: 'circle', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Счётчик монет в HUD. Прибавить монеты из любого скрипта:
//   game.broadcast('coins', { add: 100 });
let coins = 0;
function show() { game.ui.set('coins', '🪙 ' + coins, { x: 92, y: 6, anchor: 'top', color: '#ffd23a', size: 22 }); }
show();
game.onMessage('coins', (m) => { coins += (m && m.add) ? m.add : 1; show(); });` }],
    },
    {
        id: 'start-pad',
        name: 'Стартовая площадка',
        desc: 'Светящаяся платформа — игрок появляется НА ней в начале игры (задаёт точку старта).',
        icon: 'flag', category: 'world',
        prims: [{ type: 'cylinder', x: 0, y: 0.15, z: 0, sx: 3, sy: 0.3, sz: 3, color: '#36d57a', material: 'neon', name: 'Стартовая площадка' }],
        scripts: [{ attachTo: 'on-target', code:
`// Игрок появляется на этой площадке в начале игры.
// Небольшая задержка — чтобы позиция объекта и игрок успели проинициализироваться.
game.after(0.1, () => {
  const p = game.self.position;
  game.player.teleport(p.x, p.y + 1.5, p.z);
});` }],
    },
    {
        id: 'checkpoint',
        name: 'Чекпоинт',
        desc: 'Светящийся столб-чекпоинт. При касании сохраняет прогресс и показывает уведомление.',
        icon: 'flag', category: 'world',
        prims: [{ type: 'cylinder', x: 0, y: 1.5, z: 0, sx: 0.6, sy: 3, sz: 0.6, color: '#4d6bff', material: 'neon', name: 'Чекпоинт' }],
        scripts: [{ attachTo: 'on-target', code:
`// Чекпоинт: касание → сообщение
game.self.onInteract(() => {
  game.ui.set('cp', '✓ Чекпоинт сохранён!', { x: 50, y: 85, anchor: 'bottom', color: '#36d57a', size: 18 });
  game.after(2, () => game.ui.set('cp', ''));
}, { text: 'Активировать', key: 'f', distance: 4 });` }],
    },
    {
        id: 'confetti',
        name: 'Конфетти',
        desc: 'Праздничный фонтан конфетти из этого объекта. Кубики разлетаются и падают. Запускается периодически.',
        icon: 'sparkles', category: 'fx',
        prims: [{ type: 'sphere', x: 0, y: 1, z: 0, sx: 0.5, sy: 0.5, sz: 0.5, color: '#ff5ab0', material: 'neon', name: 'Конфетти-источник', canCollide: false }],
        scripts: [{ attachTo: 'on-target', code:
`// Конфетти вылетает из ПОЗИЦИИ этого объекта (не из центра сцены).
function burst() {
  const p = game.self.position;          // где стоит конфетти-источник
  for (let k = 0; k < 16; k++) {
    const col = ['#ff5ab0','#ffd23a','#4d6bff','#36d57a','#ff7a3a'][k % 5];
    game.scene.spawn('primitive:cube', {
      x: p.x + (Math.random()-0.5)*0.6,
      y: p.y + 0.5,
      z: p.z + (Math.random()-0.5)*0.6,
      sx: 0.22, sy: 0.22, sz: 0.22, color: col,
      anchored: false, canCollide: false, lifetime: 2.5,
    });
  }
}
burst();
game.every(3, burst);` }],
    },
    {
        id: 'floating-platform',
        name: 'Парящая платформа',
        desc: 'Платформа, которая плавно качается вверх-вниз — для паркура.',
        icon: 'square', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 2, z: 0, sx: 4, sy: 0.5, sz: 4, color: '#c8a86a', material: 'matte', name: 'Парящая платформа' }],
        scripts: [{ attachTo: 'on-target', code:
`// Качание платформы вверх-вниз
let t = 0; const baseY = 2;
game.onTick((dt) => {
  t += dt;
  game.self.move(game.self.position.x, baseY + Math.sin(t * 1.5) * 1.2, game.self.position.z);
});` }],
    },
    {
        id: 'rotating-trap',
        name: 'Вращающийся объект',
        desc: 'Объект, который постоянно вращается — препятствие или декор.',
        icon: 'refresh', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 1.5, z: 0, sx: 5, sy: 0.4, sz: 0.6, color: '#e0483c', material: 'matte', name: 'Вертушка' }],
        scripts: [{ attachTo: 'on-target', code:
`// Постоянное вращение
let a = 0;
game.onTick((dt) => { a += dt * 1.5; game.self.rotate(a); });` }],
    },
    {
        id: 'timer-hud',
        name: 'Таймер забега',
        desc: 'Секундомер в HUD — считает время с начала игры. Основа для гонок на время.',
        icon: 'clock', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Таймер забега
let t = 0;
game.every(0.1, () => {
  t += 0.1;
  game.ui.set('timer', '⏱ ' + t.toFixed(1) + ' c', { x: 50, y: 6, anchor: 'top', color: '#ffffff', size: 22 });
});` }],
    },
    {
        id: 'welcome-message',
        name: 'Приветствие',
        desc: 'Показывает приветственное сообщение при входе в игру и убирает через 5 секунд.',
        icon: 'message', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Приветствие
game.ui.set('welcome', '👋 Добро пожаловать в игру!', { x: 50, y: 40, anchor: 'center', color: '#ffffff', size: 30 });
game.after(5, () => game.ui.set('welcome', ''));` }],
    },
    {
        id: 'loot-crate',
        name: 'Сундук с лутом',
        desc: 'Золотой сундук. При взаимодействии «открывается» — даёт награду и сообщение.',
        icon: 'box', category: 'world',
        prims: [
            { type: 'cube', x: 0, y: 0.6, z: 0, sx: 1.6, sy: 1.2, sz: 1.2, color: '#b5862e', material: 'metal', name: 'Сундук' },
            { type: 'cube', x: 0, y: 1.35, z: 0, sx: 1.7, sy: 0.4, sz: 1.3, color: '#d4a843', material: 'metal', name: 'Крышка сундука', canCollide: false },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Сундук с лутом — даёт 100 монет (через счётчик монет, если он добавлен).
let opened = false;
game.self.onInteract(() => {
  if (opened) { game.ui.set('loot', 'Сундук уже открыт.', { x: 50, y: 85, anchor: 'bottom', color: '#bbb', size: 16 }); return; }
  opened = true;
  game.broadcast('coins', { add: 100 });   // обновит «Счётчик монет», если он есть
  game.ui.set('loot', '🎁 Ты получил награду: +100 монет!', { x: 50, y: 85, anchor: 'bottom', color: '#ffd23a', size: 18 });
  game.after(3, () => game.ui.set('loot', ''));
}, { text: 'Открыть сундук', key: 'f', distance: 4 });` }],
    },

    // ===== Партия 1 из Вики (киты 13-17) =====

    {
        id: 'trampoline',
        name: 'Батут (пружина)',
        desc: 'Яркая платформа-батут — наступи на неё, и игрока подбросит высоко вверх. (Вики: «Прыжок-пружина»)',
        icon: 'arrow-up', category: 'movement',
        prims: [{ type: 'cylinder', x: 0, y: 0.3, z: 0, sx: 3, sy: 0.6, sz: 3, color: '#ff3c8e', material: 'neon', name: 'Батут' }],
        scripts: [{ attachTo: 'on-target', code:
`// Батут: касание → подброс игрока вверх.
game.self.onTouch(() => {
  game.player.setVy(20);            // вертикальный импульс (как трамплин)
});` }],
    },
    {
        id: 'speed-pad',
        name: 'Лента ускорения',
        desc: 'Жёлтая плита-ускоритель — наступи, и игрок бежит быстрее несколько секунд. (Вики: «бусты скорости»)',
        icon: 'zap', category: 'movement',
        prims: [{ type: 'cube', x: 0, y: 0.1, z: 0, sx: 3, sy: 0.2, sz: 5, color: '#ffd23a', material: 'neon', name: 'Лента ускорения', canCollide: false }],
        scripts: [{ attachTo: 'on-target', code:
`// Лента ускорения: касание → x2 скорости на 3 секунды.
let boosting = false;
game.self.onTouch(() => {
  if (boosting) return;
  boosting = true;
  game.player.setSpeed(2.0);
  game.ui.set('boost', '⚡ Ускорение!', { x: 50, y: 80, anchor: 'bottom', color: '#ffd23a', size: 18 });
  game.after(3, () => { game.player.setSpeed(1.0); game.ui.set('boost', ''); boosting = false; });
});` }],
    },
    {
        id: 'teleport-portal',
        name: 'Портал-телепорт',
        desc: 'Два синих портала. Вошёл в портал — мгновенно переносишься ко второму. Поставь второй портал где нужно. (Вики: «секретный путь»)',
        icon: 'sparkles', category: 'movement',
        prims: [
            { type: 'cylinder', x: 0, y: 1.5, z: 0, sx: 0.4, sy: 3, sz: 3, color: '#4d6bff', material: 'neon', name: 'Портал A' },
            { type: 'cylinder', x: 8, y: 1.5, z: 0, sx: 0.4, sy: 3, sz: 3, color: '#4dffd6', material: 'neon', name: 'Портал B', canCollide: false },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Портал A: касание → телепорт к «Портал B» (ищем его по имени в момент входа).
// Передвигай «Портал B» куда угодно — телепорт всегда попадёт к нему.
let cd = false;
game.self.onTouch(() => {
  if (cd) return;
  const b = game.scene.findOne('Портал B');   // ищем второй портал
  if (!b || !b.position) return;
  cd = true;
  game.player.teleport(b.position.x, b.position.y + 1, b.position.z);
  game.after(1.2, () => { cd = false; });      // защита от повторного входа
});` }],
    },
    {
        id: 'disappearing-platform',
        name: 'Исчезающая платформа',
        desc: 'Платформа пропадает под ногами через секунду после касания и возвращается через 3с. (Вики: «Не упади», «Падающий мост»)',
        icon: 'square', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 0.25, z: 0, sx: 4, sy: 0.5, sz: 4, color: '#e06a3c', material: 'matte', name: 'Исчезающая платформа' }],
        scripts: [{ attachTo: 'on-target', code:
`// Исчезающая платформа: наступил → через 1с пропадает, через 3с возвращается.
let busy = false;
game.self.onTouch(() => {
  if (busy) return; busy = true;
  game.after(1, () => { game.self.setVisible(false); game.self.setCollide(false); });
  game.after(3, () => { game.self.setVisible(true); game.self.setCollide(true); busy = false; });
});` }],
    },
    {
        id: 'door-button',
        name: 'Дверь по кнопке E',
        desc: 'Красивая дверь с рамкой, филёнками и ручкой. Нажми E — плавно распахивается вокруг петли. (Вики: «Кнопка-открывашка»)',
        icon: 'door', category: 'world',
        // Полотно двери — ПЕРВЫЙ prim (на нём скрипт). Остальные части — рамка
        // (неподвижный косяк) + декор полотна. Всё уходит в одну папку.
        prims: [
            // 0) Полотно двери (тёмное дерево).
            { type: 'cube', x: 0, y: 2, z: 0, sx: 0.25, sy: 3.8, sz: 2.6, color: '#6b4423', material: 'matte', name: 'Полотно двери' },
            // Филёнки (светлее, чуть выступают) — верхняя и нижняя.
            { type: 'cube', x: 0.16, y: 2.9, z: 0, sx: 0.08, sy: 1.2, sz: 1.9, color: '#8a5a2e', material: 'matte', canCollide: false, name: 'Филёнка верх' },
            { type: 'cube', x: 0.16, y: 1.2, z: 0, sx: 0.08, sy: 1.4, sz: 1.9, color: '#8a5a2e', material: 'matte', canCollide: false, name: 'Филёнка низ' },
            // Ручка (золотая).
            { type: 'sphere', x: 0.28, y: 2, z: 0.95, sx: 0.3, sy: 0.3, sz: 0.3, color: '#e0b030', material: 'metal', canCollide: false, name: 'Ручка' },
            // Косяк-рамка (неподвижная) — две стойки + перемычка.
            { type: 'cube', x: 0, y: 2, z: -1.55, sx: 0.4, sy: 4.2, sz: 0.4, color: '#4a3018', material: 'matte', name: 'Косяк левый' },
            { type: 'cube', x: 0, y: 2, z: 1.55, sx: 0.4, sy: 4.2, sz: 0.4, color: '#4a3018', material: 'matte', name: 'Косяк правый' },
            { type: 'cube', x: 0, y: 4.1, z: 0, sx: 0.4, sy: 0.4, sz: 3.5, color: '#4a3018', material: 'matte', name: 'Перемычка' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Дверь по E: ПЛАВНО поворачивается вокруг петли (левой грани).
// Скрипт на полотне двери. Филёнки/ручка двигаются вместе как часть полотна?
// Нет — они отдельные примитивы, поэтому анимируем только полотно (game.self),
// а декор оставляем — на низкой толщине двери смотрится цельно.
const p0 = game.self.position;        // центр полотна (закрытое положение)
const halfW = 1.3;                    // половина ширины полотна по Z (sz=2.6)
const hingeX = p0.x;
const hingeZ = p0.z - halfW;          // петля у левого края
let open = false;
let cur = 0, target = 0;              // текущий и целевой угол (радианы)
const SPEED = Math.PI;               // рад/сек → ~0.5с на 90°

// Декор полотна — двигаем вместе с дверью. Запоминаем их СМЕЩЕНИЕ относительно
// центра полотна (в закрытом виде), чтобы вращать вокруг той же петли.
const decorNames = ['Филёнка верх', 'Филёнка низ', 'Ручка'];
const decor = [];
for (const nm of decorNames) {
  const o = game.scene.findOne(nm);
  if (o && o.position) {
    decor.push({ obj: o, dx: o.position.x - p0.x, dy: o.position.y - p0.y, dz: o.position.z - p0.z });
  }
}

// Поворот локального вектора (lx,lz) вокруг оси Y на angle — согласованно с
// тем, как Babylon поворачивает меш при rotation.y=angle (левосторонняя СК).
function rotY(lx, lz, a) {
  const s = Math.sin(a), c = Math.cos(a);
  return { x: lx * c + lz * s, z: -lx * s + lz * c };
}
function place(angle) {
  // Полотно: центр = петля + повёрнутый локальный вектор (0, +halfW).
  const pc = rotY(0, halfW, angle);
  const cx = hingeX + pc.x;
  const cz = hingeZ + pc.z;
  game.self.move(cx, p0.y, cz);
  game.self.rotate(angle);
  // Декор: центр полотна + повёрнутое локальное смещение (той же формулой).
  for (const d of decor) {
    const r = rotY(d.dx, d.dz, angle);
    d.obj.move(cx + r.x, p0.y + d.dy, cz + r.z);
    if (d.obj.rotate) d.obj.rotate(angle);
  }
}

// Один постоянный тик плавно ведёт cur → target.
game.onTick((dt) => {
  if (cur === target) return;
  const step = SPEED * dt;
  if (Math.abs(target - cur) <= step) cur = target;
  else cur += Math.sign(target - cur) * step;
  place(cur);
});
game.self.onInteract(() => {
  open = !open;
  target = open ? Math.PI / 2 : 0;   // 90° открыта / 0° закрыта
}, { text: 'Открыть / закрыть', key: 'e', distance: 5 });` }],
    },

    // ===== Партия 2 из Вики (киты 18-22) =====

    {
        id: 'color-tiles',
        name: 'Цветная плитка',
        desc: 'Наступи на плитку — она меняет цвет на случайный. (Вики: «Цветные плитки»)',
        icon: 'palette', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 0.1, z: 0, sx: 2.5, sy: 0.2, sz: 2.5, color: '#cfd8dc', material: 'matte', name: 'Цветная плитка' }],
        scripts: [{ attachTo: 'on-target', code:
`// Плитка меняет цвет при касании.
const colors = ['#ff5ab0','#ffd23a','#4d6bff','#36d57a','#ff7a3a','#a05aff'];
let i = 0;
game.self.onTouch(() => {
  i = (i + 1) % colors.length;
  game.self.setColor(colors[i]);
});` }],
    },
    {
        id: 'lava-floor',
        name: 'Лава (урон по касанию)',
        desc: 'Раскалённая плита: наступишь — теряешь здоровье каждую секунду, пока стоишь. (Вики: «Лава-пол»)',
        icon: 'lava', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 0.1, z: 0, sx: 5, sy: 0.2, sz: 5, color: '#ff4422', material: 'neon', name: 'Лава' }],
        scripts: [{ attachTo: 'on-target', code:
`// Лава: пока игрок на плите — урон каждую секунду.
let onLava = false, timer = null;
game.self.onTouch(() => {
  if (onLava) return; onLava = true;
  const tick = () => { if (!onLava) return; game.player.damage(15);
    game.ui.set('lava', '🔥 Горячо! -15 HP', { x: 50, y: 80, anchor: 'bottom', color: '#ff6644', size: 18 });
    timer = game.after(1, tick); };
  tick();
});
game.self.onUntouch(() => { onLava = false; if (timer) game.cancel(timer); game.ui.set('lava', ''); });` }],
    },
    {
        id: 'elevator',
        name: 'Лифт',
        desc: 'Платформа-лифт сама ездит вверх-вниз между двумя этажами. Встань на неё и катайся. (Вики: «Лифт»)',
        icon: 'elevator', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 0.5, z: 0, sx: 4, sy: 0.5, sz: 4, color: '#7a8a9a', material: 'metal', name: 'Лифт' }],
        scripts: [{ attachTo: 'on-target', code:
`// Лифт: плавно ездит между нижней и верхней высотой.
const p0 = game.self.position;
const lowY = p0.y, highY = p0.y + 8;
let t = 0;
game.onTick((dt) => {
  t += dt;
  // Синусоида 0..1 с паузами на концах (период ~8с).
  const k = (Math.sin(t * 0.5 - Math.PI/2) + 1) / 2;
  game.self.move(p0.x, lowY + (highY - lowY) * k, p0.z);
});` }],
    },
    {
        id: 'finish-line',
        name: 'Финиш (победа)',
        desc: 'Финишная плита: дойди до неё — на экране «ПОБЕДА!» и управление блокируется. (Вики: «Беги к финишу»)',
        icon: 'flag', category: 'ui',
        prims: [{ type: 'cube', x: 0, y: 0.15, z: 0, sx: 4, sy: 0.3, sz: 2, color: '#ffd23a', material: 'neon', name: 'Финиш', canCollide: false }],
        scripts: [{ attachTo: 'on-target', code:
`// Финиш: касание → экран победы.
let done = false;
game.self.onTouch(() => {
  if (done) return; done = true;
  game.ui.set('win', '🏆 ПОБЕДА!', { x: 50, y: 42, anchor: 'center', color: '#ffd23a', size: 48 });
  game.ui.set('winsub', 'Ты дошёл до финиша!', { x: 50, y: 54, anchor: 'center', color: '#fff', size: 22 });
  game.player.setInputBlocked(true);
});` }],
    },
    {
        id: 'sound-tile',
        name: 'Звуковая плитка',
        desc: 'Наступи на плитку — играет звук. Из таких можно собрать мелодию. (Вики: «Эхо-комната»)',
        icon: 'sound', category: 'fx',
        prims: [{ type: 'cube', x: 0, y: 0.1, z: 0, sx: 2, sy: 0.2, sz: 2, color: '#6f8bff', material: 'neon', name: 'Звуковая плитка' }],
        scripts: [{ attachTo: 'on-target', code:
`// Плитка играет звук при касании + подсвечивается.
let cd = false;
game.self.onTouch(() => {
  if (cd) return; cd = true;
  game.sound.play('coin');
  game.self.setColor('#ffffff');
  game.after(0.25, () => { game.self.setColor('#6f8bff'); cd = false; });
});` }],
    },

    // ===== Партия 3 из Вики (остальные механики) =====

    // --- Мир ---
    {
        id: 'damage-zone',
        name: 'Зона опасности',
        desc: 'Невидимая зона: пока игрок внутри — теряет здоровье. (Вики: «Зона опасности»)',
        icon: 'warning', category: 'world',
        prims: [{ type: 'cube', x: 0, y: 2, z: 0, sx: 6, sy: 4, sz: 6, color: '#ff3344', material: 'glass', canCollide: false, name: 'Зона опасности' }],
        scripts: [{ attachTo: 'on-target', code:
`// Зона урона: внутри — 10 HP/сек.
let inside = false, t = null;
game.self.onTouch(() => { if (inside) return; inside = true;
  const tick = () => { if (!inside) return; game.player.damage(10);
    game.ui.set('dz', '☠ Опасно! -10 HP', { x:50, y:78, anchor:'bottom', color:'#ff5555', size:18 });
    t = game.after(1, tick); }; tick(); });
game.self.onUntouch(() => { inside = false; if (t) game.cancel(t); game.ui.set('dz',''); });` }],
    },
    {
        id: 'spikes-trap',
        name: 'Шипы-ловушка',
        desc: 'Ряд острых шипов: наступишь — мгновенный урон. (Вики: «Полоса препятствий»)',
        icon: 'warning', category: 'world',
        prims: [
            { type: 'cube', x: 0, y: 0.1, z: 0, sx: 4, sy: 0.2, sz: 1.5, color: '#555', material: 'metal', name: 'Основание шипов' },
            { type: 'cone', x: -1.2, y: 0.7, z: 0, sx: 0.6, sy: 1.2, sz: 0.6, color: '#cccccc', material: 'metal', canCollide: false, name: 'Шип 1' },
            { type: 'cone', x: 0, y: 0.7, z: 0, sx: 0.6, sy: 1.2, sz: 0.6, color: '#cccccc', material: 'metal', canCollide: false, name: 'Шип 2' },
            { type: 'cone', x: 1.2, y: 0.7, z: 0, sx: 0.6, sy: 1.2, sz: 0.6, color: '#cccccc', material: 'metal', canCollide: false, name: 'Шип 3' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Шипы: касание → урон + отброс.
let cd = false;
game.self.onTouch(() => { if (cd) return; cd = true;
  game.player.damage(34);
  game.ui.set('sp', '🗡 Ой! -34 HP', { x:50, y:78, anchor:'bottom', color:'#ff5555', size:18 });
  game.after(1.2, () => { game.ui.set('sp',''); cd = false; }); });` }],
    },
    {
        id: 'traffic-light',
        name: 'Светофор',
        desc: 'Светофор переключает красный/жёлтый/зелёный по таймеру. (Вики: «Светофор»)',
        icon: 'light', category: 'world',
        prims: [
            { type: 'cube', x: 0, y: 3, z: 0, sx: 1.2, sy: 4, sz: 1.2, color: '#2a2a2a', material: 'matte', name: 'Корпус светофора' },
            { type: 'sphere', x: 0, y: 4.2, z: 0.6, sx: 0.8, sy: 0.8, sz: 0.4, color: '#5a0000', material: 'neon', canCollide: false, name: 'Красный' },
            { type: 'sphere', x: 0, y: 3.3, z: 0.6, sx: 0.8, sy: 0.8, sz: 0.4, color: '#5a5a00', material: 'neon', canCollide: false, name: 'Жёлтый' },
            { type: 'sphere', x: 0, y: 2.4, z: 0.6, sx: 0.8, sy: 0.8, sz: 0.4, color: '#005a00', material: 'neon', canCollide: false, name: 'Зелёный' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Светофор: переключение фаз каждые 2 сек.
const R = game.scene.findOne('Красный'), Y = game.scene.findOne('Жёлтый'), G = game.scene.findOne('Зелёный');
const phases = [['#ff0000','#5a5a00','#005a00'], ['#5a0000','#ffcc00','#005a00'], ['#5a0000','#5a5a00','#00ff00']];
let p = 0;
function show(){ const c = phases[p]; if(R) R.color = c[0]; if(Y) Y.color = c[1]; if(G) G.color = c[2]; }
show();
game.every(2, () => { p = (p+1) % phases.length; show(); });` }],
    },
    {
        id: 'harvest-plant',
        name: 'Грядка с урожаем',
        desc: 'Растение растёт, по клику собираешь урожай (+10 монет) и оно вырастает заново. (Вики: «Сбор урожая»)',
        icon: 'plant', category: 'world',
        prims: [
            { type: 'cube', x: 0, y: 0.2, z: 0, sx: 2, sy: 0.4, sz: 2, color: '#6b4a2e', material: 'matte', name: 'Грядка' },
            { type: 'sphere', x: 0, y: 0.8, z: 0, sx: 1, sy: 1, sz: 1, color: '#3f9a48', material: 'matte', name: 'Урожай' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Грядка: собрал урожай → он исчезает, растёт заново (цвет красный→зелёный),
// при полном размере снова созрел и его можно собрать.
const plant = game.scene.findOne('Урожай');
let ripe = true;       // созрел ли (можно собирать)
let growth = 1;        // 0..1 — степень роста
// Плавная интерполяция цвета красный(незрелый)→зелёный(спелый).
function colorAt(g) {
  const r = Math.round(0xb0 + (0x3f - 0xb0) * g);
  const gr = Math.round(0x40 + (0x9a - 0x40) * g);
  const b = Math.round(0x2e + (0x48 - 0x2e) * g);
  return '#' + [r,gr,b].map(v => v.toString(16).padStart(2,'0')).join('');
}
game.self.onInteract(() => {
  if (!ripe) { game.ui.set('h','Ещё не созрело...', {x:50,y:80,anchor:'bottom',color:'#bbb',size:16}); return; }
  ripe = false;
  growth = 0;
  game.broadcast('coins', { add: 10 });
  if (plant) { plant.scale = 0.01; plant.color = colorAt(0); }
  game.ui.set('h','🌾 Собрано! +10 монет', {x:50,y:80,anchor:'bottom',color:'#ffd23a',size:18});
  game.after(2, () => game.ui.set('h',''));
}, { text: 'Собрать урожай', key: 'e', distance: 4 });
// Рост: за ~5 секунд от 0 до 1.
game.onTick((dt) => {
  if (ripe || !plant) return;
  growth = Math.min(1, growth + dt / 5);
  plant.scale = Math.max(0.05, growth);
  plant.color = colorAt(growth);
  if (growth >= 1) ripe = true;
});` }],
    },
    {
        id: 'falling-objects',
        name: 'Падающие предметы',
        desc: 'Из этой точки с неба сыплются кубики — лови их или уворачивайся. (Вики: «Поймай падающее»)',
        icon: 'box', category: 'world',
        prims: [{ type: 'sphere', x: 0, y: 8, z: 0, sx: 0.6, sy: 0.6, sz: 0.6, color: '#4d6bff', material: 'neon', canCollide: false, name: 'Тучка-источник' }],
        scripts: [{ attachTo: 'on-target', code:
`// Каждые 1.5с роняет куб из позиции источника.
const p = game.self.position;
game.every(1.5, () => {
  game.scene.spawn('primitive:cube', {
    x: p.x + (Math.random()-0.5)*6, y: p.y, z: p.z + (Math.random()-0.5)*6,
    sx: 0.6, sy: 0.6, sz: 0.6, color: '#ffaa33', anchored: false, canCollide: true, lifetime: 6 });
});` }],
    },

    // --- Интерфейс ---
    {
        id: 'score-counter',
        name: 'Счётчик очков',
        desc: 'Счёт очков в HUD. Другие механики шлют game.broadcast("score",{add:N}). (Вики: «Собери монетки»)',
        icon: 'star', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Счётчик очков. Прибавить: game.broadcast('score', { add: 1 });
let score = 0;
function show(){ game.ui.set('score', '⭐ ' + score, { x:8, y:6, anchor:'top', color:'#ffd23a', size:22 }); }
show();
game.onMessage('score', (m) => { score += (m && m.add) ? m.add : 1; show(); });` }],
    },
    {
        id: 'leaderboard',
        name: 'Таблица лидеров',
        desc: 'Лидерборд справа-сверху (Очки/Время). Растёт от монет и очков других механик. Сохраняется в БД между сессиями. (Задача 20)',
        icon: 'trophy', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Таблица лидеров: столбцы «Очки» (primary) и «Время».
game.leaderstats.define('Очки',  { initial: 0, format: 'number', icon: '⭐', color: '#ffd23a', primary: true });
game.leaderstats.define('Время', { initial: 0, format: 'time',   icon: '⏱', color: '#7fd0ff' });
// Время идёт само.
game.every(1, () => game.leaderstats.me.add('Время', 1));
// Любая механика, шлющая broadcast('score',{add}) или ('coins',{add}),
// автоматически добавляет очки в таблицу.
game.onMessage('score', (m) => game.leaderstats.me.add('Очки', (m && m.add) ? m.add : 1));
game.onMessage('coins', (m) => game.leaderstats.me.add('Очки', (m && m.add) ? m.add : 1));` }],
    },
    {
        id: 'hp-bar',
        name: 'Полоска здоровья',
        desc: 'Показывает HP игрока в углу экрана, обновляется при уроне/лечении. (Вики: «Лава-пол»)',
        icon: 'warning', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Своя полоска HP. Сначала прячем стандартную, чтобы не дублировалась.
game.hud.setHpVisible(false);
function show(){ const hp = Math.max(0, Math.round(game.player.hp));
  game.ui.set('hp', '❤ ' + hp + ' / 100', { x:50, y:94, anchor:'bottom', color: hp>30?'#36d57a':'#ff4444', size:22 }); }
show();
game.every(0.2, show);` }],
    },
    {
        id: 'hide-default-hp',
        name: 'Скрыть стандартный HUD HP',
        desc: 'Прячет стандартную полосу здоровья сверху — чтобы показать свою. (Свойство игрока game.hud.setHpVisible)',
        icon: 'warning', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Скрыть стандартную полосу здоровья игрока.
game.hud.setHpVisible(false);` }],
    },
    {
        id: 'code-door',
        name: 'Дверь по коду',
        desc: 'Красивая дверь с кодовой панелью: подойди — появится поле ввода, введи код (1234) — откроется. (Вики: «Дверь по коду»)',
        icon: 'keypad', category: 'world',
        // Красивая дверь (полотно + рамка) + кодовая панель на стене.
        prims: [
            { type: 'cube', x: 0, y: 2, z: 0, sx: 0.25, sy: 3.8, sz: 2.6, color: '#5a6478', material: 'metal', name: 'Полотно двери-код' },
            { type: 'cube', x: 0.16, y: 2.6, z: 0, sx: 0.06, sy: 0.9, sz: 1.8, color: '#79879a', material: 'metal', canCollide: false, name: 'Панель двери' },
            { type: 'cube', x: 0.16, y: 1.3, z: 0, sx: 0.06, sy: 0.9, sz: 1.8, color: '#79879a', material: 'metal', canCollide: false, name: 'Панель двери низ' },
            { type: 'cube', x: 0.3, y: 2, z: 0.95, sx: 0.15, sy: 0.6, sz: 0.5, color: '#ffd23a', material: 'neon', canCollide: false, name: 'Кодовая панель' },
            { type: 'cube', x: 0, y: 2, z: -1.55, sx: 0.4, sy: 4.2, sz: 0.4, color: '#3a4250', material: 'metal', name: 'Косяк левый' },
            { type: 'cube', x: 0, y: 2, z: 1.55, sx: 0.4, sy: 4.2, sz: 0.4, color: '#3a4250', material: 'metal', name: 'Косяк правый' },
            { type: 'cube', x: 0, y: 4.1, z: 0, sx: 0.4, sy: 0.4, sz: 3.5, color: '#3a4250', material: 'metal', name: 'Перемычка' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Дверь по коду 1234. Поле ввода появляется ТОЛЬКО рядом. Верный код →
// дверь ПЛАВНО распахивается вокруг петли (вместе с панелями).
const CODE = '1234';
const p0 = game.self.position;
const halfW = 1.3;                 // полуширина полотна (sz=2.6)
const hingeX = p0.x, hingeZ = p0.z - halfW;
const RADIUS = 6;
let opened = false, near = false, cur = 0, target = 0;
const SPEED = Math.PI;             // ~0.5с на 90°

// Декор полотна — двигается вместе с дверью.
const decorNames = ['Панель двери', 'Панель двери низ', 'Кодовая панель'];
const decor = [];
for (const nm of decorNames) {
  const o = game.scene.findOne(nm);
  if (o && o.position) decor.push({ obj:o, dx:o.position.x-p0.x, dy:o.position.y-p0.y, dz:o.position.z-p0.z });
}
function rotY(lx, lz, a){ const s=Math.sin(a),c=Math.cos(a); return { x: lx*c+lz*s, z: -lx*s+lz*c }; }
function place(a){
  const pc = rotY(0, halfW, a);
  const cx = hingeX + pc.x, cz = hingeZ + pc.z;
  game.self.move(cx, p0.y, cz); game.self.rotate(a);
  for (const d of decor){ const r = rotY(d.dx, d.dz, a); d.obj.move(cx+r.x, p0.y+d.dy, cz+r.z); if (d.obj.rotate) d.obj.rotate(a); }
}
game.onTick((dt) => {
  if (cur !== target){ const st=SPEED*dt; cur = Math.abs(target-cur)<=st ? target : cur+Math.sign(target-cur)*st; place(cur); }
  const pl = game.player.position;
  const d = Math.sqrt((pl.x-p0.x)**2 + (pl.z-p0.z)**2);
  if (opened){
    // Дверь открыта: подсказка «E закрыть» только когда игрок рядом.
    if (d < RADIUS && !near){ near = true; game.ui.set('codehint','Нажми E чтобы закрыть дверь', {x:50,y:80,anchor:'bottom',color:'#fff',size:16}); }
    else if (d >= RADIUS && near){ near = false; game.ui.set('codehint','',{}); }
    return;
  }
  // Дверь закрыта: поле ввода кода по дистанции.
  if (d < RADIUS && !near){ near = true;
    game.gui.create('textbox', { id:'codein', x:50, y:86, w:24, h:8, anchor:'center', placeholder:'Код...', textSize:20 });
    game.ui.set('codehint', '🔢 Введи код двери (1234) и нажми Enter', {x:50,y:78,anchor:'bottom',color:'#fff',size:16}); }
  else if (d >= RADIUS && near){ near = false; game.gui.remove('codein'); game.ui.set('codehint','',{}); }
});
game.gui.onSubmit('codein', (text) => {
  if (opened) return;
  if (String(text).trim() === CODE){
    opened = true; near = false; target = Math.PI/2;   // плавно распахнуть
    game.gui.remove('codein');
    game.ui.set('codehint','✓ Открыто!', {x:50,y:78,anchor:'bottom',color:'#36d57a',size:18});
    game.after(2, () => game.ui.set('codehint','',{}));
  } else game.ui.set('codehint','✗ Неверный код', {x:50,y:78,anchor:'bottom',color:'#ff5555',size:18});
});
// Закрыть дверь по E (только если открыта и игрок рядом).
game.onKey('e', () => {
  if (!opened) return;
  const pl = game.player.position;
  if (Math.sqrt((pl.x-p0.x)**2 + (pl.z-p0.z)**2) >= RADIUS) return;
  opened = false; near = false; target = 0;
  game.ui.set('codehint','🔒 Дверь закрыта.', {x:50,y:80,anchor:'bottom',color:'#fff',size:16});
  game.after(1.5, () => game.ui.set('codehint','',{}));
});` }],
    },
    {
        id: 'name-label',
        name: 'Метка с именем',
        desc: 'Над объектом висит табличка с текстом (имя/HP). (Вики: «Имена над врагами»)',
        icon: 'tag', category: 'ui',
        prims: [{ type: 'cube', x: 0, y: 1, z: 0, sx: 1.5, sy: 2, sz: 1.5, color: '#c83030', material: 'matte', name: 'Объект с меткой' }],
        scripts: [{ attachTo: 'on-target', code:
`// Метка-табличка над объектом.
game.self.setLabel('Враг  ❤ 100', { color: '#ffffff', bg: '#c83030' });` }],
    },
    {
        id: 'countdown',
        name: 'Обратный отсчёт',
        desc: 'Таймер обратного отсчёта в HUD. По нулю — событие (тут просто сообщение). (Вики: «продержись N секунд»)',
        icon: 'clock', category: 'ui',
        scripts: [{ attachTo: 'global', code:
`// Обратный отсчёт 30 секунд.
let left = 30;
game.ui.set('cd', '⏳ ' + left, { x:50, y:6, anchor:'top', color:'#fff', size:26 });
const id = game.every(1, () => {
  left--; game.ui.set('cd', '⏳ ' + left, { x:50, y:6, anchor:'top', color: left<=5?'#ff4444':'#fff', size:26 });
  if (left <= 0) { game.cancel(id); game.ui.set('cd', '⏰ Время вышло!', { x:50, y:42, anchor:'center', color:'#ff4444', size:40 }); }
});` }],
    },

    // --- Эффекты ---
    {
        id: 'fire-emitter',
        name: 'Костёр (огонь)',
        desc: 'Источник частиц огня — горит постоянно. (Палитра эффектов)',
        icon: 'sparkles', category: 'fx',
        prims: [{ type: 'cylinder', x: 0, y: 0.2, z: 0, sx: 1.2, sy: 0.4, sz: 1.2, color: '#3a2a1a', material: 'matte', name: 'Костёр' }],
        scripts: [{ attachTo: 'on-target', code:
`// Постоянный огонь из точки костра.
const p = game.self.position;
function fire(){ game.scene.spawnParticles('fire', { x:p.x, y:p.y+0.5, z:p.z }, { duration: 1.5, count: 40 }); }
fire(); game.every(1.2, fire);` }],
    },
    {
        id: 'magnet-coins',
        name: 'Магнит монет',
        desc: 'Монета сама летит к игроку, когда он подходит близко. (Вики: «Магнит монет»)',
        icon: 'circle', category: 'fx',
        prims: [{ type: 'cylinder', x: 0, y: 1, z: 0, sx: 0.8, sy: 0.2, sz: 0.8, color: '#ffd23a', material: 'metal', name: 'Монета' }],
        scripts: [{ attachTo: 'on-target', code:
`// Монета летит к игроку, если он ближе 6 м; коснулся — +1 монета.
let taken = false;
game.onTick(() => {
  if (taken) return;
  const me = game.self.position, pl = game.player.position;
  const dx = pl.x-me.x, dy = pl.y-me.y, dz = pl.z-me.z;
  const d = Math.sqrt(dx*dx+dy*dy+dz*dz);
  if (d < 1.2) { taken = true; game.broadcast('coins', { add: 1 }); game.self.setVisible(false); return; }
  if (d < 6) { game.self.move(me.x+dx*0.12, me.y+dy*0.12, me.z+dz*0.12); }
});` }],
    },

    // --- NPC и бой ---
    {
        id: 'npc-chaser',
        name: 'NPC-преследователь',
        desc: 'Враг бежит за игроком по всему уровню. (Вики: «Преследователь»)',
        icon: 'chase', category: 'npc',
        scripts: [{ attachTo: 'global', code:
`// Спавним NPC, который преследует игрока.
const enemy = game.scene.spawnNpc('skin_roblox-noob', { x: 8, z: 8, name: 'Охотник', speed: 4 });
if (enemy && enemy.follow) enemy.follow('player');` }],
    },
    {
        id: 'npc-trader',
        name: 'Торговец (NPC)',
        desc: 'NPC-персонаж торговец: подойди, нажми E — открывается диалог. (Вики: «Торговец»)',
        icon: 'trader', category: 'npc',
        // Невидимый prim-триггер держит onInteract; рядом спавнится NPC-персонаж.
        prims: [{ type: 'cube', x: 0, y: 1.5, z: 0, sx: 2, sy: 3, sz: 2, color: '#3a6ea5', material: 'matte', visible: false, canCollide: false, name: 'Зона торговца' }],
        scripts: [{ attachTo: 'on-target', code:
`// Торговец — настоящий NPC-персонаж. Триггер (этот объект) держит диалог по E.
const p = game.self.position;
const npc = game.scene.spawnNpc('skin_roblox-noob', { x: p.x, z: p.z, name: 'Торговец Боб' });
game.self.onInteract(() => {
  game.modal.dialog('Торговец Боб', [
    'Привет, путник! Заходи за товарами.',
    'У меня лучшие мечи во всём королевстве!',
    'Возвращайся, когда накопишь монет.',
  ]);
}, { text: 'Поговорить', key: 'e', distance: 4 });` }],
    },
    {
        id: 'shooting-target',
        name: 'Мишень для стрельбы',
        desc: 'Кликни по мишени — +10 очков, мишень исчезает и появляется снова. (Вики: «Тир»)',
        icon: 'crosshair', category: 'npc',
        prims: [
            { type: 'cylinder', x: 0, y: 2, z: 0, sx: 0.3, sy: 2, sz: 2, color: '#ffffff', material: 'matte', name: 'Мишень' },
            { type: 'cylinder', x: 0.2, y: 2, z: 0, sx: 0.1, sy: 1.2, sz: 1.2, color: '#ff3333', material: 'matte', canCollide: false, name: 'Кольцо мишени' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Мишень: клик → +10 очков, прячется на 1.5с.
let active = true;
game.self.onClick(() => {
  if (!active) return; active = false;
  game.broadcast('score', { add: 10 });
  game.self.setVisible(false);
  game.ui.set('hit', '🎯 +10!', { x:50, y:75, anchor:'bottom', color:'#36d57a', size:20 });
  game.after(1.5, () => { game.self.setVisible(true); active = true; game.ui.set('hit',''); });
});` }],
    },
    {
        id: 'enemy-hp',
        name: 'Враг с HP',
        desc: 'Враг-персонаж: преследует игрока, бьёт при касании. Над головой — полоска здоровья. (Вики: «босс», «имена над врагами»)',
        icon: 'boss', category: 'npc',
        // Невидимый триггер-якорь; рядом спавнится NPC-враг.
        prims: [{ type: 'cube', x: 0, y: 1, z: 0, sx: 1, sy: 2, sz: 1, color: '#7a2030', material: 'matte', visible: false, canCollide: false, name: 'Якорь врага' }],
        scripts: [{ attachTo: 'on-target', code:
`// Враг-персонаж: преследует игрока, бьёт с анимацией удара при сближении.
const p = game.self.position;
const enemy = game.scene.spawnNpc('skin_retro-zombie', { x: p.x, z: p.z, name: 'Враг', hp: 100, speed: 3.5 });
if (enemy && enemy.follow) enemy.follow('player');
let cd = 0, atk = false;
game.onTick((dt) => {
  if (!enemy || !enemy.position) return;
  cd -= dt;
  const pl = game.player.position, e = enemy.position;
  const d = Math.sqrt((pl.x-e.x)**2 + (pl.z-e.z)**2);
  const inRange = d < 3.5;
  if (inRange !== atk) { atk = inRange; enemy.setAttacking && enemy.setAttacking(inRange); }
  if (inRange && cd <= 0) { game.player.damage(10); cd = 1; }   // удар раз в секунду
});` }],
    },
    {
        id: 'enemy-wave',
        name: 'Волна врагов',
        desc: 'Спавнер: выпускает врагов волнами по таймеру. (Вики: «Выживание от волн», tower defense)',
        icon: 'zombie', category: 'npc',
        prims: [{ type: 'cylinder', x: 0, y: 0.15, z: 0, sx: 2, sy: 0.3, sz: 2, color: '#7a2030', material: 'neon', name: 'Портал врагов' }],
        scripts: [{ attachTo: 'on-target', code:
`// Каждые 5с спавнит 2 врагов из портала, они идут к игроку И бьют при касании.
const p = game.self.position;
const enemies = [];
function wave(){
  for (let i=0;i<2;i++){ const e = game.scene.spawnNpc('skin_retro-zombie', { x:p.x+(Math.random()-0.5)*2, z:p.z+(Math.random()-0.5)*2, name:'Враг', hp:60, speed:3 });
    if (e){ if (e.follow) e.follow('player'); enemies.push({ npc:e, cd:0 }); } }
}
game.after(2, wave); game.every(5, wave);
// Урон + анимация удара при сближении (у каждого врага свой кулдаун).
game.onTick((dt) => {
  const pl = game.player.position;
  for (const en of enemies){
    if (!en.npc || !en.npc.position) continue;
    en.cd -= dt;
    const e = en.npc.position;
    const d = Math.sqrt((pl.x-e.x)**2 + (pl.z-e.z)**2);
    const inRange = d < 3.5;
    if (inRange !== en.atk){ en.atk = inRange; en.npc.setAttacking && en.npc.setAttacking(inRange); }
    if (inRange && en.cd <= 0){ game.player.damage(8); en.cd = 1; }
  }
});` }],
    },

    // --- Экономика ---
    {
        id: 'shop-button',
        name: 'Магазин (кнопка покупки)',
        desc: 'GUI-кнопка магазина: покупка предмета за 50 монет (если хватает). (Вики: «Магазин»)',
        icon: 'cart', category: 'economy',
        scripts: [{ attachTo: 'global', code:
`// Кнопка магазина: купить за 50 монет.
let coins = 100;   // локальный баланс кита (для демо)
game.ui.set('bal', '🪙 ' + coins, { x:92, y:6, anchor:'top', color:'#ffd23a', size:22 });
game.onMessage('coins', (m) => { coins += (m&&m.add)?m.add:0; game.ui.set('bal','🪙 '+coins,{x:92,y:6,anchor:'top',color:'#ffd23a',size:22}); });
game.gui.create('button', { id:'buybtn', x:50, y:90, w:26, h:9, anchor:'center', text:'Купить меч — 50 🪙',
  bgGradient:{ stops:['#ffe066','#e0a000'], angle:90 }, textColor:'#3a2a00', textSize:18, fontWeight:800, borderRadius:12 });
game.gui.onClick('buybtn', () => {
  if (coins >= 50) { game.broadcast('coins', { add: -50 }); game.ui.set('shopmsg','✓ Куплено!',{x:50,y:80,anchor:'bottom',color:'#36d57a',size:18}); }
  else game.ui.set('shopmsg','✗ Мало монет',{x:50,y:80,anchor:'bottom',color:'#ff5555',size:18});
  game.after(2, () => game.ui.set('shopmsg',''));
});` }],
    },
    {
        id: 'clicker-button',
        name: 'Кликер',
        desc: 'GUI-кнопка: кликай и копи очки. (Вики: «Кликер»)',
        icon: 'click', category: 'economy',
        scripts: [{ attachTo: 'global', code:
`// Кликер: кнопка по центру, клик → +1 очко.
let n = 0;
function show(){ game.ui.set('clk', '👆 ' + n, { x:50, y:20, anchor:'center', color:'#fff', size:36 }); }
show();
game.gui.create('button', { id:'clickbtn', x:50, y:55, w:30, h:14, anchor:'center', text:'КЛИК!',
  bgGradient:{ stops:['#6f8bff','#3a4ed0'], angle:90 }, textColor:'#fff', textSize:32, fontWeight:900, borderRadius:18,
  hover:{ scale:1.05 }, active:{ scale:0.94 } });
game.gui.onClick('clickbtn', () => { n++; show(); });` }],
    },
    {
        id: 'key-lock',
        name: 'Ключ и замок',
        desc: 'Найди золотой ключ, подбери — и дверь рядом плавно откроется по E. Без ключа заперта. (Вики: «Ключ и сундук»)',
        icon: 'key', category: 'economy',
        prims: [
            // Ключ из примитивов: стержень + бородка + кольцо (torus). ПЕРВЫЙ — скрипт на нём.
            { type: 'cylinder', x: 0, y: 1, z: 0, sx: 0.12, sy: 1.0, sz: 0.12, color: '#ffd23a', material: 'metal', name: 'Ключ' },
            { type: 'torus', x: 0, y: 1.6, z: 0, sx: 0.5, sy: 0.5, sz: 0.5, color: '#ffd23a', material: 'metal', canCollide: false, name: 'Кольцо ключа' },
            { type: 'cube', x: 0.18, y: 0.6, z: 0, sx: 0.3, sy: 0.12, sz: 0.12, color: '#ffd23a', material: 'metal', canCollide: false, name: 'Бородка ключа' },
            { type: 'cube', x: 0.18, y: 0.4, z: 0, sx: 0.2, sy: 0.12, sz: 0.12, color: '#ffd23a', material: 'metal', canCollide: false, name: 'Бородка ключа 2' },
            // Красивая дверь (полотно + рамка) на расстоянии.
            { type: 'cube', x: 6, y: 2, z: 0, sx: 0.25, sy: 3.8, sz: 2.6, color: '#6b4423', material: 'matte', name: 'Запертая дверь' },
            { type: 'cube', x: 6, y: 2, z: -1.55, sx: 0.4, sy: 4.2, sz: 0.4, color: '#4a3018', material: 'matte', name: 'Косяк левый замка' },
            { type: 'cube', x: 6, y: 2, z: 1.55, sx: 0.4, sy: 4.2, sz: 0.4, color: '#4a3018', material: 'matte', name: 'Косяк правый замка' },
            { type: 'cube', x: 6, y: 4.1, z: 0, sx: 0.4, sy: 0.4, sz: 3.5, color: '#4a3018', material: 'matte', name: 'Перемычка замка' },
        ],
        scripts: [{ attachTo: 'on-target', code:
`// Ключ подбирается касанием. Дверь рядом открывается по E ТОЛЬКО с ключом —
// плавный поворот вокруг петли (как дверь по кнопке E).
let hasKey = false;
const keyParts = ['Ключ','Кольцо ключа','Бородка ключа','Бородка ключа 2'];
game.self.onTouch(() => {
  if (hasKey) return; hasKey = true;
  for (const nm of keyParts){ const o = game.scene.findOne(nm); if (o) o.visible = false; }
  game.ui.set('key', '🔑 Ключ найден! Иди к двери (E).', {x:50,y:80,anchor:'bottom',color:'#ffd23a',size:18});
});
const door = game.scene.findOne('Запертая дверь');
if (door && door.onInteract){
  const dp0 = door.position;
  const halfW = 1.3, hingeZ = dp0.z - halfW;
  let open = false, cur = 0, target = 0;
  function rotY(lx,lz,a){ const s=Math.sin(a),c=Math.cos(a); return {x:lx*c+lz*s, z:-lx*s+lz*c}; }
  game.onTick((dt) => {
    if (cur===target) return;
    const st = Math.PI*dt; cur = Math.abs(target-cur)<=st ? target : cur+Math.sign(target-cur)*st;
    const pc = rotY(0, halfW, cur);
    door.move(dp0.x+pc.x, dp0.y, hingeZ+pc.z); if (door.rotate) door.rotate(cur);
  });
  door.onInteract(() => {
    if (!hasKey){ game.ui.set('key','🔒 Заперто. Нужен ключ.',{x:50,y:80,anchor:'bottom',color:'#ff5555',size:18}); return; }
    open = !open; target = open ? Math.PI/2 : 0;
    game.ui.set('key', open ? '✓ Дверь открыта!' : '🔒 Дверь закрыта.', {x:50,y:80,anchor:'bottom',color:'#36d57a',size:18});
    game.after(2, () => game.ui.set('key','',{}));
  }, { text:'Открыть / закрыть', key:'e', distance:4 });
}` }],
    },

    // --- Из готовых игр (g5) ---
    {
        id: 'spawn-car',
        name: 'Машина (сядь за руль)',
        desc: 'Готовый автомобиль: подойди, держи F — садись за руль, WASD — едь. (Вики: «Такси-симулятор»)',
        icon: 'car', category: 'npc',
        scripts: [{ attachTo: 'global', code:
`// Спавн машины, на которой можно ездить.
game.scene.spawn('vehicle:car', { x: 0, y: 0.5, z: 5, model: 'car-sedan', color: '#c83030',
  name: 'Авто', params: { maxSpeed: 18, turnSpeed: 1.7, enginePower: 20 } });
game.ui.set('carhint', 'Подойди к машине и держи F — за руль!', {x:50,y:90,anchor:'bottom',color:'#fff',size:16});` }],
    },
    {
        id: 'cutscene-dialog',
        name: 'Диалог (кат-сцена)',
        desc: 'Объект, по взаимодействию показывает диалог по строкам. (Вики: «Тайна старого сундука»)',
        icon: 'scroll', category: 'npc',
        prims: [{ type: 'cube', x: 0, y: 0.6, z: 0, sx: 1.4, sy: 1.2, sz: 1, color: '#8a6a3a', material: 'matte', name: 'Рассказчик' }],
        scripts: [{ attachTo: 'on-target', code:
`// Диалог по строкам через готовый game.modal.dialog.
const lines = ['Давным-давно здесь стоял замок...', 'Его охранял древний страж.', 'Найди три ключа, чтобы войти!'];
game.self.onInteract(() => {
  game.modal.dialog('Рассказчик', lines);
}, { text:'Поговорить', key:'e', distance:4 });` }],
    },
    {
        id: 'guide-arrow',
        name: '3D-стрелка-указатель',
        desc: 'Стрелка-подсказка «иди сюда» ведёт игрока к цели. (Вики: «Туториал — собери монетки»)',
        icon: 'flag', category: 'ui',
        prims: [{ type: 'cylinder', x: 0, y: 1, z: 0, sx: 1, sy: 2, sz: 1, color: '#ffd23a', material: 'neon', name: 'Цель-указатель' }],
        scripts: [{ attachTo: 'on-target', code:
`// Стрелка от игрока к этому объекту-цели.
const arrow = game.fx.pointer({ from: 'player', to: game.self, preset: 'guide' });
game.self.onTouch(() => { if (arrow && arrow.remove) arrow.remove();
  game.ui.set('arr','✓ Дошёл!', {x:50,y:80,anchor:'bottom',color:'#36d57a',size:18}); });` }],
    },
];

/** Найти кит по id. */
export function getKit(id) {
    return GAMEPLAY_KITS.find(k => k.id === id) || null;
}
