Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c29e0d43 | ||
|
|
aaf4ace87c | ||
|
|
77e62189cf | ||
|
|
1c35a9e06c | ||
|
|
f0ba5fd2b9 | ||
|
|
a2e7456362 | ||
|
|
417a352823 | ||
|
|
6cb2639055 | ||
|
|
44510e2926 | ||
|
|
ed1ecb7d1c | ||
|
|
2191545629 | ||
|
|
9cb983a9b5 | ||
|
|
39846421bc |
No files matched your search
@@ -5,6 +5,7 @@
|
||||
# Сборки и артефакты экспорта
|
||||
build/
|
||||
export/
|
||||
dist/
|
||||
|
||||
# Временные файлы
|
||||
*.tmp
|
||||
|
||||
|
After Width: | Height: | Size: 633 KiB |
|
After Width: | Height: | Size: 97 KiB |
@@ -1,4 +1,4 @@
|
||||
# AI_REPORT — PEAK HOUR 97
|
||||
# AI_REPORT — PEAK HOUR 97 (v3.0)
|
||||
|
||||
## 1. Инструменты и модели
|
||||
|
||||
@@ -13,66 +13,218 @@ test-writer, explorer, feis-regulator, oracle):
|
||||
| **MiMo-V2.6-Flash** | аудио: SFX, ambient, фон |
|
||||
| дефолтные модели субагентов | поиск, тесты, ревью |
|
||||
|
||||
MR-ревью — агент **oracle** (`opencode-go/deepseek-v4.1-flash`): dry-run
|
||||
MR-ревью — агент **oracle** (opencode-go/deepseek-v4.1-flash): dry-run
|
||||
74–90 с, комментарий в MR, без мержа.
|
||||
|
||||
## 2. Где применялся ИИ
|
||||
## 2. Что изменилось в v3.0
|
||||
|
||||
- **Код:** `GameController`, `HourDirector`, `AttackDirector`, `Shop`,
|
||||
`SaveManager`, UI, мини-игры.
|
||||
- **UI v2:** Kaleidos, маскот **БАЙТ**, туториал.
|
||||
- **Ambient:** 4 WAV (`audio/amb`, PSU/fan/room/seek), генератор
|
||||
`generate_ambient.py`, autoload после первого жеста, mute/пауза/громкость.
|
||||
- **VHS:** процедурный `vhs_overlay.gd` (`RefCounted`) в `MainMenu._draw`;
|
||||
скан-линии/зерно/виньетка, без shader/screen texture, ввод не блокирует,
|
||||
текст резкий; только меню, чекбокс в настройках, HEADLESS без рендера.
|
||||
- **Баланс/мета:** SLA, 10 апгрейдов, таймеры, канал, мета 3/5/9.
|
||||
- **Аудит:** calm-фон, Esc итогов, уничтожение, тосты/fullscreen.
|
||||
- **Тесты/доки/звук:** unit/fuzz/интеграция/полный забег; `docs/`;
|
||||
18 SFX + `hum.wav` на Python.
|
||||
- **Запросы:** 1–4 **конкретных именованных файла** вместо анонимных слотов;
|
||||
имена на карточке 2×2, прикрепление по точному `file_id`, «ПОТЕРЯН» для
|
||||
пропавшего файла, жёсткая резервация. Новые `request_mix` H1–H9 и
|
||||
`request_cap`; H1-затравка и анти-голодание.
|
||||
- **TCP:** возвращена полоска соединения (3 попадания, 440/340/280 px/с,
|
||||
формула зоны, промах −4 SLA / 8 с, Esc 0 SLA / 4 с). TCP платит ×1.15,
|
||||
UDP — ×0.85 и передаёт ×0.8.
|
||||
- **Мини-игры:** 6 отправки + 4 атаки; игра с соединением проводов удалена;
|
||||
у отправки 3 ступени (старые T2–T4), у атак 4. Выбор — по сумме
|
||||
коэффициентов и таблице `rank_games` на 27 достижимых сумм.
|
||||
- **Магазин:** 8 лотов, дерево **8 085 $**, SLA до **220**, ускорение ×0.90,
|
||||
щит с 70 % награды, папки до 9×6, новая папка 180 $ (в v3.1 — 9 лотов и
|
||||
8 825 $, см. §10).
|
||||
- **UI:** Golos Text для тела текста, Press Start 2P только для логотипа и
|
||||
цифр; шкала 11/13/17/26; пиксельные иконки `art/icons/`; карточка запроса
|
||||
400×128 с сеткой файлов и прогнозом; пустое состояние отправки; «СЛОТЫ».
|
||||
- **Туториал:** 5 шагов с событийными триггерами, пульсирующие рамки,
|
||||
«ДАЛЕЕ», сохранение `tutorial_done`, повтор из главного меню.
|
||||
- **Документация:** GDD/DECISIONS/README/ITCH_PAGE/UI_REFRESH/ASSETS
|
||||
переписаны под фактические числа v3; старые решения помечены, а не удалены.
|
||||
|
||||
## 3. Приём: «враждебный критик»
|
||||
## 3. Где применялся ИИ
|
||||
|
||||
- **Код:** HourDirector, FileRegistry, Economy, Shop, AttackDirector,
|
||||
SaveManager, UI, соединение TCP и мини-игры.
|
||||
- **UI v3:** Golos Text, пиксельные иконки, карточка запроса, туториал с
|
||||
рамками, VHS главного меню.
|
||||
- **Ambient:** 4 WAV (audio/amb), генератор generate_ambient.py, autoload
|
||||
после первого жеста, mute/пауза/громкость.
|
||||
- **Баланс:** цели дохода, дерево магазина 8 085 $ (в v3.1 — 8 825 $),
|
||||
симулятор на 240 сидах.
|
||||
- **Аудит:** B4 (дыры в выборе мини-игры) закрыт ещё в v2; в v3 выбор
|
||||
перестроен на 27 сумм и 3 ступени. M8 (масштаб денег атак), M16
|
||||
(soft-lock «нет денег/нет места») сохранены.
|
||||
- **Тесты/доки/звук:** unit/fuzz/интеграция/полный забег; docs/;
|
||||
18 SFX + hum.wav на Python.
|
||||
|
||||
## 4. Приём: «враждебный критик»
|
||||
|
||||
Промпт просил быть враждебным критиком: «найди блокеры, из-за которых
|
||||
прототип нельзя собрать; не хвали». Нашлось **6 блокеров**. Один из них:
|
||||
выбор мини-игры по самому сложному типу ломал смешанные запросы. Заменили на
|
||||
средний коэффициент:
|
||||
прототип нельзя собрать; не хвали». Главный дефект v2 — **B4**: выбор
|
||||
мини-игры по `S−1` давал дыры и недостижимые строки. В v3 выбор перестроен:
|
||||
|
||||
```
|
||||
difficulty = sum(coeff(required)) / n
|
||||
coeff = {text: 1.0, sound: 2.0, photo: 3.0, video: 4.0}
|
||||
```
|
||||
ACHIEVABLE_SUMS = [1..22, 24, 25, 26, 28, 32] # 27 сумм для 1–4 файлов
|
||||
rank = index of S in ACHIEVABLE_SUMS
|
||||
tier = T1 при rank 0–7, T2 при 8–13, T3 при 14–26
|
||||
id = RANK_GAMES[rank] # фиксированная таблица
|
||||
|
||||
Это изменило §8.4 и §10 GDD и решение D17; пороги `<1.5 / <2.5 / <3.5`
|
||||
зафиксированы в `data/balance.json` и проверяются в `test_minigame.gd`.
|
||||
Все 6 игр достижимы в каждой из 3 ступеней; S=32 → video/T3. Дополнительно
|
||||
закрыты: H1-голодание запросов (анти-голодание + seed-файл), потерянные
|
||||
required-файлы (подстановка того же типа), неоднозначность TCP-протокола
|
||||
(блокировка после соединения). M16 (бесплатное уничтожение неприкреплённого
|
||||
файла) сохранён, но файл, закреплённый за запросом, удалить нельзя.
|
||||
|
||||
## 4. Что сделано без ИИ
|
||||
## 5. Что сделано без ИИ
|
||||
|
||||
Идея, логлайн, исходная спецификация, ключевые решения (1997 год, 9 часов,
|
||||
4 атаки, экономика, тон 90-х) и финальная приёмка — человеком, автором
|
||||
проекта. ИИ не придумывал игру: он помогал формулировать, писать код,
|
||||
считать баланс и искать противоречия.
|
||||
|
||||
## 5. Ассеты и лицензии
|
||||
## 6. Ассеты и лицензии
|
||||
|
||||
- **Handjet** и **Press Start 2P** — SIL OFL 1.1, тексты в
|
||||
`art/fonts/OFL-Handjet.txt` и `OFL-PressStart2P.txt`.
|
||||
- Pixelify Sans в теме не используется (нет глифов `О`/`П`); файл и OFL
|
||||
оставлены в `art/fonts/`.
|
||||
- UI, иконки и `icon.svg` — команда проекта, MIT.
|
||||
- SFX, ambient и `hum.wav` — скрипты проекта, MIT/CC0.
|
||||
- Код — MIT (`LICENSE`); OFL-шрифты совместимы с MIT при сохранении
|
||||
лицензий. Подробности — в `ASSETS.md`.
|
||||
- **Golos Text** — SIL OFL 1.1 (`art/fonts/OFL-GolosText.txt`), основной
|
||||
шрифт UI с v3.
|
||||
- **Press Start 2P** — SIL OFL 1.1 (`art/fonts/OFL-PressStart2P.txt`), только
|
||||
логотип и цифры.
|
||||
- **Handjet** — SIL OFL 1.1, с v3 в теме не используется и помечен как
|
||||
legacy.
|
||||
- Pixelify Sans в теме не используется (нет глифов О/П); файл и OFL
|
||||
оставлены в art/fonts/.
|
||||
- Пиксельные иконки `art/icons/*.png` сгенерированы `generate_icons.py`
|
||||
(Pillow), команда проекта, **MIT**; исходный тайл-лист был недоступен,
|
||||
поэтому набор перегенерирован с нуля.
|
||||
- UI, маскот **БАЙТ** и icon.svg — команда проекта, MIT.
|
||||
- SFX, ambient и hum.wav — скрипты проекта, MIT/CC0.
|
||||
- Код — MIT (LICENSE); OFL-шрифты совместимы с MIT при сохранении лицензий.
|
||||
Подробности — в ASSETS.md.
|
||||
|
||||
## 6. Проверки и ограничения
|
||||
## 7. Проверки v3.1
|
||||
|
||||
- **22/22 PASS** (21 тест + parse). Core 543, hour 133, shop 116, minigame
|
||||
51, slots 34, audio 37; fuzz 3249, integration 231, attacks 173,
|
||||
controller 143.
|
||||
- `test_full_playthrough` — 294 на сидах `1/42/777`: 3/3 victory, 9/9,
|
||||
SLA 200; main 350, toast 14, fullscreen 31, ui 135, ambient 74.
|
||||
- Web-экспорт — exit 0; Chromium **1920×1080**: меню → «Новый забег» →
|
||||
подготовка, JS-ошибок нет.
|
||||
- **Симулятор баланса:** `python tests/sim_balance.py`, 240 сидов, 9 часов,
|
||||
`dt=0.25`, `q=0.85`, TCP. Отчёт `tests/SIM_BALANCE_REPORT.md`: средний итог
|
||||
**7 205 $** в полосе **6 982–8 500 $**, расход магазина **3 964 $**
|
||||
(**44,9 %** дерева **8 825 $**), **0 поражений из 240**, SLA в конце H9 —
|
||||
**64,9**, запросов выполнено — **62,0**. Все 9 часов в ±20 %, exit code **0**,
|
||||
вердикт `PASS`. H2, H4 и H5 идут близко к −20 % (H2 −16,4 %, H4 −15,3 %,
|
||||
H5 −15,1 %), H1 — +14,1 % при цели **100 $**.
|
||||
- **Тесты:** актуальный список — **полный набор + parse-check** в
|
||||
`scripts/run_all_tests.sh` / `.ps1` (см. также `tests/`; счётчик наборов не
|
||||
хардкодится). `test_economy` и `test_save` входят в runner; core-наборы
|
||||
обновлены и зелёные; UI-наборы (`test_main_*`, `ui_smoke`) прогоняются
|
||||
отдельно.
|
||||
- **Web-пресет** `export_presets.cfg` существует: `thread_support=false`,
|
||||
экспорт в `build/web/index.html`, просмотр только по HTTP.
|
||||
|
||||
Ограничения: туториал — маскот и подсказки; web-save — IndexedDB; плейтест,
|
||||
громкость и масштаб — за человеком; remote push ещё не выполнен.
|
||||
### Финальная проверка v3
|
||||
|
||||
> Замер предыдущей волны. После изменений баланса, каталога файлов и UI
|
||||
> core-наборы перепрогнаны (зелёные), UI-наборы и E2E/веб-смоук требуют
|
||||
> повторного прогона.
|
||||
|
||||
- **Headless:** полный набор + parse-check; список и статус — в
|
||||
`scripts/run_all_tests.sh` / `.ps1`. Число наборов здесь не фиксируется.
|
||||
- **Баланс:** симулятор — `PASS`, 240 сидов, все 9 часов в ±20 %, средний
|
||||
итог **7 205 $**, магазин — **3 964 $ (44,9 %)** дерева **8 825 $**,
|
||||
поражений **0/240** (`tests/SIM_BALANCE_REPORT.md`).
|
||||
- **E2E на реальной сцене Main (1920×1080):** замер предыдущей волны,
|
||||
**190/190**; после изменений темпа и «ЖАРЫ» требует повторного прогона:
|
||||
меню → подготовка → час, приём пакета, прикрепление конкретного файла,
|
||||
модалка TCP и промах/кулдаун, мини-игра отправки → завершение (файлы
|
||||
**вернулись в папку**, деньги начислены), UDP-обход, конец часа при
|
||||
открытом соединении, H1-голодание, очистка ghost-привязок при save/load,
|
||||
защита ввода в модалках, шаг туториала «отправка»; повторный «НОВЫЙ ЗАБЕГ»
|
||||
тоже получает seed-файл в подготовке.
|
||||
- **Web (Godot 4.7.2, Web/no-threads; Chromium + SwiftShader):** сборка
|
||||
загружается, WebGL2 OK, 0 ошибок/предупреждений в консоли; меню →
|
||||
«Новый забег» → «НАЧАТЬ ЧАС» работает; drag пакета в папку и drag нужного
|
||||
файла в чип запроса работают; модалка соединения открывается (палочка
|
||||
промахивается сама, если не кликнуть за ~1,1 с, — так задумано).
|
||||
- **Полоска TCP** — рефлекторная мини-игра: 3 попадания, зона 30 % на H1;
|
||||
уход палочки за край = автопромах; промах = −4 SLA и кулдаун 8 с
|
||||
(`Esc` = 4 с, без штрафа).
|
||||
|
||||
## 8. Известные отклонения
|
||||
|
||||
- `ПРОПУСТИТЬ` даёт `q=0`; `quality = 0.5`, то есть половина
|
||||
quality-множителя, а не нулевая выплата. Осознанно.
|
||||
- `speed` выплаты фиксируется на старте отправки, пока таймер был заморожен
|
||||
мини-игрой; быстрый игрок видит ≈1.0.
|
||||
- Лимит папок 6, новая папка 180 $.
|
||||
- Бесплатное уничтожение любого неприкреплённого файла в подготовке
|
||||
(review M16); файл, закреплённый за запросом, не удаляется.
|
||||
- Деньги атак масштабируются по часу `max(0.75, hour/3)` (review M8).
|
||||
- H2 теоретически может выдать T2-запрос из трёх фото (S12); на практике
|
||||
кривая H2 остаётся почти полностью T1.
|
||||
- Расход магазина **44,9 %** — ниже проектной полосы 55–65 %; это известное
|
||||
отклонение, не влияющее на exit code.
|
||||
|
||||
## 9. Ограничения
|
||||
|
||||
Туториал автоматически запускается только в первом часу первого забега;
|
||||
повтор — из главного меню. Web-save — IndexedDB; плейтест, громкость и
|
||||
масштаб — за человеком; remote push ещё не выполнен. Мета-ключ `search`
|
||||
остаётся в данных, но UI-поиск не подключён. Скриншоты в `docs/screenshots/`
|
||||
стоит пересобрать под v3 перед публикацией страницы.
|
||||
|
||||
## 10. Изменение v3.1: TCP-модель, UDP-лот и шрифты
|
||||
|
||||
Короткая запись о текущей волне — без переписывания истории выше.
|
||||
|
||||
- **TCP-соединение — отдельная сущность `TcpConnection`.** Оно живёт в
|
||||
`HourDirector.active_connections`, занимает out-слот и хранит только клиента,
|
||||
IP и `request_id`. До соединения файлы в запрос не прикрепляются (drag
|
||||
отклоняется, backend режет). Кнопка `ПОДКЛЮЧИТЬСЯ` справа снизу запускает
|
||||
полоску; успех больше не открывает мини-игру отправки.
|
||||
- **Слот после отправки.** Успешная TCP-отправка удаляет запрос, но файлы
|
||||
**не расходует**: они возвращаются в папку и доступны новым запросам. Слот
|
||||
остаётся занятым (idle) до ручного `ОТКЛЮЧИТЬ`. Отключение
|
||||
мгновенное; во время передачи оно прерывает её без выплаты и без расхода
|
||||
файлов. `reject`/`timeout`/конец часа освобождают слоты. Слоты общие,
|
||||
`1 + slot_level` (макс. 5); TCP может занять единственный слот и
|
||||
заблокировать приём — это осознанно.
|
||||
- **UDP-риск.** Новый лот `lot_udp` («UDP-повторитель», 4 уровня, цены
|
||||
60/120/210/350) поднимает шанс с **40 %** до **80 %** шагом **+10 %**.
|
||||
Ролл — в конце передачи: успех даёт обычную выплату UDP ×0.85; провал —
|
||||
**−3 SLA**, без денег, файлы остаются, запрос жив, повтор через **4 с**,
|
||||
слот освобождается, streak сбрасывается. Всего лотов **9**, дерево
|
||||
**8 825 $**.
|
||||
- **Шрифты.** Тело — **Kechusilka Regular** (нормализован под Golos,
|
||||
x-height 0.530 em) с fallback **Golos Text**; крупный текст — **DisplayOTF**
|
||||
(wordmark 46, «ПОБЕДА/ПОРАЖЕНИЕ» 34, титлбар 14, «РЕЗУЛЬТАТ» 18); цифры и
|
||||
счётчики — **Press Start 2P**. Шкала 11/13/17/26.
|
||||
- **Документация:** обновлены `docs/GDD.md`, `docs/DECISIONS.md` (D53–D61),
|
||||
`docs/UI_REFRESH.md`, `docs/IMPLEMENTATION.md`, `docs/TEST_PLAN.md`,
|
||||
`docs/ITCH_PAGE.md` и `README.md`. `ASSETS.md` не менялся — шрифтовые
|
||||
лицензии уже зафиксированы там.
|
||||
- **Проверки:** полный набор + parse-check; core-наборы обновлены и зелёные;
|
||||
UI-наборы (`test_main_*`, `ui_smoke`) ещё идут. `tests/SIM_BALANCE_REPORT.md`
|
||||
отражает текущий `data/balance.json`: **7 205 $**, магазин **44,9 %**,
|
||||
**0/240** поражений, exit **0**.
|
||||
|
||||
## 11. v3.1: темп, каталог файлов и «ЖАРА»
|
||||
|
||||
Текущая волна; история выше не переписана.
|
||||
|
||||
- **Длительность:** часы **120 / 150 / 180 / 210 / 240 / 270 / 300 / 330 /
|
||||
360 с** (`120 + 30×(h−1)`), забег **36 минут** + подготовка. H1
|
||||
`income_target` — **100 $**. Окно спавна — **75 %** часа.
|
||||
- **Файлы:** успешная отправка не удаляет файл; он остаётся в папке и снова
|
||||
доступен пулу запросов. Общего лимита на забег нет; `hours[].files`
|
||||
(**6,6,7,8,9,9,10,10,10**) — скорость, не жёсткий cap. Интервал —
|
||||
`spawn_window / files`, переноса между часами нет (carry = 0); папки
|
||||
докупаются, к финалу ~**5–6**.
|
||||
- **Запросы:** `request_cap` удалён. Темп — `send_interval`
|
||||
(**20,24,18,24,24,24,24,24,24**), лимит — только `active_requests` +
|
||||
cutoff последних **10 с**. Анти-голодание — только первый запрос часа;
|
||||
требования выбираются из файлов в папках и не связаны с приходом.
|
||||
- **Атаки:** `start = clamp(0.25×duration, 20, 60)`,
|
||||
`end = min(0.85×duration, duration−20)`; H2 **37,5–127,5 с**,
|
||||
H9 **60–306 с**.
|
||||
- **Визуал:** `TemperatureGauge` вместо 03:42 в меню-баре, `StressOverlay`
|
||||
(heat-модель, alpha-only шейдер без screen texture), пульс SLA, тряска
|
||||
панелей ≤2 px, `reduce_motion`, `UiMotion`, Lane B (drag/карточки/слоты),
|
||||
headless-safe.
|
||||
- **Баланс:** `tests/SIM_BALANCE_REPORT.md` — **7 205 $**, полоса
|
||||
**6 982–8 500 $**, магазин **3 964 $ (44,9 %)**, **0/240** поражений,
|
||||
exit **0**; H2, H4 и H5 близко к −20 % (H2 −16,4 %, H4 −15,3 %, H5 −15,1 %).
|
||||
- **Тесты:** полный набор + parse-check; `test_economy` и `test_save` в
|
||||
runner; точный счёт не фиксируется.
|
||||
@@ -5,23 +5,123 @@
|
||||
|
||||
## Шрифты
|
||||
|
||||
### Handjet — основной UI-шрифт
|
||||
### Golos Text — fallback для редких символов (v3)
|
||||
|
||||
| Поле | Значение |
|
||||
| --- | --- |
|
||||
| Файл | `art/fonts/GolosText-VF.ttf` (variable; локальное имя для `GolosText[wght].ttf`) |
|
||||
| Оси | `wght` 400–900; в теме — `wght 600` |
|
||||
| Автор / проект | The Golos Text Project Authors — https://github.com/googlefonts/golos-text |
|
||||
| Источник | https://github.com/google/fonts/tree/main/ofl/golostext |
|
||||
| Лицензия | SIL Open Font License 1.1 — `art/fonts/OFL-GolosText.txt` |
|
||||
| SHA-256 | `17bb58fb69aec2dfb047a2ebf52534023e9b688c97a6b7ac795b0a72912c2063` |
|
||||
| Кириллица | полная (О/П/Ё/№), гуманистический рисунок, хорошо читается в 11–17 px |
|
||||
| Использование | fallback для Kechusilka и DisplayOTF (`Theme90.FONT_FALLBACK`, `Font.fallbacks`): `№ – … · × © ± ≈ −` и стрелки. Прямо в разметке не используется |
|
||||
|
||||
Типографическая шкала: **11 / 13 / 17 / 26 px** (`FS_MICRO`, `FS_BODY`,
|
||||
`FS_HEAD`, `FS_LOGO`); значения проверяет `ui_smoke.gd`, поэтому менять их
|
||||
можно только вместе с проверкой. Крупнее 26 px размер передаётся на месте
|
||||
вызова (24/28/34/46). Роли: тело текста — Kechusilka, крупный текст —
|
||||
DisplayOTF (`display()`), цифры и счётчики — Press Start 2P (`logo()`),
|
||||
страховка для редких символов — Golos Text.
|
||||
|
||||
### Kechusilka — основной шрифт тела (нормализованный)
|
||||
|
||||
| Поле | Значение |
|
||||
| --- | --- |
|
||||
| Файл | `art/fonts/Kechusilka-Regular.otf` — нормализованный (UPM 2048, 173 глифа) |
|
||||
| Оригинал | `art/fonts/source/Kechusilka-Regular-original.otf` — только для сверки, к игре не подключён |
|
||||
| Источник | архив `kechusilka-regular.zip` (загрузка пользователя), внутри `kechusilka-Regular_1.otf` и два PNG-превью |
|
||||
| Версия | Version 1.000; 16.09.2026; FontCreator 15 |
|
||||
| SHA-256 (нормализованный) | `e296eca059c05886da25808170accf6004619850dcac14200046199be04e10c8` |
|
||||
| SHA-256 (оригинал) | `3f666b9b04dcbbfcfe3a31256d99e807553c9dd2efa3f360cc14422599917d36` |
|
||||
| Лицензия | **подтверждена владельцем проекта** (license confirmed by project owner). Файла лицензии в архиве нет, строк copyright/license в шрифте тоже нет — поэтому текст лицензии здесь не приводится и её название не угадывается |
|
||||
| Кириллица | полная: все 66 букв (включая Ё/ё), все цифры |
|
||||
| Нет глифов | `№` U+2116, `–` U+2013, `…` U+2026, `·` `×` `← → ↑ ↓` `≤ ≥` `§` `°` `€` `•` `©` `±` `≈` `−` — закрываются fallback-шрифтом |
|
||||
| Fallback | `GolosText-VF.ttf` через `Font.fallbacks` в `Theme90.ui()` |
|
||||
| Использование | `FONT_UI`, `Theme90.ui()`; `default_font` темы `art/fonts/ui_theme.tres` |
|
||||
|
||||
**Нормализация.** У исходника x-height `0.312 em` против `0.530 em` у Golos
|
||||
Text: в одном кегле текст был бы на 40 % мельче, и шкала 11/13/17/26 поехала
|
||||
бы во всех карточках. Шрифт пересобран скриптом
|
||||
`art/fonts/normalize_kechusilka.py`:
|
||||
|
||||
- коэффициент `scale = 0.530 × 2048 / 640 = 1.696`;
|
||||
- масштабируются контуры (`TransformPen`), `hmtx` (advance + lsb),
|
||||
`hhea`/`OS/2`/`head` и 150 значений `GPOS` — иначе кернинг стал бы на 40 %
|
||||
слабее;
|
||||
- результат: x-height `0.5298 em`, cap `0.7422 em`, линия `15.16 px` против
|
||||
`15.60 px` у Golos при 13 px; ширина русских строк отличается от Golos
|
||||
в пределах ±6 %, поэтому вёрстку пересчитывать не пришлось;
|
||||
- хинтов в исходнике нет (0 stem-операторов), потерь при пересборке нет;
|
||||
- сборка воспроизводима: повторный запуск даёт тот же файл и тот же SHA-256.
|
||||
|
||||
Оригинал не удалён — он лежит в `art/fonts/source/` и в теме не используется:
|
||||
это точка сверки, если понадобится поменять коэффициент.
|
||||
|
||||
**Вес штриха.** Kechusilka светлее прежнего Golos Text `wght 600`: толщина
|
||||
стойки `0.10 em` против `0.145 em`, то есть 1.35 px против 1.88 px при 13 px
|
||||
(замер по растеризации `l`/`I`). На 11–13 px это читается, но на 9 px стойка
|
||||
уходит в 0.94 px и текст «сереет». Поэтому подписи мельче 11 px лучше поднять
|
||||
до `FS_MICRO`. Если после первого запуска в Godot захочется веса ближе к
|
||||
прежнему, это делается одной строкой — `FontVariation.variation_embolden` в
|
||||
`Theme90.ui()`; значение подбирать на месте, начиная с 0.1, потому что
|
||||
масштаб эффекта зависит от версии TextServer.
|
||||
|
||||
### Kechusilka: чем закрыты отсутствующие символы
|
||||
|
||||
В шрифте нет 19 символов, которые встречаются в тексте проекта. Цепочка
|
||||
`Kechusilka → Golos Text` закрывает все, кроме трёх: `Σ` U+03A3, `↔` U+2194
|
||||
(только комментарии и тесты, на экран не попадают) и `▯` U+25AF — его нет ни в
|
||||
одном шрифте проекта, включая прежний Golos, поэтому он и раньше рисовался
|
||||
встроенным резервом Godot. Регрессии нет.
|
||||
|
||||
Проверено разбором `cmap` всех шрифтов проекта и всех строковых литералов
|
||||
`.gd`/`.json`/`.tscn`, плюс рендер-симуляция подстановки: на 11–13 px стык
|
||||
шрифтов не виден, потому что у Kechusilka и Golos совпадает x-height.
|
||||
|
||||
|
||||
### DisplayOTF — крупный текст и логотип
|
||||
|
||||
| Поле | Значение |
|
||||
| --- | --- |
|
||||
| Файл | `art/fonts/DisplayOTF.otf` (статический, вариаций нет) |
|
||||
| Автор | Denis A Serikov, 2003–2006 — `art/fonts/COPYRIGHT-DisplayOTF.txt` |
|
||||
| Источник | архив `displayotf.zip` (загрузка пользователя; каталог `www.otlab.ru`), публичной ссылки нет |
|
||||
| Лицензия | Freeware — текст соглашения `art/fonts/LICENSE-DisplayOTF.txt` (в архиве `license_131.txt` в CP1251, перекодирован в UTF-8 без правок текста) |
|
||||
| SHA-256 | `c6578605ddc6992008044a1c7266cdd46dcf846a5d5f5dffbd391023e6ba0552` |
|
||||
| Кириллица | полная: все 66 букв (включая Ё/ё), цифры, `№ % « » — – … ! ?` |
|
||||
| Кернинг | таблиц `GPOS`/`kern` нет; есть `liga`, `salt`, `aalt` |
|
||||
| Цифры | пропорциональные (`1` = 395/1000 em, остальные 553/1000) |
|
||||
| Embedding | `fsType 0` — встраивание в игру разрешено |
|
||||
| Использование | крупный текст: `theme90.gd → display()`; wordmark `PEAK HOUR 97`, «ПОБЕДА»/«ПОРАЖЕНИЕ», «РЕЗУЛЬТАТ», заголовки окон, вариация темы `LogoLabel` |
|
||||
| Не использовать | тело текста и живые счётчики: мельче 16 px насечки сливаются, а пропорциональные цифры «дрожат» при смене значения |
|
||||
|
||||
Шрифт держит ту же «машинную» интонацию, что Press Start 2P, но с более
|
||||
узким и характерным рисунком, поэтому занимает роль крупного текста, а Press
|
||||
Start 2P остаётся на цифрах с моноширинной сеткой.
|
||||
|
||||
Условия лицензии, которые нужно соблюдать: файл распространяется «как есть» —
|
||||
не переконвертировать в TTF, не сабсетить и не менять состав знаков без
|
||||
разрешения автора; продавать шрифт отдельно нельзя.
|
||||
|
||||
### Handjet — legacy, в теме не используется
|
||||
|
||||
| Поле | Значение |
|
||||
| --- | --- |
|
||||
| Файл | `art/fonts/Handjet-VF.ttf` (variable; локальное имя для `Handjet[ELGR,ELSH,wght].ttf`) |
|
||||
| Оси | `wght` 100–900, `ELGR` 1–2, `ELSH` 0–16; в теме — `wght 700, ELGR 2, ELSH 2` |
|
||||
| Оси | `wght` 100–900, `ELGR` 1–2, `ELSH` 0–16 |
|
||||
| Автор / проект | The Handjet Project Authors / Rosetta Type — https://github.com/google/fonts/tree/main/ofl/handjet |
|
||||
| Прямая ссылка | https://raw.githubusercontent.com/google/fonts/main/ofl/handjet/Handjet%5BELGR%2CELSH%2Cwght%5D.ttf |
|
||||
| Лицензия | SIL Open Font License 1.1 — `art/fonts/OFL-Handjet.txt` |
|
||||
| SHA-256 | `9262749e8bb0b73ebcae0e20428689c3c59576eebeb6c4e1020300d2d41bdf4d` |
|
||||
| Кириллица | полная (проверено по cmap: А–Я, а–я, Ё/ё, цифры; 1339 кодпоинтов) |
|
||||
| Использование | `default_font` темы `art/fonts/ui_theme.tres`; все UI-тексты |
|
||||
| Кириллица | полная (1339 кодпоинтов) |
|
||||
| Использование | **нигде**: после перехода на Golos Text (D51) файл оставлен как исторический артефакт |
|
||||
|
||||
Handjet узкий (средняя ширина ≈0.36 em), поэтому в карточки 132 px влезают
|
||||
имена файлов. Допустимые кегли: **10 / 12 / 16 / 24**.
|
||||
TTF, `.import` и OFL можно удалить без влияния на игру; тема на Handjet не
|
||||
ссылается. Узкий рисунок Handjet больше не нужен: Golos Text держит плотный
|
||||
русский текст в карточках 400 px.
|
||||
|
||||
### Press Start 2P — логотип и крупные цифры
|
||||
### Press Start 2P — цифры и числовые акценты
|
||||
|
||||
| Поле | Значение |
|
||||
| --- | --- |
|
||||
@@ -32,10 +132,12 @@ Handjet узкий (средняя ширина ≈0.36 em), поэтому в
|
||||
| Лицензия | SIL Open Font License 1.1 — `art/fonts/OFL-PressStart2P.txt` |
|
||||
| SHA-256 | `034c77f1f05ec89421e4a63f0e3a4ca1ecf852cc6d2bf611f126f275728e017d` |
|
||||
| Кириллица | полная (656 кодпоинтов, включая Ё/ё и служебные `:/-`) |
|
||||
| Использование | только логотип, заголовки и числовые строки (`theme90.gd → logo()`) |
|
||||
| Использование | `theme90.gd → logo()`: цифры и счётчики (`num_line()`), часы, номера слотов, знак «?». Крупные слова и заголовки — уже на `display()` |
|
||||
|
||||
Шрифт слишком широкий для карточек (17 символов не влезают в 132 px), поэтому
|
||||
основным текстом он не используется.
|
||||
Шрифт слишком широкий для тела текста и карточек, поэтому основным текстом
|
||||
он не используется (D51). Цифры у него ровно 1 em, поэтому счётчики и часы
|
||||
не меняют ширину при смене значения — это главная причина держать его на
|
||||
числах, а крупный текст отдавать DisplayOTF.
|
||||
|
||||
### Pixelify Sans — в теме не используется
|
||||
|
||||
@@ -54,22 +156,71 @@ Handjet узкий (средняя ширина ≈0.36 em), поэтому в
|
||||
|
||||
### Совместимость лицензий
|
||||
|
||||
Все три шрифта — **SIL OFL 1.1**. OFL разрешает встраивать шрифты в игру
|
||||
(включая коммерческую), но требует сохранять текст лицензии и не продавать
|
||||
шрифт отдельно. Для производных шрифтов действует Reserved Font Name
|
||||
(у Press Start 2P — «Press Start 2P»). Это совместимо с MIT-лицензией кода:
|
||||
Golos Text, Handjet, Press Start 2P и Pixelify Sans — **SIL OFL 1.1**.
|
||||
OFL разрешает встраивать шрифты в игру (включая коммерческую), но требует
|
||||
сохранять текст лицензии и не продавать шрифт отдельно. Для производных
|
||||
шрифтов действует Reserved Font Name. Это совместимо с MIT-лицензией кода:
|
||||
MIT не накладывает ограничений на шрифты, а OFL-тексты лежат рядом с файлами.
|
||||
|
||||
DisplayOTF — **freeware**, не OFL. Играть и встраивать в игру можно, текст
|
||||
соглашения лежит рядом (`LICENSE-DisplayOTF.txt`); продавать сам шрифт
|
||||
отдельно и менять состав знаков нельзя. Это не мешает MIT-коду игры, но при
|
||||
смене лицензии проекта или платной сборке условия нужно перечитать.
|
||||
|
||||
Kechusilka — по шрифту лицензия **подтверждена владельцем проекта** (license
|
||||
confirmed by project owner). Текста лицензии в архиве и в файле шрифта нет,
|
||||
поэтому её название здесь не указано и условия не пересказываются: если
|
||||
проект будет публиковаться или продаваться, текст нужно запросить у
|
||||
владельца. Нормализация (масштаб контуров) — техническое изменение файла,
|
||||
о котором владелец уведомлён.
|
||||
|
||||
## Графика
|
||||
|
||||
| Файл | Автор | Лицензия | Примечание |
|
||||
| --- | --- | --- | --- |
|
||||
| UI, иконки, фаски | команда проекта | MIT | рисуются кодом в `scripts/ui/theme90.gd`; внешних текстур нет |
|
||||
| `art/icons/file_icons.png`, `art/icons/*.png` | исходный тайлсет — автор не указан; импорт — команда проекта | **неизвестна** (уточнить у владельца) | 128×128 атлас 8×8 (64 id) + шесть 32×32 fallback; собраны `art/icons/import_tileset.py` из `art/icons/source/tileset1.png` |
|
||||
| UI, панели, маскот | команда проекта | MIT | рисуются кодом в `scripts/ui/theme90.gd`; внешних UI-текстур нет |
|
||||
| `icon.svg` | команда проекта | MIT | иконка проекта, формы без текста (ThorVG) |
|
||||
| `docs/screenshots/*.png` | команда проекта | MIT | кадры игры 1280×720, сняты из работающей сборки |
|
||||
|
||||
`art/icons/` и `art/ui/` пусты (`.gitkeep`): спрайтов, тайлсетов и сторонней
|
||||
графики в проекте нет.
|
||||
### Пиксельные иконки файлов
|
||||
|
||||
`art/icons/file_icons.png` — атлас 128×128 RGBA, 8×8 ячеек по 16 px, 64
|
||||
стабильных `icon_id` (0..63). `art/icons/` также содержит `file_text.png`,
|
||||
`file_sound.png`, `file_photo.png`, `file_video.png`, `folder.png` и
|
||||
`packet.png` (все 32×32 RGBA) — fallback по типу файла для старых сейвов с
|
||||
`icon_id = -1`. Иконки показываются в сетке папки, drag-preview, чипах запроса
|
||||
и карточках пакетов; в `Theme90.draw_file_icon()` они рисуются с `nearest`,
|
||||
поэтому остаются резкими в 16/24/32 px.
|
||||
|
||||
- Источник: `art/icons/source/tileset1.png` — 128×128 RGBA, сетка 8×8 по
|
||||
16 px, 64 иконки, прозрачный фон, альфа только 0/255. Файл хранится в
|
||||
репозитории (каталог исключён из импорта Godot через `.gdignore`).
|
||||
- Импорт: `art/icons/import_tileset.py` (Python 3 + Pillow) — явная таблица
|
||||
`ATLAS_SLOTS` (id атласа → тайл источника) и `SINGLE_ICONS` (шесть
|
||||
fallback-иконок). Атлас собирается 1:1 без масштабирования, отдельные
|
||||
иконки — nearest 16→32 px; полупрозрачных пикселей скрипт не создаёт.
|
||||
- Превью для сверки: `art/icons/source/tileset1_preview.png` (все 64 тайла с
|
||||
номерами) и `art/icons/source/file_icons_preview.png` (атлас 1x/1.5x/2x/4x
|
||||
— ячейки 16/24/32/64 px, fallback в 16/24/32 px и ×4).
|
||||
- `art/icons/generate_icons.py` — **legacy**-генератор прежнего векторного
|
||||
набора; после импорта его запускать не нужно (перезапишет шесть fallback).
|
||||
- `art/icons/shop/*` — отдельный пак апгрейдов, этим импортом не затрагивается.
|
||||
|
||||
**Лицензия:** исходный `tileset1.png` пришёл без файла лицензии, автор и
|
||||
условия использования **неизвестны**. Прежний набор был сгенерирован в команде
|
||||
(MIT), но на импортированные PNG это не распространяется. До выяснения условий
|
||||
у владельца исходника не публиковать и не продавать сборку с этими иконками и
|
||||
не указывать для них MIT.
|
||||
|
||||
Пересборка:
|
||||
|
||||
```bash
|
||||
python3 art/icons/import_tileset.py
|
||||
# без превью: python3 art/icons/import_tileset.py --no-preview
|
||||
```
|
||||
|
||||
`art/ui/` по-прежнему пуст (`.gitkeep`): отдельных UI-спрайтов нет.
|
||||
|
||||
## Аудио
|
||||
|
||||
@@ -108,25 +259,38 @@ python3 audio/generate_sfx.py
|
||||
|
||||
```bash
|
||||
cd peak-hour/art/fonts
|
||||
curl -L -o Handjet-VF.ttf \
|
||||
"https://raw.githubusercontent.com/google/fonts/main/ofl/handjet/Handjet%5BELGR%2CELSH%2Cwght%5D.ttf"
|
||||
# Golos Text: скачайте GolosText[wght].ttf из google/fonts/ofl/golostext
|
||||
# и сохраните локально как GolosText-VF.ttf
|
||||
curl -L -o PressStart2P-Regular.ttf \
|
||||
"https://raw.githubusercontent.com/google/fonts/main/ofl/pressstart2p/PressStart2P-Regular.ttf"
|
||||
sha256sum Handjet-VF.ttf PressStart2P-Regular.ttf
|
||||
sha256sum GolosText-VF.ttf PressStart2P-Regular.ttf
|
||||
# Kechusilka пересобирается из оригинала (нужен fontTools):
|
||||
python3 normalize_kechusilka.py
|
||||
```
|
||||
|
||||
Ожидаемые хеши — в таблицах выше. Тексты OFL уже лежат в `art/fonts/`; при
|
||||
обновлении шрифта обновите и их. Pixelify Sans скачивается по ссылке из его
|
||||
раздела, но для сборки не требуется.
|
||||
обновлении шрифта обновите и их. Handjet и Pixelify Sans для сборки не
|
||||
требуются.
|
||||
|
||||
DisplayOTF уже лежит в `art/fonts/` вместе с лицензией — публичной ссылки на
|
||||
него нет, файл нужно сохранять как есть (не переконвертировать в TTF и не
|
||||
сабсетить).
|
||||
|
||||
Kechusilka тоже в репозитории: `Kechusilka-Regular.otf` — результат
|
||||
нормализации, оригинал лежит в `art/fonts/source/`. Правьте не результат, а
|
||||
скрипт: он пересобирает файл из оригинала и даёт тот же SHA-256. Если
|
||||
пересобрали — обновите хеш в таблице выше.
|
||||
|
||||
### Графика
|
||||
|
||||
Генерировать нечего: UI и иконки рисуются кодом, `icon.svg` — обычный SVG.
|
||||
После клонирования достаточно импортировать проект в Godot 4.7.2.
|
||||
Иконки пересобираются `python3 art/icons/import_tileset.py` (нужен Pillow) из
|
||||
`art/icons/source/tileset1.png`. `icon.svg` — обычный SVG. После клонирования
|
||||
достаточно импортировать проект в Godot 4.7.2.
|
||||
|
||||
## Чего в проекте нет
|
||||
|
||||
- Сторонних спрайтов, тайлсетов, музыкальных треков и сэмплов.
|
||||
- Сторонних спрайтов, кроме исходного `art/icons/source/tileset1.png`
|
||||
(происхождение и лицензия уточняются), музыкальных треков и сэмплов.
|
||||
- Ассетов из коммерческих игр; Press Start 2P лишь стилизован под аркадные
|
||||
bitmap-шрифты и распространяется по OFL.
|
||||
- Секретов, ключей и токенов в ассетах и данных.
|
||||
@@ -2,56 +2,102 @@
|
||||
|
||||
> Час пик. Одна смена. Ни одного свободного места.
|
||||
|
||||
Ночной диспетчер узла файлового обмена, 1997 год. Принимайте пакеты,
|
||||
раскладывайте файлы, собирайте запросы клиентов, держите канал и SLA — в
|
||||
интерфейсе, вдохновлённом Kaleidos: тёплая бумага, тонкие контуры и
|
||||
бирюзовый акцент.
|
||||
1997 год. Ночной диспетчер узла файлового обмена: принимайте пакеты,
|
||||
раскладывайте файлы-полимино по папкам, собирайте контейнеры запросов,
|
||||
проходите мини-игры и держите SLA. UI — «Kaleidos»: тёплая бумага, тонкие
|
||||
контуры и бирюзовый акцент.
|
||||
|
||||
**Жанр:** менеджмент / тайм-менеджмент, retro UI.
|
||||
**Платформа:** Godot **4.7.2** → **Web (HTML5, без threads)**, RU-only.
|
||||
**Окно:** 1920×1080, логический viewport 1280×720, fractional scale.
|
||||
**Забег:** 9 игровых часов по 5 минут (≈45 минут) + подготовка между часами.
|
||||
**Забег:** 9 игровых часов — **120 / 150 / 180 / 210 / 240 / 270 / 300 / 330 /
|
||||
360 с** (`120 + 30×(h−1)`, всего **36 минут**) + подготовка между часами.
|
||||
|
||||
## Статус v1.2
|
||||
## Статус v3.0
|
||||
|
||||
Играбельный веб-прототип полного цикла: **меню → подготовка → час → итоги
|
||||
часа → … → итоги забега**. Девять часов проходят на сидах `1`, `42`, `777`
|
||||
без потери часов; SLA после магазина — 200.
|
||||
часа → … → итоги забега**. Актуальная спецификация — [GDD v3.0](docs/GDD.md),
|
||||
решения — [DECISIONS](docs/DECISIONS.md) (актуальны D45–D61, D01–D44 — история).
|
||||
|
||||
Реализовано:
|
||||
|
||||
- 4 типа файлов (text/sound/photo/video), буфер приёма, папки и секции.
|
||||
- **10 апгрейдов** магазина, включая `SLA +24`; магазин открывается с часа 2
|
||||
и скрыт во время часа.
|
||||
- TCP-слоты и полоска соединения: три попадания; промах закрывает панель
|
||||
без автоповтора.
|
||||
- 4 атаки, но **час 1 проходит без атак**; junk/ddos — с часа 2,
|
||||
phishing — с 3, ransomware — с 4.
|
||||
- **SLA 128 / 200** на старте; потеря пакета: text 2 / sound 4 / photo 8 /
|
||||
video 16.
|
||||
- Мета-награды **постепенно по часам**: поиск после 3-го часа, щит после
|
||||
5-го, `complete_run` после 9-го; итоги показывают «ОТКРЫТО: …», прогресс
|
||||
живёт между забегами. `minigame_advanced` — резерв под будущий контент.
|
||||
- Поиск по имени в текущей папке после открытия; заголовок «N ИЗ M ОБЪЕКТОВ».
|
||||
- Автообновление папки/дерева, уникальные имена папок (`Имя`, `Имя 2`),
|
||||
удаление пустых папок, attached-галочки, скролл приёма до 8 пакетов.
|
||||
- «Уничтожить содержимое» папки удаляет файлы с подтверждением и −5 SLA;
|
||||
пустая папка удаляется без штрафа.
|
||||
- «Спокойный фон» — плоская бумага без dither; Esc на итогах забега ведёт
|
||||
в главное меню.
|
||||
- UI v2 «Kaleidos», маскот **БАЙТ** и короткий туториал первого часа.
|
||||
- Ambient машинного зала: 4 WAV, автозапуск после первого жеста, mute/пауза/
|
||||
громкость; генератор `audio/generate_ambient.py`.
|
||||
- Процедурный VHS только в главном меню (настройки → «ЭКРАН»): скан-линии,
|
||||
зерно, виньетка и полоса; рисуется кодом, не блокирует ввод, текст резкий.
|
||||
Игровой UI остаётся чистым, без CRT на весь интерфейс.
|
||||
- Handjet + Press Start 2P, 18 SFX + фоновый `hum`.
|
||||
- **9 часов**, длительность **120 → 360 с** (+30 с за час), старт **50 $**,
|
||||
**SLA 60/60** (кап +40 за уровень `lot_sla` до **220**), 1 слот (макс. 5),
|
||||
канал 10 (макс. 46), буфер 6 (макс. 12), таймер запроса 30 с (макс. 65),
|
||||
таймер пакета 20 с.
|
||||
- **4 типа файлов-полимино:** текст 1, звук 2, фото 3, видео 4 клетки;
|
||||
поворот `R`/ПКМ во время drag; сетка папки **5×4 → 6×4 → 7×5 → 8×5 →
|
||||
8×6 → 9×6** (до 54 клеток). Новая папка — **180 $**, всего до 6.
|
||||
- **Стол сортировки 4×6** в подготовке; файлы со стола нельзя прикрепить,
|
||||
на старте часа они автоматически возвращаются в папки (если места нет —
|
||||
час не начинается).
|
||||
- **Запросы на 1–4 конкретных именованных файла:** имена видны на карточке
|
||||
сеткой 2×2, прикрепление проверяется по точному `file_id`, потерянный файл
|
||||
помечается «ПОТЕРЯН», обязательные файлы жёстко зарезервированы. H1
|
||||
стартует с seed-файла и сразу с запроса на него. Отдельного `request_cap`
|
||||
нет: одновременно живут не больше `active_requests` запросов, новые не
|
||||
появляются в последние **10 с** часа; анти-голодание срабатывает только для
|
||||
первого запроса часа, дальше темп задаёт `send_interval`.
|
||||
- **Файлы — каталог, а не расходник:** успешная отправка только снимает
|
||||
привязку к запросу, файл остаётся в папке и может быть затребован снова.
|
||||
Приход задаётся только скоростью: окно спавна **75 %** часа, интервал —
|
||||
`spawn_window / hours[].files` с джиттером ±20 %, без переноса и общего
|
||||
лимита файлов (carry = 0).
|
||||
- **9 лотов магазина**; ровно 3 предложения, рероллы 40/80/120 (макс. 3 за
|
||||
подготовку), замок отдельного предложения. Магазин — с H2 и только в
|
||||
подготовке. Полное дерево — **8 825 $** (было 8 085 $ с 8 лотами), новый
|
||||
лот `lot_udp` — «UDP-повторитель».
|
||||
- **6 мини-игр отправки** (`dots`, `sound`, `text`, `video`, `photo`, `maze`;
|
||||
**3 ступени**) и **4 мини-игры атак** (`junk`, `ddos`, `phishing`,
|
||||
`ransomware`; 4 ступени); без таймеров, с шапкой «название + цель», полосой
|
||||
прогресса и экраном результата: имя игры, `q%`, полоса качества, вердикт,
|
||||
детали и `ПРОДОЛЖИТЬ`/`Esc`.
|
||||
- **TCP как отдельное соединение:** до `ПОДКЛЮЧИТЬСЯ` файлы в запрос не
|
||||
прикрепляются. Успех (3 попадания) занимает out-слот, в слоте — имя клиента
|
||||
и случайный IP; мини-игра отправки сама не открывается. `ОТПРАВИТЬ`
|
||||
запускает её, только когда прикреплены все файлы. После успешной отправки
|
||||
запрос исчезает, файлы возвращаются в папку и остаются доступны новым
|
||||
запросам, выплата начисляется, но TCP-слот остаётся занятым (idle) до
|
||||
ручного `ОТКЛЮЧИТЬ` на слоте. TCP платит **×1.15**.
|
||||
- **UDP без соединения:** время передачи **×0.8**, выплата **×0.85**, но
|
||||
успех — ролл в конце передачи: база **40 %**, +**10 %** за уровень
|
||||
`lot_udp`, максимум **80 %** (цены 60/120/210/350). Провал: **−3 SLA**, без
|
||||
выплаты и денежного штрафа, файлы не расходуются, запрос остаётся, повтор
|
||||
через **4 с**, слот освобождается. Файлы не расходуются ни при успешной
|
||||
отправке, ни при отмене/таймауте/отключении.
|
||||
- **Слоты общие для приёма и отправки:** `1 + slot_level`, максимум 5.
|
||||
TCP-соединение может занять единственный слот и заблокировать приём — это
|
||||
осознанное требование. Открепление файла — двойной клик по чипу.
|
||||
- **Шрифты:** тело — **Kechusilka Regular** (нормализован под Golos, fallback
|
||||
Golos Text), крупный текст — **DisplayOTF**, цифры/счётчики — **Press Start
|
||||
2P**; шкала 11/13/17/26.
|
||||
- **«ЖАРА»:** в меню-баре вместо часов 03:42 — `TemperatureGauge`
|
||||
(216×18, колба, деления, °C); поверх игры — `StressOverlay` с heat-моделью
|
||||
и alpha-only шейдером без чтения экрана. Пульс SLA/CRIT, тряска панелей
|
||||
≤2 px, «Снизить эффекты» (`reduce_motion`) отключает движение; в headless
|
||||
слой скрыт. Анимации карточек, слотов и drag — через `UiMotion` (Lane B).
|
||||
- **20 атак за забег** (`0,1,1,2,2,3,3,4,4`), 4 вида, 4 ступени; атака — это
|
||||
мини-игра, которая ставит час на паузу. Щит «Заземление» гасит атаку
|
||||
автоматически (**70 %** награды, без штрафа).
|
||||
- **Пошаговый туториал v3:** 5 шагов (пакет → прикрепление → TCP-соединение →
|
||||
мини-игра → SLA/атаки), пульсирующие рамки у цели и кнопка «ДАЛЕЕ».
|
||||
Прохождение хранится в `Game.stats["tutorial_done"]`; кнопка «ОБУЧЕНИЕ» в
|
||||
главном меню запускает повтор.
|
||||
- **Save VERSION 2**: позиции только в `folder.files`/`table`, метаданные в
|
||||
`files`; v1-сейвы мигрируют без потери файлов и без уменьшения сеток;
|
||||
офферы/рероллы/замки, стол и `tutorial_done` сохраняются.
|
||||
- Мета-награды после 3/5/9 часов, маскот **БАЙТ**, UI v3 «Kaleidos», ambient
|
||||
машинного зала, VHS только в главном меню.
|
||||
|
||||
Ограничения:
|
||||
|
||||
- Туториал — маскот и несколько подсказок, без пошагового сценария.
|
||||
- Сохранение в Web идёт через IndexedDB (`user://save.json`); проверяйте
|
||||
продолжение после перезагрузки вкладки.
|
||||
- Средний расход магазина в симуляторе — **44,9 %** дерева **8 825 $**;
|
||||
итог **7 205 $** в полосе **6 982–8 500 $**, поражений **0/240**.
|
||||
H2, H4 и H5 идут близко к нижней границе −20 % (−16,4 %, −15,3 %, −15,1 %),
|
||||
поэтому дальше ужесточать темп нельзя. Подробности —
|
||||
`tests/SIM_BALANCE_REPORT.md`.
|
||||
- Remote push ещё не выполнен; сборка для itch.io готовится локально.
|
||||
|
||||
## Управление
|
||||
@@ -60,20 +106,29 @@
|
||||
| --- | --- |
|
||||
| Выбор, меню, перетаскивание | ЛКМ |
|
||||
| Контекстное меню | ПКМ |
|
||||
| Пакет из «Приёма» → папка; файл → карточка запроса | drag&drop |
|
||||
| Поиск по имени в текущей папке | после открытия «Поиска» |
|
||||
| Мини-игра соединения: три попадания | ЛКМ или `Space` |
|
||||
| Мини-игра подготовки: отправить | `Enter` или кнопка |
|
||||
| Поворот фигуры во время drag | `R` или ПКМ |
|
||||
| Пакет из «Приёма» → папка | drag&drop |
|
||||
| Файл из папки → карточка запроса | drag&drop (TCP — только после `ПОДКЛЮЧИТЬСЯ`) |
|
||||
| Файл ↔ стол сортировки | drag&drop |
|
||||
| TCP-соединение | кнопка `ПОДКЛЮЧИТЬСЯ` справа снизу, 3 попадания в зелёную зону |
|
||||
| Отправка запроса | кнопка `ОТПРАВИТЬ` слева снизу |
|
||||
| Отклонить запрос | кнопка `ОТКЛОНИТЬ` слева снизу |
|
||||
| Закрыть TCP-соединение | `ОТКЛЮЧИТЬ` на слоте (мгновенно) |
|
||||
| Открепить файл от запроса | двойной клик по чипу |
|
||||
| TCP/UDP | переключатель на карточке запроса до соединения |
|
||||
| Мини-игра: действие | ЛКМ / `Space` / стрелки (по игре) |
|
||||
| `dots`: линия | нажать на «С» и вести курсор по клеткам |
|
||||
| Пропустить мини-игру | `ПРОПУСТИТЬ`/`СДАТЬСЯ` + второй клик |
|
||||
| Результат мини-игры | `ПРОДОЛЖИТЬ` или `Esc` |
|
||||
| Магазин: закрыть | `ЗАКРЫТЬ` или `Esc` |
|
||||
| Пауза | `Esc` |
|
||||
| Панель соединения: отмена (слот свободен, запрос остаётся) | `Esc` |
|
||||
|
||||
## Запуск
|
||||
|
||||
Нужен Godot 4.7.2. В этом окружении:
|
||||
Нужен Godot **4.7.2**. Команды выполняются из корня репозитория; ниже
|
||||
`GODOT` — путь к бинарю Godot.
|
||||
|
||||
```bash
|
||||
GODOT=/home/eq/techlb/tools/godot/Godot_v4.7.2-stable_linux.x86_64
|
||||
|
||||
# один раз после клона: импорт шрифтов и темы
|
||||
"$GODOT" --headless --path . --import
|
||||
|
||||
@@ -84,6 +139,13 @@ GODOT=/home/eq/techlb/tools/godot/Godot_v4.7.2-stable_linux.x86_64
|
||||
"$GODOT" -e --path .
|
||||
```
|
||||
|
||||
В Windows удобно задать путь один раз:
|
||||
|
||||
```powershell
|
||||
$env:GODOT_BIN = "D:\Apps\godot\Godot_v4.7.2-stable_win64_console.exe"
|
||||
& $env:GODOT_BIN --path .
|
||||
```
|
||||
|
||||
## Сборка Web
|
||||
|
||||
```bash
|
||||
@@ -97,20 +159,37 @@ mkdir -p build/web
|
||||
просмотр — только по HTTP:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8000 --directory build/web
|
||||
python -m http.server 8000 --directory build/web
|
||||
# http://localhost:8000
|
||||
```
|
||||
|
||||
Нужны export templates 4.7.2 (`web_nothreads_release.zip`).
|
||||
Нужны export templates 4.7.2 (`web_nothreads_release.zip`). Каталог шаблонов
|
||||
должен называться `4.7.2.stable`.
|
||||
|
||||
## Тесты
|
||||
|
||||
Полный прогон (21 набор + parse-check):
|
||||
Актуальный список — **полный набор + parse-check** в
|
||||
`scripts/run_all_tests.sh` / `scripts/run_all_tests.ps1` (см. также `tests/`;
|
||||
число наборов и проверок может меняться между волнами, поэтому здесь не
|
||||
хардкодится). Core-наборы (`test_hour`, `test_economy`, `test_save`,
|
||||
`test_slots`, `test_controller`, `test_integration`, `test_fuzz`,
|
||||
`test_full_playthrough`, `test_shop`, `test_shop_modal`) обновлены и зелёные;
|
||||
UI-наборы (`test_main_*`, `ui_smoke`) прогоняются отдельно.
|
||||
|
||||
```powershell
|
||||
# Windows
|
||||
powershell -ExecutionPolicy Bypass -File scripts/run_all_tests.ps1
|
||||
```
|
||||
|
||||
```bash
|
||||
# Linux / macOS
|
||||
bash scripts/run_all_tests.sh
|
||||
```
|
||||
|
||||
Windows-раннер сам ищет Godot (сначала `GODOT_BIN`, затем стандартный путь,
|
||||
затем `godot4`/`godot` из `PATH`); путь можно передать явно:
|
||||
`-GodotBin <path>`.
|
||||
|
||||
Точечно:
|
||||
|
||||
```bash
|
||||
@@ -120,32 +199,49 @@ bash scripts/run_all_tests.sh
|
||||
"$GODOT" --headless --path . --script scripts/ui/ui_smoke.gd
|
||||
```
|
||||
|
||||
Фактический итог: **22/22 PASS** (21 набор + parse-check); ключевые
|
||||
счётчики — `test_main_prep` 92/0, `ui_smoke` 135/0, `test_ambient` 74/0.
|
||||
Подробности — в
|
||||
[AI_REPORT](AI_REPORT.md).
|
||||
Баланс (240 сидов, отчёт `tests/SIM_BALANCE_REPORT.md`):
|
||||
|
||||
```bash
|
||||
python tests/sim_balance.py
|
||||
```
|
||||
|
||||
Отчёт `tests/SIM_BALANCE_REPORT.md` (240 сидов, 9 часов, `q=0.85`, TCP):
|
||||
средний итог **7 205 $** (payout 6 344 + `recv_fee` 177 + атаки 684) в полосе
|
||||
**6 982–8 500 $**, расход магазина **3 964 $** (**44,9 %** дерева **8 825 $**),
|
||||
**0 поражений из 240**, SLA в конце H9 — **64,9**, запросов выполнено — **62,0**,
|
||||
вердикт `PASS`, exit code **0**. H1 `income_target` — **100 $** (2-минутный
|
||||
час), H2, H4 и H5 близко к −20 %.
|
||||
Подробности — в [AI_REPORT](AI_REPORT.md).
|
||||
|
||||
E2E-прогоны на реальной сцене `Main` и веб-смоук проводились в предыдущей
|
||||
волне; после изменений TCP/UDP-модели и UI их нужно перепроверить перед
|
||||
публикацией. Финальный счёт UI-наборов здесь не хардкодится.
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
peak-hour/
|
||||
Game-JAM/
|
||||
├─ project.godot # 1920×1080 / 1280×720, GL Compatibility
|
||||
├─ export_presets.cfg # Web (no threads)
|
||||
├─ scenes/ # Main + dev-превью
|
||||
├─ scenes/ # Main + UI/мини-игры
|
||||
├─ scripts/
|
||||
│ ├─ main.gd # UI, ввод, модалки, drag&drop, туториал
|
||||
│ ├─ game_controller.gd # цикл menu → prep → hour → results
|
||||
│ ├─ run_all_tests.sh # полный headless-прогон
|
||||
│ ├─ run_all_tests.ps1/.sh # полный headless-прогон
|
||||
│ ├─ autoload/ # Game, Data, Audio, Rng, Ambient
|
||||
│ ├─ core/ # HourDirector, FileRegistry, SlotManager, Economy
|
||||
│ ├─ entities/ # файлы, пакеты, запросы, папки, слоты
|
||||
│ ├─ systems/ # атаки, магазин, сохранения
|
||||
│ ├─ ui/ # Theme90 v2, модалки, маскот, VHS-оверлей
|
||||
│ └─ minigames/ # ConnectionGame, MiniGame
|
||||
│ ├─ entities/ # файлы, папки, секции, пакеты, запросы, слоты
|
||||
│ ├─ systems/ # Shop, AttackDirector, SaveManager
|
||||
│ ├─ ui/ # Theme90, FileGrid, SortingTable, ShopModal, VHS,
|
||||
│ │ # TemperatureGauge, StressOverlay, UiMotion
|
||||
│ └─ minigames/ # MiniGame, factory, connection_game, send/, attack/
|
||||
├─ data/ # balance.json, strings_ru.json, names_ru.json
|
||||
├─ art/fonts/ # Handjet, Press Start 2P + OFL
|
||||
├─ art/fonts/ # Kechusilka, DisplayOTF, Golos fallback, Press Start 2P
|
||||
├─ art/icons/ # пиксельные иконки + generate_icons.py
|
||||
├─ audio/ # sfx/, music/, amb/, generate_*py
|
||||
├─ tests/ # headless-наборы
|
||||
├─ docs/ # GDD, DECISIONS, UI_REFRESH, screenshots
|
||||
├─ shaders/ # vhs.gdshader (только главное меню)
|
||||
├─ tests/ # headless-наборы, sim_balance.py
|
||||
├─ docs/ # GDD, DECISIONS, UI_REFRESH, скриншоты
|
||||
└─ build/ # артефакты экспорта (gitignored)
|
||||
```
|
||||
|
||||
@@ -153,23 +249,26 @@ peak-hour/
|
||||
|
||||
- Godot **4.7.2** (GL Compatibility).
|
||||
- Для Web: export templates 4.7.2, `web_nothreads_release.zip`.
|
||||
- Python 3 — только для пересборки SFX.
|
||||
- Python 3 — для баланса и пересборки SFX/ambient.
|
||||
- Никаких секретов и внешних сервисов.
|
||||
|
||||
## Документация
|
||||
|
||||
- [GDD](docs/GDD.md) — правила и числа v1.2.
|
||||
- [DECISIONS](docs/DECISIONS.md) — решения D01–D25.
|
||||
- [GDD](docs/GDD.md) — правила и числа v3.0.
|
||||
- [DECISIONS](docs/DECISIONS.md) — решения D01–D61 (актуальны D45–D61).
|
||||
- [AI_REPORT](AI_REPORT.md) — модели, приёмы и проверки.
|
||||
- [ASSETS](ASSETS.md) — шрифты, звук, лицензии и воспроизведение.
|
||||
- [UI_REFRESH](docs/UI_REFRESH.md) — UI v2 «Kaleidos».
|
||||
- [ASSETS](ASSETS.md) — шрифты, иконки, звук, лицензии и воспроизведение.
|
||||
- [UI_REFRESH](docs/UI_REFRESH.md) — UI v3 «Kaleidos».
|
||||
- [IMPLEMENTATION](docs/IMPLEMENTATION.md) и [TEST_PLAN](docs/TEST_PLAN.md) —
|
||||
контракты и матрица тестов.
|
||||
исторические контракты v1 и матрица тестов (v3-дельты — в начале
|
||||
IMPLEMENTATION).
|
||||
- [ITCH_PAGE](docs/ITCH_PAGE.md) — текст страницы itch.io.
|
||||
- [screenshots](docs/screenshots/) — кадры меню, подготовки, часа и атак.
|
||||
|
||||
## Лицензии
|
||||
|
||||
Код — **MIT** ([LICENSE](LICENSE)). Шрифты — **SIL OFL 1.1**
|
||||
(`art/fonts/OFL-*.txt`). UI и иконка — MIT. SFX и `hum.wav` — MIT/CC0,
|
||||
сгенерированы `audio/generate_sfx.py`. Подробности — в [ASSETS.md](ASSETS.md).
|
||||
Код — **MIT** ([LICENSE](LICENSE)). Шрифты: Golos Text и Press Start 2P —
|
||||
**SIL OFL 1.1**, Kechusilka — лицензия подтверждена владельцем проекта,
|
||||
DisplayOTF — **freeware/personal** (Denis A Serikov). UI, пиксельные иконки
|
||||
(`art/icons/`) и иконка — MIT. SFX и `hum.wav` — MIT/CC0, сгенерированы
|
||||
`audio/generate_sfx.py`. Подробности и тексты лицензий — в [ASSETS.md](ASSETS.md).
|
||||
@@ -0,0 +1 @@
|
||||
Copyright (c) Denis A Serikov, 2003-2006. All rights reserved. denis_box@mtu-net.ru
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://d26do8xk5bs24"
|
||||
path="res://.godot/imported/DisplayOTF.otf-521c0bcaa41177fabd074bc7be7bb37f.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/fonts/DisplayOTF.otf"
|
||||
dest_files=["res://.godot/imported/DisplayOTF.otf-521c0bcaa41177fabd074bc7be7bb37f.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cwms0uuyam0fk"
|
||||
path="res://.godot/imported/GolosText-VF.ttf-af72b844d15035464cce46e3ecf9f3ea.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/fonts/GolosText-VF.ttf"
|
||||
dest_files=["res://.godot/imported/GolosText-VF.ttf-af72b844d15035464cce46e3ecf9f3ea.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://blvusebgit70d"
|
||||
path="res://.godot/imported/Kechusilka-Regular.otf-b002f5d4ceda13c25fba42b2e2fc1ede.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/fonts/Kechusilka-Regular.otf"
|
||||
dest_files=["res://.godot/imported/Kechusilka-Regular.otf-b002f5d4ceda13c25fba42b2e2fc1ede.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -0,0 +1,34 @@
|
||||
-------------------------------------------------------------------------------------------------------------------------------------
|
||||
Пожалуйста, перед использованием, прочтите данное соглашение!
|
||||
-------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
====================================================================================
|
||||
|
||||
Лицензионное соглашение:
|
||||
|
||||
Используя этот шрифт, вы автоматически принимаете данное соглашение.
|
||||
Шрифт является бесплатным (freeware) и предоставляется по принципу "как есть",
|
||||
Следовательно, никаких гарантий со стороны производителя не прилагается
|
||||
и не предусматривается. Вы используете этот шрифт на свой страх и риск.
|
||||
Автор, не будет отвечать ни за какие потери или искажения данных, любую упущенную
|
||||
выгоду в процессе использования, или не правильного использования этого шрифта.
|
||||
Любая продажа шрифта или передача с целью получения дохода, запрещена.
|
||||
Автор оставляет за собой право собственности на данный продукт и возможность
|
||||
внесения любых изменений без предварительного уведомления и предупреждения.
|
||||
Декомпиляция шрифта, изменение формата, и внесение изменений в состав
|
||||
знаков - только с разрешения автора.
|
||||
В случае возникновения вопросов, свяжитесь со мной по E-mail:denis_box@mtu-net.ru
|
||||
|
||||
====================================================================================
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
Следует помнить, что все логотипы и торговые марки и зарегистрированные торговые
|
||||
марки принадлежат их законным владельцам.
|
||||
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
====================================================================================
|
||||
Этот шрифт был скачен с сайта www.otlab.ru
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2019 The Golos Text Project Authors (https://github.com/googlefonts/golos-text)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Нормализация Kechusilka Regular под метрики Golos Text.
|
||||
|
||||
Зачем: у исходного шрифта x-height 0.312 em против 0.530 em у Golos Text,
|
||||
то есть в одном кегле текст выглядел примерно на 40 % мельче. Из-за этого
|
||||
шкала Theme90 (11/13/17/26) и вся вёрстка при смене шрифта «поехали» бы.
|
||||
|
||||
Что делает: масштабирует всё, что задано в единицах шрифта, на
|
||||
scale = 0.530 * 2048 / 640 = 1.696
|
||||
— контуры, advance widths, side bearings, метрики hhea/OS-2/head и значения
|
||||
GPOS (кернинг). UPM остаётся 2048, поэтому файл выглядит обычным.
|
||||
|
||||
Хинтов в исходнике нет (0 stem-операторов на 173 глифа), так что пересборка
|
||||
чарстрингов через T2CharStringPen ничего не теряет.
|
||||
|
||||
Запуск:
|
||||
python art/fonts/normalize_kechusilka.py
|
||||
Нужен fontTools. Оригинал не перезаписывается.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.pens.t2CharStringPen import T2CharStringPen
|
||||
from fontTools.pens.transformPen import TransformPen
|
||||
from fontTools.misc.transform import Transform
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "source", "Kechusilka-Regular-original.otf")
|
||||
DST = os.path.join(HERE, "Kechusilka-Regular.otf")
|
||||
|
||||
TARGET_X_HEIGHT = 0.530 # как у Golos Text
|
||||
SCALE = TARGET_X_HEIGHT * 2048.0 / 640.0
|
||||
|
||||
|
||||
def scale_value_record(vr, s, counter):
|
||||
if vr is None:
|
||||
return
|
||||
for attr in ("XPlacement", "YPlacement", "XAdvance", "YAdvance"):
|
||||
v = getattr(vr, attr, None)
|
||||
if v is not None:
|
||||
setattr(vr, attr, s(v))
|
||||
counter[0] += 1
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.exists(SRC):
|
||||
raise SystemExit("нет исходника: %s" % SRC)
|
||||
print("scale = %.6f" % SCALE)
|
||||
|
||||
font = TTFont(SRC, recalcTimestamp=False)
|
||||
if font["head"].unitsPerEm != 2048:
|
||||
raise SystemExit("ожидался UPM 2048, получен %d" % font["head"].unitsPerEm)
|
||||
|
||||
def s(v):
|
||||
return int(round(v * SCALE))
|
||||
|
||||
# 1. контуры + ширины
|
||||
cff = font["CFF "].cff.topDictIndex[0]
|
||||
charstrings = cff.CharStrings
|
||||
glyph_set = font.getGlyphSet()
|
||||
private = cff.Private
|
||||
global_subrs = font["CFF "].cff.GlobalSubrs
|
||||
hmtx = font["hmtx"]
|
||||
|
||||
new_hmtx = {}
|
||||
for name in font.getGlyphOrder():
|
||||
old_adv, old_lsb = hmtx[name]
|
||||
pen = T2CharStringPen(s(old_adv), glyph_set)
|
||||
glyph_set[name].draw(TransformPen(pen, Transform(SCALE, 0, 0, SCALE, 0, 0)))
|
||||
cs = pen.getCharString(private=private, globalSubrs=global_subrs)
|
||||
cs.private = private
|
||||
charstrings[name] = cs
|
||||
new_hmtx[name] = (s(old_adv), s(old_lsb))
|
||||
font["hmtx"].metrics = new_hmtx
|
||||
|
||||
# 2. вертикальные метрики
|
||||
hhea = font["hhea"]
|
||||
hhea.ascent = s(hhea.ascent)
|
||||
hhea.descent = s(hhea.descent)
|
||||
hhea.lineGap = s(hhea.lineGap)
|
||||
|
||||
os2 = font["OS/2"]
|
||||
for attr in ("sTypoAscender", "sTypoDescender", "sTypoLineGap",
|
||||
"usWinAscent", "usWinDescent", "sxHeight", "sCapHeight",
|
||||
"ySubscriptXSize", "ySubscriptYSize", "ySubscriptXOffset",
|
||||
"ySubscriptYOffset", "ySuperscriptXSize", "ySuperscriptYSize",
|
||||
"ySuperscriptXOffset", "ySuperscriptYOffset",
|
||||
"yStrikeoutSize", "yStrikeoutPosition"):
|
||||
if hasattr(os2, attr):
|
||||
setattr(os2, attr, s(getattr(os2, attr)))
|
||||
|
||||
head = font["head"]
|
||||
head.xMin = s(head.xMin)
|
||||
head.yMin = s(head.yMin)
|
||||
head.xMax = s(head.xMax)
|
||||
head.yMax = s(head.yMax)
|
||||
|
||||
# 3. кернинг (GPOS): значения в единицах шрифта, иначе станут слабее на 40 %
|
||||
counter = [0]
|
||||
if "GPOS" in font and font["GPOS"].table.LookupList:
|
||||
for lookup in font["GPOS"].table.LookupList.Lookup:
|
||||
for st in lookup.SubTable:
|
||||
fmt = getattr(st, "Format", None)
|
||||
if lookup.LookupType == 2:
|
||||
if fmt == 1:
|
||||
for ps in st.PairSet:
|
||||
for rec in ps.PairValueRecord:
|
||||
scale_value_record(rec.Value1, s, counter)
|
||||
scale_value_record(rec.Value2, s, counter)
|
||||
elif fmt == 2:
|
||||
for c1 in st.Class1Record:
|
||||
for c2 in c1.Class2Record:
|
||||
scale_value_record(c2.Value1, s, counter)
|
||||
scale_value_record(c2.Value2, s, counter)
|
||||
elif lookup.LookupType == 1:
|
||||
if fmt == 1:
|
||||
scale_value_record(st.Value, s, counter)
|
||||
elif fmt == 2:
|
||||
for rec in st.Value:
|
||||
scale_value_record(rec, s, counter)
|
||||
|
||||
font.save(DST)
|
||||
print("GPOS полей масштабировано: %d" % counter[0])
|
||||
print("сохранено: %s (%d байт)" % (DST, os.path.getsize(DST)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://vsxr4ck4tuq8"
|
||||
path="res://.godot/imported/Kechusilka-Regular-original.otf-d2be4abf143de1d0b6c7f67898af1427.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/fonts/source/Kechusilka-Regular-original.otf"
|
||||
dest_files=["res://.godot/imported/Kechusilka-Regular-original.otf-d2be4abf143de1d0b6c7f67898af1427.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
@@ -1,17 +1,15 @@
|
||||
[gd_resource type="Theme" format=3]
|
||||
|
||||
[ext_resource type="FontFile" path="res://art/fonts/Handjet-VF.ttf" id="1_hjthv"]
|
||||
[ext_resource type="FontFile" path="res://art/fonts/Kechusilka-Regular.otf" id="1_cg7jb"]
|
||||
[ext_resource type="FontFile" path="res://art/fonts/GolosText-VF.ttf" id="1_fum4e"]
|
||||
[ext_resource type="FontFile" path="res://art/fonts/PressStart2P-Regular.ttf" id="2_fum4e"]
|
||||
[ext_resource type="FontFile" path="res://art/fonts/DisplayOTF.otf" id="3_dc2yr"]
|
||||
|
||||
[sub_resource type="FontVariation" id="FontVariation_fum4e"]
|
||||
base_font = ExtResource("1_hjthv")
|
||||
variation_opentype = {
|
||||
1162626898: 2.0,
|
||||
1162629960: 2.0,
|
||||
2003265652: 700.0
|
||||
}
|
||||
[sub_resource type="FontVariation" id="FontVariation_6m2gx"]
|
||||
fallbacks = Array[Font]([ExtResource("1_fum4e")])
|
||||
base_font = ExtResource("1_cg7jb")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lr7a5"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x3s7b"]
|
||||
bg_color = Color(0.913725, 0.901961, 0.866667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -24,7 +22,7 @@ corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cg7jb"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ukyte"]
|
||||
bg_color = Color(0, 0, 0, 0)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -37,7 +35,7 @@ corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dc2yr"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wjr02"]
|
||||
bg_color = Color(1, 1, 1, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -53,7 +51,7 @@ shadow_color = Color(0, 0, 0, 0.094)
|
||||
shadow_size = 3
|
||||
shadow_offset = Vector2(0, 2)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6m2gx"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4munx"]
|
||||
bg_color = Color(0.968627, 0.960784, 0.933333, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -69,7 +67,7 @@ shadow_color = Color(0, 0, 0, 0.094)
|
||||
shadow_size = 3
|
||||
shadow_offset = Vector2(0, 2)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x3s7b"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oc7r5"]
|
||||
bg_color = Color(0.847059, 0.827451, 0.788235, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -82,33 +80,9 @@ corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="Image" id="Image_ukyte"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 14,
|
||||
"mipmaps": false,
|
||||
"width": 14
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_wjr02"]
|
||||
image = SubResource("Image_ukyte")
|
||||
|
||||
[sub_resource type="Image" id="Image_4munx"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 14,
|
||||
"mipmaps": false,
|
||||
"width": 14
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_oc7r5"]
|
||||
image = SubResource("Image_4munx")
|
||||
|
||||
[sub_resource type="Image" id="Image_y0w1v"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 14,
|
||||
"mipmaps": false,
|
||||
@@ -120,7 +94,7 @@ image = SubResource("Image_y0w1v")
|
||||
|
||||
[sub_resource type="Image" id="Image_dywbi"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 14,
|
||||
"mipmaps": false,
|
||||
@@ -130,38 +104,38 @@ data = {
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_47b07"]
|
||||
image = SubResource("Image_dywbi")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_danrt"]
|
||||
[sub_resource type="Image" id="Image_danrt"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 14,
|
||||
"mipmaps": false,
|
||||
"width": 14
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_1j4f6"]
|
||||
image = SubResource("Image_danrt")
|
||||
|
||||
[sub_resource type="Image" id="Image_djhhc"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 65, 61, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 14,
|
||||
"mipmaps": false,
|
||||
"width": 14
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_a0hrt"]
|
||||
image = SubResource("Image_djhhc")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jvde3"]
|
||||
bg_color = Color(0, 0, 0, 0)
|
||||
border_color = Color(0, 0, 0, 0)
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="Image" id="Image_1j4f6"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 9,
|
||||
"mipmaps": false,
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_djhhc"]
|
||||
image = SubResource("Image_1j4f6")
|
||||
|
||||
[sub_resource type="Image" id="Image_a0hrt"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 9,
|
||||
"mipmaps": false,
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_jvde3"]
|
||||
image = SubResource("Image_a0hrt")
|
||||
|
||||
[sub_resource type="Image" id="Image_372il"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 9,
|
||||
"mipmaps": false,
|
||||
@@ -173,7 +147,7 @@ image = SubResource("Image_372il")
|
||||
|
||||
[sub_resource type="Image" id="Image_11awt"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 9,
|
||||
"mipmaps": false,
|
||||
@@ -183,7 +157,31 @@ data = {
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_loh4e"]
|
||||
image = SubResource("Image_11awt")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a1mnj"]
|
||||
[sub_resource type="Image" id="Image_a1mnj"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 9,
|
||||
"mipmaps": false,
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_4uvxq"]
|
||||
image = SubResource("Image_a1mnj")
|
||||
|
||||
[sub_resource type="Image" id="Image_petse"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 9,
|
||||
"mipmaps": false,
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_j2rjp"]
|
||||
image = SubResource("Image_petse")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7om8y"]
|
||||
bg_color = Color(0.603922, 0.584314, 0.541176, 0.3)
|
||||
border_color = Color(0, 0, 0, 0)
|
||||
corner_radius_top_left = 2
|
||||
@@ -192,7 +190,7 @@ corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4uvxq"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o55pb"]
|
||||
bg_color = Color(0.184314, 0.498039, 0.560784, 0.55)
|
||||
border_color = Color(0, 0, 0, 0)
|
||||
corner_radius_top_left = 2
|
||||
@@ -201,7 +199,7 @@ corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_petse"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wl44i"]
|
||||
bg_color = Color(0.847059, 0.827451, 0.788235, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -214,33 +212,9 @@ corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="Image" id="Image_j2rjp"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_7om8y"]
|
||||
image = SubResource("Image_j2rjp")
|
||||
|
||||
[sub_resource type="Image" id="Image_o55pb"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_wl44i"]
|
||||
image = SubResource("Image_o55pb")
|
||||
|
||||
[sub_resource type="Image" id="Image_mni32"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 131, 198, 212, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
@@ -250,7 +224,31 @@ data = {
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_8vjq1"]
|
||||
image = SubResource("Image_mni32")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w06qd"]
|
||||
[sub_resource type="Image" id="Image_w06qd"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 232, 230, 221, 255, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 65, 61, 52, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_barbq"]
|
||||
image = SubResource("Image_w06qd")
|
||||
|
||||
[sub_resource type="Image" id="Image_dvi42"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 47, 126, 142, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_foj1q"]
|
||||
image = SubResource("Image_dvi42")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jyq6k"]
|
||||
bg_color = Color(0.513726, 0.780392, 0.831373, 1)
|
||||
border_color = Color(0, 0, 0, 0)
|
||||
corner_radius_top_left = 2
|
||||
@@ -259,7 +257,7 @@ corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_barbq"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cjati"]
|
||||
bg_color = Color(0.184314, 0.498039, 0.560784, 1)
|
||||
border_color = Color(0, 0, 0, 0)
|
||||
corner_radius_top_left = 2
|
||||
@@ -268,7 +266,7 @@ corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dvi42"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m7381"]
|
||||
bg_color = Color(1, 1, 1, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -281,7 +279,7 @@ corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_foj1q"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_of6nr"]
|
||||
content_margin_left = 2.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 2.0
|
||||
@@ -298,7 +296,11 @@ corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jyq6k"]
|
||||
[sub_resource type="FontVariation" id="FontVariation_s7l1c"]
|
||||
fallbacks = Array[Font]([ExtResource("1_fum4e")])
|
||||
base_font = ExtResource("3_dc2yr")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7j4rs"]
|
||||
bg_color = Color(0.968627, 0.960784, 0.933333, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
@@ -314,7 +316,7 @@ shadow_color = Color(0, 0, 0, 0.094)
|
||||
shadow_size = 3
|
||||
shadow_offset = Vector2(0, 2)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cjati"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_05v8i"]
|
||||
content_margin_left = 2.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 2.0
|
||||
@@ -327,7 +329,7 @@ corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
corner_detail = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m7381"]
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7ph3p"]
|
||||
content_margin_left = 5.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
@@ -347,7 +349,7 @@ shadow_color = Color(0, 0, 0, 0.16)
|
||||
shadow_size = 6
|
||||
shadow_offset = Vector2(0, 3)
|
||||
|
||||
[sub_resource type="Image" id="Image_of6nr"]
|
||||
[sub_resource type="Image" id="Image_2b66g"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
@@ -356,10 +358,10 @@ data = {
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_s7l1c"]
|
||||
image = SubResource("Image_of6nr")
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_1tdcs"]
|
||||
image = SubResource("Image_2b66g")
|
||||
|
||||
[sub_resource type="Image" id="Image_7j4rs"]
|
||||
[sub_resource type="Image" id="Image_hv5yg"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
@@ -368,10 +370,10 @@ data = {
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_05v8i"]
|
||||
image = SubResource("Image_7j4rs")
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_yjsxn"]
|
||||
image = SubResource("Image_hv5yg")
|
||||
|
||||
[sub_resource type="Image" id="Image_7ph3p"]
|
||||
[sub_resource type="Image" id="Image_s5gth"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 154, 149, 137, 255, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 149, 137, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
@@ -380,10 +382,10 @@ data = {
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_2b66g"]
|
||||
image = SubResource("Image_7ph3p")
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_bkd55"]
|
||||
image = SubResource("Image_s5gth")
|
||||
|
||||
[sub_resource type="Image" id="Image_1tdcs"]
|
||||
[sub_resource type="Image" id="Image_xoku7"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 43, 91, 103, 255, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 91, 103, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
@@ -392,74 +394,74 @@ data = {
|
||||
"width": 9
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_hv5yg"]
|
||||
image = SubResource("Image_1tdcs")
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_d1bwe"]
|
||||
image = SubResource("Image_xoku7")
|
||||
|
||||
[resource]
|
||||
default_font = SubResource("FontVariation_fum4e")
|
||||
default_font_size = 12
|
||||
default_font = SubResource("FontVariation_6m2gx")
|
||||
default_font_size = 13
|
||||
Button/colors/font_color = Color(0.121569, 0.117647, 0.105882, 1)
|
||||
Button/colors/font_disabled_color = Color(0.603922, 0.584314, 0.541176, 1)
|
||||
Button/colors/font_hover_color = Color(0.172549, 0.360784, 0.407843, 1)
|
||||
Button/colors/font_pressed_color = Color(0.121569, 0.27451, 0.313726, 1)
|
||||
Button/font_sizes/font_size = 12
|
||||
Button/fonts/font = SubResource("FontVariation_fum4e")
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_lr7a5")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_cg7jb")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_dc2yr")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_6m2gx")
|
||||
Button/styles/pressed = SubResource("StyleBoxFlat_x3s7b")
|
||||
Button/font_sizes/font_size = 13
|
||||
Button/fonts/font = SubResource("FontVariation_6m2gx")
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_x3s7b")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_ukyte")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_wjr02")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_4munx")
|
||||
Button/styles/pressed = SubResource("StyleBoxFlat_oc7r5")
|
||||
CheckBox/colors/font_color = Color(0.121569, 0.117647, 0.105882, 1)
|
||||
CheckBox/colors/font_disabled_color = Color(0.603922, 0.584314, 0.541176, 1)
|
||||
CheckBox/colors/font_hover_color = Color(0.172549, 0.360784, 0.407843, 1)
|
||||
CheckBox/colors/font_pressed_color = Color(0.121569, 0.27451, 0.313726, 1)
|
||||
CheckBox/icons/checked = SubResource("ImageTexture_wjr02")
|
||||
CheckBox/icons/radio_checked = SubResource("ImageTexture_oc7r5")
|
||||
CheckBox/icons/radio_unchecked = SubResource("ImageTexture_8m7xh")
|
||||
CheckBox/icons/unchecked = SubResource("ImageTexture_47b07")
|
||||
CheckBox/styles/focus = SubResource("StyleBoxFlat_danrt")
|
||||
CheckBox/styles/hover = SubResource("StyleBoxFlat_danrt")
|
||||
CheckBox/styles/normal = SubResource("StyleBoxFlat_danrt")
|
||||
CheckBox/styles/pressed = SubResource("StyleBoxFlat_danrt")
|
||||
HScrollBar/icons/decrement = SubResource("ImageTexture_djhhc")
|
||||
HScrollBar/icons/decrement_highlight = SubResource("ImageTexture_jvde3")
|
||||
HScrollBar/icons/increment = SubResource("ImageTexture_i4ifm")
|
||||
HScrollBar/icons/increment_highlight = SubResource("ImageTexture_loh4e")
|
||||
HScrollBar/styles/grabber = SubResource("StyleBoxFlat_a1mnj")
|
||||
HScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_4uvxq")
|
||||
HScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_4uvxq")
|
||||
HScrollBar/styles/scroll = SubResource("StyleBoxFlat_petse")
|
||||
HScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_petse")
|
||||
CheckBox/icons/checked = SubResource("ImageTexture_8m7xh")
|
||||
CheckBox/icons/radio_checked = SubResource("ImageTexture_47b07")
|
||||
CheckBox/icons/radio_unchecked = SubResource("ImageTexture_1j4f6")
|
||||
CheckBox/icons/unchecked = SubResource("ImageTexture_a0hrt")
|
||||
CheckBox/styles/focus = SubResource("StyleBoxFlat_jvde3")
|
||||
CheckBox/styles/hover = SubResource("StyleBoxFlat_jvde3")
|
||||
CheckBox/styles/normal = SubResource("StyleBoxFlat_jvde3")
|
||||
CheckBox/styles/pressed = SubResource("StyleBoxFlat_jvde3")
|
||||
HScrollBar/icons/decrement = SubResource("ImageTexture_i4ifm")
|
||||
HScrollBar/icons/decrement_highlight = SubResource("ImageTexture_loh4e")
|
||||
HScrollBar/icons/increment = SubResource("ImageTexture_4uvxq")
|
||||
HScrollBar/icons/increment_highlight = SubResource("ImageTexture_j2rjp")
|
||||
HScrollBar/styles/grabber = SubResource("StyleBoxFlat_7om8y")
|
||||
HScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_o55pb")
|
||||
HScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_o55pb")
|
||||
HScrollBar/styles/scroll = SubResource("StyleBoxFlat_wl44i")
|
||||
HScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_wl44i")
|
||||
HSlider/constants/grabber_offset = 0
|
||||
HSlider/icons/grabber = SubResource("ImageTexture_7om8y")
|
||||
HSlider/icons/grabber_disabled = SubResource("ImageTexture_wl44i")
|
||||
HSlider/icons/grabber_highlight = SubResource("ImageTexture_8vjq1")
|
||||
HSlider/styles/grabber_area = SubResource("StyleBoxFlat_w06qd")
|
||||
HSlider/styles/grabber_area_highlight = SubResource("StyleBoxFlat_barbq")
|
||||
HSlider/styles/slider = SubResource("StyleBoxFlat_petse")
|
||||
HSlider/icons/grabber = SubResource("ImageTexture_8vjq1")
|
||||
HSlider/icons/grabber_disabled = SubResource("ImageTexture_barbq")
|
||||
HSlider/icons/grabber_highlight = SubResource("ImageTexture_foj1q")
|
||||
HSlider/styles/grabber_area = SubResource("StyleBoxFlat_jyq6k")
|
||||
HSlider/styles/grabber_area_highlight = SubResource("StyleBoxFlat_cjati")
|
||||
HSlider/styles/slider = SubResource("StyleBoxFlat_wl44i")
|
||||
HeadLabel/base_type = &"Label"
|
||||
HeadLabel/font_sizes/font_size = 16
|
||||
HeadLabel/fonts/font = SubResource("FontVariation_fum4e")
|
||||
HeadLabel/font_sizes/font_size = 17
|
||||
HeadLabel/fonts/font = SubResource("FontVariation_6m2gx")
|
||||
Label/colors/font_color = Color(0.121569, 0.117647, 0.105882, 1)
|
||||
Label/font_sizes/font_size = 12
|
||||
Label/fonts/font = SubResource("FontVariation_fum4e")
|
||||
Label/font_sizes/font_size = 13
|
||||
Label/fonts/font = SubResource("FontVariation_6m2gx")
|
||||
LineEdit/colors/font_color = Color(0.121569, 0.117647, 0.105882, 1)
|
||||
LineEdit/colors/font_placeholder_color = Color(0.603922, 0.584314, 0.541176, 1)
|
||||
LineEdit/font_sizes/font_size = 12
|
||||
LineEdit/fonts/font = SubResource("FontVariation_fum4e")
|
||||
LineEdit/styles/focus = SubResource("StyleBoxFlat_dvi42")
|
||||
LineEdit/styles/normal = SubResource("StyleBoxFlat_foj1q")
|
||||
LineEdit/font_sizes/font_size = 13
|
||||
LineEdit/fonts/font = SubResource("FontVariation_6m2gx")
|
||||
LineEdit/styles/focus = SubResource("StyleBoxFlat_m7381")
|
||||
LineEdit/styles/normal = SubResource("StyleBoxFlat_of6nr")
|
||||
LogoLabel/base_type = &"Label"
|
||||
LogoLabel/colors/font_color = Color(0.172549, 0.360784, 0.407843, 1)
|
||||
LogoLabel/font_sizes/font_size = 24
|
||||
LogoLabel/fonts/font = ExtResource("2_fum4e")
|
||||
LogoLabel/font_sizes/font_size = 26
|
||||
LogoLabel/fonts/font = SubResource("FontVariation_s7l1c")
|
||||
MicroLabel/base_type = &"Label"
|
||||
MicroLabel/colors/font_color = Color(0.172549, 0.164706, 0.14902, 1)
|
||||
MicroLabel/font_sizes/font_size = 10
|
||||
MicroLabel/font_sizes/font_size = 11
|
||||
NumLabel/base_type = &"Label"
|
||||
NumLabel/font_sizes/font_size = 12
|
||||
NumLabel/font_sizes/font_size = 13
|
||||
NumLabel/fonts/font = ExtResource("2_fum4e")
|
||||
Panel/styles/panel = SubResource("StyleBoxFlat_jyq6k")
|
||||
Panel/styles/panel = SubResource("StyleBoxFlat_7j4rs")
|
||||
PopupMenu/colors/font_accelerator_color = Color(0.603922, 0.584314, 0.541176, 1)
|
||||
PopupMenu/colors/font_color = Color(0.121569, 0.117647, 0.105882, 1)
|
||||
PopupMenu/colors/font_disabled_color = Color(0.603922, 0.584314, 0.541176, 1)
|
||||
@@ -471,20 +473,20 @@ PopupMenu/constants/item_end_padding = 12
|
||||
PopupMenu/constants/item_start_padding = 8
|
||||
PopupMenu/constants/separator_outline_size = 0
|
||||
PopupMenu/constants/v_separation = 3
|
||||
PopupMenu/font_sizes/font_size = 12
|
||||
PopupMenu/fonts/font = SubResource("FontVariation_fum4e")
|
||||
PopupMenu/styles/hover = SubResource("StyleBoxFlat_cjati")
|
||||
PopupMenu/styles/panel = SubResource("StyleBoxFlat_m7381")
|
||||
PopupMenu/font_sizes/font_size = 13
|
||||
PopupMenu/fonts/font = SubResource("FontVariation_6m2gx")
|
||||
PopupMenu/styles/hover = SubResource("StyleBoxFlat_05v8i")
|
||||
PopupMenu/styles/panel = SubResource("StyleBoxFlat_7ph3p")
|
||||
TooltipLabel/colors/font_color = Color(0.121569, 0.117647, 0.105882, 1)
|
||||
TooltipLabel/font_sizes/font_size = 10
|
||||
TooltipLabel/fonts/font = SubResource("FontVariation_fum4e")
|
||||
TooltipPanel/styles/panel = SubResource("StyleBoxFlat_m7381")
|
||||
VScrollBar/icons/decrement = SubResource("ImageTexture_s7l1c")
|
||||
VScrollBar/icons/decrement_highlight = SubResource("ImageTexture_05v8i")
|
||||
VScrollBar/icons/increment = SubResource("ImageTexture_2b66g")
|
||||
VScrollBar/icons/increment_highlight = SubResource("ImageTexture_hv5yg")
|
||||
VScrollBar/styles/grabber = SubResource("StyleBoxFlat_a1mnj")
|
||||
VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_4uvxq")
|
||||
VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_4uvxq")
|
||||
VScrollBar/styles/scroll = SubResource("StyleBoxFlat_petse")
|
||||
VScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_petse")
|
||||
TooltipLabel/font_sizes/font_size = 11
|
||||
TooltipLabel/fonts/font = SubResource("FontVariation_6m2gx")
|
||||
TooltipPanel/styles/panel = SubResource("StyleBoxFlat_7ph3p")
|
||||
VScrollBar/icons/decrement = SubResource("ImageTexture_1tdcs")
|
||||
VScrollBar/icons/decrement_highlight = SubResource("ImageTexture_yjsxn")
|
||||
VScrollBar/icons/increment = SubResource("ImageTexture_bkd55")
|
||||
VScrollBar/icons/increment_highlight = SubResource("ImageTexture_d1bwe")
|
||||
VScrollBar/styles/grabber = SubResource("StyleBoxFlat_7om8y")
|
||||
VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_o55pb")
|
||||
VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_o55pb")
|
||||
VScrollBar/styles/scroll = SubResource("StyleBoxFlat_wl44i")
|
||||
VScrollBar/styles/scroll_focus = SubResource("StyleBoxFlat_wl44i")
|
||||
|
After Width: | Height: | Size: 350 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://das8asb4b05gm"
|
||||
path="res://.godot/imported/file_icons.png-aadd037746f865f0fe6757930f0f608c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/file_icons.png"
|
||||
dest_files=["res://.godot/imported/file_icons.png-aadd037746f865f0fe6757930f0f608c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://djb7w10tao8ns"
|
||||
path="res://.godot/imported/file_photo.png-b23698027d48c718a6fc500f302db87b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/file_photo.png"
|
||||
dest_files=["res://.godot/imported/file_photo.png-b23698027d48c718a6fc500f302db87b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 274 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dw5oie5dxfky4"
|
||||
path="res://.godot/imported/file_sound.png-ef9804b30db6fa708a74022e1489cfcd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/file_sound.png"
|
||||
dest_files=["res://.godot/imported/file_sound.png-ef9804b30db6fa708a74022e1489cfcd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 211 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://gtofenp8bd1n"
|
||||
path="res://.godot/imported/file_text.png-e2cb12a8276ae34819d095735a1e4828.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/file_text.png"
|
||||
dest_files=["res://.godot/imported/file_text.png-e2cb12a8276ae34819d095735a1e4828.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 280 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c8yfny20jcs15"
|
||||
path="res://.godot/imported/file_video.png-ffcbd0629f5e28094e97a5a051839521.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/file_video.png"
|
||||
dest_files=["res://.godot/imported/file_video.png-ffcbd0629f5e28094e97a5a051839521.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 194 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cb7ge0n5gg8mu"
|
||||
path="res://.godot/imported/folder.png-2d34cb1a6aa9dbae664638a5df628136.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/folder.png"
|
||||
dest_files=["res://.godot/imported/folder.png-2d34cb1a6aa9dbae664638a5df628136.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""LEGACY: старый генератор иконок «в стиле Kaleidos» (32×32 RGBA PNG).
|
||||
|
||||
Актуальный пайплайн — `art/icons/import_tileset.py`: он собирает
|
||||
`file_icons.png` (128×128, 8×8) и шесть отдельных иконок из исходного
|
||||
тайлсета `art/icons/source/tileset1.png`. Этот скрипт оставлен как
|
||||
legacy/альтернативный стиль; не запускайте его после импорта, иначе он
|
||||
перезапишет шесть PNG старыми векторными иконками.
|
||||
|
||||
Стиль: плоские заливки из палитры Kaleidos, контур 1 px #2C2A26,
|
||||
без сглаживания (nearest). Иконки читаются в размерах 16/24/32 px.
|
||||
|
||||
Запуск:
|
||||
python3 art/icons/generate_icons.py
|
||||
python3 art/icons/generate_icons.py --preview /tmp/opencode/icons_preview.png
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
SIZE = 32
|
||||
|
||||
# --- палитра Kaleidos ------------------------------------------------------
|
||||
OUTLINE = (44, 42, 38, 255) # #2C2A26
|
||||
NAVY = (44, 92, 104, 255) # #2C5C68
|
||||
TEAL = (47, 127, 143, 255) # #2F7F8F
|
||||
TEAL_LIGHT = (131, 199, 212, 255) # #83C7D4
|
||||
TEAL_PALE = (230, 245, 247, 255) # #E6F5F7
|
||||
WARN = (197, 138, 46, 255) # #C58A2E
|
||||
SUCCESS = (63, 143, 94, 255) # #3F8F5E
|
||||
ERROR = (194, 91, 74, 255) # #C25B4A
|
||||
PAPER = (247, 245, 238, 255) # #F7F5EE
|
||||
PAPER_LO = (216, 211, 201, 255) # #D8D3C9
|
||||
WHITE = (255, 255, 255, 255)
|
||||
|
||||
|
||||
def canvas() -> Image.Image:
|
||||
return Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
|
||||
|
||||
|
||||
def shape(d: ImageDraw.ImageDraw, pts: list[tuple[int, int]], fill, outline=OUTLINE) -> None:
|
||||
"""Заливка многоугольника + контур 1 px по его периметру."""
|
||||
d.polygon(pts, fill=fill)
|
||||
if outline is not None:
|
||||
d.line(pts + [pts[0]], fill=outline, width=1, joint="curve")
|
||||
|
||||
|
||||
def box(d: ImageDraw.ImageDraw, xy: tuple[int, int, int, int], fill, outline=OUTLINE) -> None:
|
||||
"""
|
||||
Прямоугольник с координатами включительно (как у ImageDraw.rectangle).
|
||||
"""
|
||||
d.rectangle(xy, fill=fill)
|
||||
if outline is not None:
|
||||
d.rectangle(xy, outline=outline)
|
||||
|
||||
|
||||
def file_text() -> Image.Image:
|
||||
img = canvas()
|
||||
d = ImageDraw.Draw(img)
|
||||
# страница с отрезанным верхним правым углом
|
||||
page = [(7, 4), (19, 4), (25, 10), (25, 27), (7, 27)]
|
||||
shape(d, page, PAPER)
|
||||
# загнутый уголок
|
||||
shape(d, [(19, 4), (25, 10), (19, 10)], PAPER_LO)
|
||||
# три строки текста
|
||||
d.rectangle((10, 13, 22, 14), fill=NAVY)
|
||||
d.rectangle((10, 17, 22, 18), fill=NAVY)
|
||||
d.rectangle((10, 21, 18, 22), fill=NAVY)
|
||||
return img
|
||||
|
||||
|
||||
def file_sound() -> Image.Image:
|
||||
img = canvas()
|
||||
d = ImageDraw.Draw(img)
|
||||
# янтарный бейдж со срезанными углами
|
||||
badge = [(6, 3), (26, 3), (29, 6), (29, 26), (26, 29), (6, 29), (3, 26), (3, 6)]
|
||||
shape(d, badge, WARN)
|
||||
# восьмая нота: головка, штиль, флажок
|
||||
head = [(9, 21), (11, 19), (15, 19), (17, 21), (17, 24), (15, 26), (11, 26), (9, 24)]
|
||||
shape(d, head, OUTLINE)
|
||||
d.rectangle((16, 8, 17, 21), fill=OUTLINE)
|
||||
shape(d, [(17, 8), (23, 11), (23, 14), (20, 14), (20, 13), (17, 12)], OUTLINE)
|
||||
return img
|
||||
|
||||
|
||||
def file_photo() -> Image.Image:
|
||||
img = canvas()
|
||||
d = ImageDraw.Draw(img)
|
||||
# тёмная рамка
|
||||
box(d, (3, 6, 28, 26), NAVY)
|
||||
# светлое поле кадра
|
||||
box(d, (6, 9, 25, 23), TEAL_PALE)
|
||||
# дальняя и ближняя горы
|
||||
shape(d, [(6, 23), (13, 12), (19, 23)], TEAL_LIGHT, outline=None)
|
||||
shape(d, [(11, 23), (18, 14), (25, 23)], SUCCESS, outline=None)
|
||||
# солнце
|
||||
d.rectangle((20, 11, 22, 13), fill=WARN)
|
||||
return img
|
||||
|
||||
|
||||
def file_video() -> Image.Image:
|
||||
img = canvas()
|
||||
d = ImageDraw.Draw(img)
|
||||
# тело плёнки
|
||||
box(d, (3, 8, 28, 24), NAVY)
|
||||
# перфорация сверху и снизу
|
||||
for x in (5, 9, 21, 25):
|
||||
d.rectangle((x, 10, x + 1, 11), fill=PAPER)
|
||||
d.rectangle((x, 21, x + 1, 22), fill=PAPER)
|
||||
# play
|
||||
shape(d, [(13, 11), (22, 16), (13, 21)], PAPER)
|
||||
return img
|
||||
|
||||
|
||||
def folder() -> Image.Image:
|
||||
img = canvas()
|
||||
d = ImageDraw.Draw(img)
|
||||
body = [(3, 27), (3, 9), (13, 9), (16, 12), (29, 12), (29, 27)]
|
||||
shape(d, body, WARN)
|
||||
# светлая кромка передней стенки
|
||||
d.line([(5, 25), (27, 25)], fill=(226, 175, 91, 255), width=1)
|
||||
return img
|
||||
|
||||
|
||||
def packet() -> Image.Image:
|
||||
img = canvas()
|
||||
d = ImageDraw.Draw(img)
|
||||
# конверт
|
||||
box(d, (3, 10, 28, 25), PAPER)
|
||||
# клапан
|
||||
shape(d, [(4, 11), (16, 19), (27, 11)], TEAL_PALE, outline=None)
|
||||
d.line([(4, 11), (16, 19)], fill=NAVY, width=1)
|
||||
d.line([(16, 19), (27, 11)], fill=NAVY, width=1)
|
||||
# нижние сгибы
|
||||
d.line([(4, 24), (12, 18)], fill=PAPER_LO, width=1)
|
||||
d.line([(27, 24), (19, 18)], fill=PAPER_LO, width=1)
|
||||
# входящий пакет: teal-бейдж со стрелкой вниз
|
||||
shape(d, [(21, 19), (29, 19), (29, 27), (21, 27)], TEAL)
|
||||
d.rectangle((24, 21, 25, 23), fill=WHITE)
|
||||
d.polygon([(22, 23), (27, 23), (24, 26)], fill=WHITE)
|
||||
return img
|
||||
|
||||
|
||||
ICONS = {
|
||||
"file_text": file_text,
|
||||
"file_sound": file_sound,
|
||||
"file_photo": file_photo,
|
||||
"file_video": file_video,
|
||||
"folder": folder,
|
||||
"packet": packet,
|
||||
}
|
||||
|
||||
# Проверка: только полностью прозрачные или полностью непрозрачные пиксели.
|
||||
# Это гарантирует отсутствие сглаживания по краям.
|
||||
def assert_hard_edges(img: Image.Image, name: str) -> None:
|
||||
alpha = img.getchannel("A")
|
||||
values = set(alpha.getdata())
|
||||
assert values <= {0, 255}, f"{name}: найден полупрозрачный пиксель {values}"
|
||||
|
||||
|
||||
def build_preview(path: Path) -> None:
|
||||
"""Контактный лист: все иконки в 16/24/32 px + увеличение 32 px ×4."""
|
||||
scale = 4
|
||||
cell_w = 200
|
||||
cell_h = 236
|
||||
cols = 3
|
||||
sheet = Image.new("RGBA", (cols * cell_w, 2 * cell_h), (233, 230, 221, 255))
|
||||
d = ImageDraw.Draw(sheet)
|
||||
try:
|
||||
font = ImageFont.truetype("/usr/share/fonts/dejavu/DejaVuSans.ttf", 15)
|
||||
small = ImageFont.truetype("/usr/share/fonts/dejavu/DejaVuSans.ttf", 11)
|
||||
except OSError: # pragma: no cover - depends on system fonts
|
||||
font = ImageFont.load_default()
|
||||
small = font
|
||||
|
||||
for index, (name, factory) in enumerate(ICONS.items()):
|
||||
col = index % cols
|
||||
row = index // cols
|
||||
x0 = col * cell_w
|
||||
y0 = row * cell_h
|
||||
d.rectangle((x0 + 4, y0 + 4, x0 + cell_w - 6, y0 + cell_h - 6),
|
||||
fill=(247, 245, 238, 255), outline=(154, 149, 137, 255))
|
||||
d.text((x0 + 12, y0 + 10), name, fill=(31, 30, 27, 255), font=font)
|
||||
icon = factory()
|
||||
|
||||
# три реальных размера в ряд, каждый на белой «карточке»
|
||||
cx = x0 + 12
|
||||
for px in (16, 24, 32):
|
||||
d.rectangle((cx - 2, y0 + 44, cx + px + 1, y0 + 47 + px),
|
||||
fill=(255, 255, 255, 255), outline=(216, 211, 201, 255))
|
||||
sheet.alpha_composite(icon.resize((px, px), Image.Resampling.NEAREST),
|
||||
(cx, y0 + 46))
|
||||
d.text((cx, y0 + 52 + px), f"{px}", fill=(90, 86, 78, 255), font=small)
|
||||
cx += 42
|
||||
|
||||
# увеличение 32 px ×4 — чтобы видеть пиксели и контур
|
||||
big = icon.resize((SIZE * scale, SIZE * scale), Image.Resampling.NEAREST)
|
||||
sheet.alpha_composite(big, (x0 + 34, y0 + 92))
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
sheet.save(path)
|
||||
print(f"preview saved: {path}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--preview", type=Path, default=None,
|
||||
help="куда сохранить контактный лист (PNG)")
|
||||
args = parser.parse_args()
|
||||
|
||||
out_dir = Path(__file__).resolve().parent
|
||||
for name, factory in ICONS.items():
|
||||
img = factory()
|
||||
assert_hard_edges(img, name)
|
||||
target = out_dir / f"{name}.png"
|
||||
img.save(target)
|
||||
print(f"saved {target.relative_to(out_dir.parent.parent)} {img.size}")
|
||||
|
||||
if args.preview is not None:
|
||||
build_preview(args.preview)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Import the project icon pack from ``art/icons/source/tileset1.png``.
|
||||
|
||||
Source
|
||||
------
|
||||
``source/tileset1.png`` is the user-provided ``tileset1 (1).png`` (128x128
|
||||
RGBA, transparent background, hard alpha 0/255). It is an 8x8 grid of
|
||||
16x16 tiles, 64 icons total, and is kept in the repository so the build is
|
||||
reproducible.
|
||||
|
||||
Licence of the source pack is **unknown / not supplied with the file**.
|
||||
Confirm it with the project owner before publishing or selling the game.
|
||||
The previous icon set was generated in-house (MIT); this import does not
|
||||
change the licence of the source artwork itself.
|
||||
|
||||
What it writes
|
||||
--------------
|
||||
* ``art/icons/file_icons.png`` -- 128x128 atlas, 8x8 cells of 16 px, ids
|
||||
0..63 in row-major order (identical geometry to ``FileIcons``).
|
||||
* ``file_text.png`` / ``file_sound.png`` / ``file_photo.png`` /
|
||||
``file_video.png`` / ``folder.png`` / ``packet.png`` -- 32x32 fallback
|
||||
icons, each a nearest-neighbour 2x upscale of one source tile.
|
||||
* ``source/tileset1_preview.png`` -- numbered contact sheet of the source.
|
||||
* ``source/file_icons_preview.png`` -- atlas at 1x/1.5x/2x/4x (cells
|
||||
16/24/32/64 px) and the six fallbacks at 16/24/32 px plus 4x.
|
||||
|
||||
Only nearest-neighbour resampling is used. No anti-aliased or
|
||||
semi-transparent pixels are produced by this script.
|
||||
|
||||
Usage
|
||||
-----
|
||||
python3 art/icons/import_tileset.py
|
||||
python3 art/icons/import_tileset.py --no-preview
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
BASE = Path(__file__).resolve().parent
|
||||
SOURCE = BASE / "source" / "tileset1.png"
|
||||
ATLAS = BASE / "file_icons.png"
|
||||
|
||||
TILE = 16
|
||||
COLS = 8
|
||||
ROWS = 8
|
||||
COUNT = COLS * ROWS
|
||||
SINGLE_SIZE = 32
|
||||
|
||||
# Atlas id (0..63) -> source tile id (0..63). The source is already a
|
||||
# perfect 8x8/16 px sheet, so the safest mapping keeps every tile in place.
|
||||
# Change this tuple only together with FileIcons.COUNT/COLS/ROWS and
|
||||
# FileRegistry.FILE_ICON_COUNT.
|
||||
ATLAS_SLOTS: tuple[int, ...] = tuple(range(COUNT))
|
||||
|
||||
# Human-readable names, in source tile order (row-major). Used by the
|
||||
# preview and by the mapping report; not used at runtime.
|
||||
TILE_NAMES: tuple[str, ...] = (
|
||||
# row 0 -- documents and a bolt
|
||||
"page_white", "page_blue", "page_green", "page_orange",
|
||||
"page_red", "page_purple", "page_lined", "bolt",
|
||||
# row 1 -- fruit
|
||||
"strawberry", "watermelon", "cherries", "banana",
|
||||
"grapes", "orange_slice", "pineapple", "pear",
|
||||
# row 2 -- valuables, heart, ghosts
|
||||
"gem_blue", "gem_silver", "heart", "clover",
|
||||
"ghost_cyan", "ghost_purple", "ghost_white", "ghost_yellow",
|
||||
# row 3 -- media, star, folders
|
||||
"note", "star", "folder_cream", "folder_pink",
|
||||
"folder_green_face", "folder_blue_star", "folder_tan",
|
||||
"folder_green_confetti",
|
||||
# row 4 -- FX, gift, key, bomb, envelopes
|
||||
"sparkle", "gift", "key", "bomb",
|
||||
"envelope_pink", "envelope_green", "envelope_peach", "envelope_cyan",
|
||||
# row 5 -- crown, snowman, tree, gear, hat, check, whale, smiley
|
||||
"crown", "snowman", "tree", "gear",
|
||||
"wizard_hat", "check", "whale", "smiley",
|
||||
# row 6 -- potions, shield, animals
|
||||
"potion_red", "potion_blue", "potion_green", "potion_yellow",
|
||||
"shield_plus", "cat_black", "fox_orange", "rabbit_pink",
|
||||
# row 7 -- orb, bulb, devices, UI buttons
|
||||
"crystal_ball", "lightbulb", "monitor", "headphones",
|
||||
"sync", "frame_pink", "no_entry", "close",
|
||||
)
|
||||
|
||||
# Type fallback icons drawn by Theme90 when a save has icon_id = -1.
|
||||
# Mapping rationale:
|
||||
# text -> lined page (reads as a document at 16 px)
|
||||
# sound -> music note (direct match)
|
||||
# photo -> pink picture frame (no camera/photo tile in this pack)
|
||||
# video -> monitor/screen (no film/play tile in this pack)
|
||||
# folder -> cream folder (direct match)
|
||||
# packet -> green envelope (direct match, incoming mail)
|
||||
SINGLE_ICONS: dict[str, int] = {
|
||||
"file_text": 6,
|
||||
"file_sound": 24,
|
||||
"file_photo": 61,
|
||||
"file_video": 58,
|
||||
"folder": 26,
|
||||
"packet": 37,
|
||||
}
|
||||
|
||||
|
||||
def _font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
||||
for path in (
|
||||
"C:/Windows/Fonts/arialbd.ttf",
|
||||
"C:/Windows/Fonts/segoeui.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
):
|
||||
try:
|
||||
return ImageFont.truetype(path, size)
|
||||
except OSError:
|
||||
continue
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def assert_hard_alpha(img: Image.Image, label: str) -> None:
|
||||
"""Fail if the image has anti-aliased/partial alpha pixels."""
|
||||
hist = img.getchannel("A").histogram()
|
||||
partial = sum(hist[1:255])
|
||||
if partial != 0:
|
||||
raise SystemExit(f"{label}: {partial} semi-transparent pixels")
|
||||
|
||||
|
||||
def load_source(path: Path) -> Image.Image:
|
||||
if not path.is_file():
|
||||
raise SystemExit(f"source not found: {path}")
|
||||
img = Image.open(path).convert("RGBA")
|
||||
expected = (COLS * TILE, ROWS * TILE)
|
||||
if img.size != expected:
|
||||
raise SystemExit(
|
||||
f"{path}: expected {expected[0]}x{expected[1]} "
|
||||
f"(8x8 x 16 px), got {img.size[0]}x{img.size[1]}"
|
||||
)
|
||||
assert_hard_alpha(img, str(path))
|
||||
empty = [i for i in range(COUNT) if not _tile(img, i).getchannel("A").getbbox()]
|
||||
if empty:
|
||||
raise SystemExit(f"{path}: empty tiles at ids {empty}")
|
||||
return img
|
||||
|
||||
|
||||
def _tile(img: Image.Image, tile_id: int) -> Image.Image:
|
||||
if not 0 <= tile_id < COUNT:
|
||||
raise ValueError(f"tile id out of range: {tile_id}")
|
||||
col = tile_id % COLS
|
||||
row = tile_id // COLS
|
||||
return img.crop((col * TILE, row * TILE, (col + 1) * TILE, (row + 1) * TILE))
|
||||
|
||||
|
||||
def build_atlas(source: Image.Image) -> Image.Image:
|
||||
atlas = Image.new("RGBA", (COLS * TILE, ROWS * TILE), (0, 0, 0, 0))
|
||||
for slot, tile_id in enumerate(ATLAS_SLOTS):
|
||||
col = slot % COLS
|
||||
row = slot // COLS
|
||||
atlas.alpha_composite(_tile(source, tile_id), (col * TILE, row * TILE))
|
||||
return atlas
|
||||
|
||||
|
||||
def build_single(source: Image.Image, tile_id: int) -> Image.Image:
|
||||
return _tile(source, tile_id).resize(
|
||||
(SINGLE_SIZE, SINGLE_SIZE), Image.Resampling.NEAREST
|
||||
)
|
||||
|
||||
|
||||
def _checker(draw: ImageDraw.ImageDraw, box: tuple[int, int, int, int],
|
||||
cell: int = 8) -> None:
|
||||
x0, y0, x1, y1 = box
|
||||
for y in range(y0, y1, cell):
|
||||
for x in range(x0, x1, cell):
|
||||
if ((x - x0) // cell + (y - y0) // cell) % 2 == 0:
|
||||
draw.rectangle((x, y, min(x + cell, x1) - 1,
|
||||
min(y + cell, y1) - 1), fill=(58, 58, 66, 255))
|
||||
else:
|
||||
draw.rectangle((x, y, min(x + cell, x1) - 1,
|
||||
min(y + cell, y1) - 1), fill=(44, 44, 50, 255))
|
||||
|
||||
|
||||
def build_contact_sheet(source: Image.Image, path: Path) -> None:
|
||||
"""Numbered 12x source cells -- used to review the 64-tile mapping."""
|
||||
scale = 12
|
||||
big = TILE * scale
|
||||
pad = 26
|
||||
label_h = 42
|
||||
font = _font(16)
|
||||
small = _font(11)
|
||||
width = COLS * (big + pad) + pad
|
||||
height = ROWS * (big + label_h + pad) + pad + 34
|
||||
out = Image.new("RGBA", (width, height), (32, 32, 38, 255))
|
||||
draw = ImageDraw.Draw(out)
|
||||
draw.text((pad, 8),
|
||||
"tileset1.png - 128x128 RGBA, 8x8 tiles @16 px, nearest x12",
|
||||
fill=(255, 255, 255, 255), font=font)
|
||||
for tile_id in range(COUNT):
|
||||
col = tile_id % COLS
|
||||
row = tile_id // COLS
|
||||
x = pad + col * (big + pad)
|
||||
y = 34 + pad + row * (big + label_h + pad)
|
||||
_checker(draw, (x, y, x + big, y + big), 16)
|
||||
out.alpha_composite(_tile(source, tile_id).resize(
|
||||
(big, big), Image.Resampling.NEAREST), (x, y))
|
||||
draw.rectangle((x - 1, y - 1, x + big, y + big),
|
||||
outline=(120, 120, 128, 255))
|
||||
draw.text((x, y - 21), f"{tile_id:02d} ({col},{row})",
|
||||
fill=(255, 230, 120, 255), font=font)
|
||||
draw.text((x, y + big + 4), TILE_NAMES[tile_id],
|
||||
fill=(190, 196, 204, 255), font=small)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.convert("RGB").save(path)
|
||||
print(f"preview: {path}")
|
||||
|
||||
|
||||
def build_atlas_preview(atlas: Image.Image, singles: dict[str, Image.Image],
|
||||
path: Path) -> None:
|
||||
"""Atlas at 16/24/32/64 px cells + fallbacks at 16/24/32 px and 4x."""
|
||||
margin = 24
|
||||
# 1x/1.5x/2x/4x -> 16/24/32/64 px per atlas cell.
|
||||
atlas_scales = ((1, 16), (1.5, 24), (2, 32), (4, 64))
|
||||
atlas_width = sum(int(atlas.width * s) for s, _cell in atlas_scales) + \
|
||||
margin * (len(atlas_scales) - 1)
|
||||
card_w, card_h = 218, 238
|
||||
cards_width = 3 * card_w
|
||||
width = max(atlas_width, cards_width) + margin * 2
|
||||
atlas_h = int(atlas.height * atlas_scales[-1][0])
|
||||
icons_top = margin + 30 + atlas_h + 52
|
||||
height = icons_top + 2 * card_h + 18
|
||||
out = Image.new("RGBA", (width, height), (30, 30, 35, 255))
|
||||
draw = ImageDraw.Draw(out)
|
||||
title = _font(20)
|
||||
font = _font(14)
|
||||
small = _font(11)
|
||||
|
||||
draw.text((margin, 10),
|
||||
"file_icons.png (new) - 128x128, 8x8 ids 0..63",
|
||||
fill=(255, 255, 255, 255), font=title)
|
||||
x = margin
|
||||
for scale, cell_px in atlas_scales:
|
||||
size = int(atlas.width * scale)
|
||||
y = margin + 30
|
||||
_checker(draw, (x, y, x + size, y + size), 8)
|
||||
out.alpha_composite(atlas.resize((size, size),
|
||||
Image.Resampling.NEAREST), (x, y))
|
||||
draw.rectangle((x - 1, y - 1, x + size, y + size),
|
||||
outline=(120, 120, 128, 255))
|
||||
draw.text((x, y + size + 4),
|
||||
f"{scale:g}x ({size}px, cell {cell_px}px)",
|
||||
fill=(255, 230, 120, 255), font=font)
|
||||
x += size + margin
|
||||
|
||||
draw.line((margin, icons_top - 26, width - margin, icons_top - 26),
|
||||
fill=(90, 94, 102, 255), width=1)
|
||||
draw.text((margin, icons_top - 22),
|
||||
"Theme90 fallbacks - 16 / 24 / 32 px and 32 px x4 (nearest)",
|
||||
fill=(255, 255, 255, 255), font=title)
|
||||
for index, (name, icon) in enumerate(singles.items()):
|
||||
col = index % 3
|
||||
row = index // 3
|
||||
x0 = margin + col * card_w
|
||||
y0 = icons_top + row * card_h
|
||||
draw.rectangle((x0, y0, x0 + card_w - 8, y0 + card_h - 10),
|
||||
fill=(40, 40, 47, 255), outline=(86, 90, 98, 255))
|
||||
draw.text((x0 + 8, y0 + 6), f"{name}.png", fill=(255, 255, 255, 255),
|
||||
font=font)
|
||||
cx = x0 + 10
|
||||
for px in (16, 24, 32):
|
||||
_checker(draw, (cx, y0 + 32, cx + px, y0 + 32 + px), 8)
|
||||
out.alpha_composite(icon.resize((px, px),
|
||||
Image.Resampling.NEAREST),
|
||||
(cx, y0 + 32))
|
||||
draw.rectangle((cx - 1, y0 + 31, cx + px, y0 + 33 + px),
|
||||
outline=(120, 120, 128, 255))
|
||||
draw.text((cx, y0 + 36 + px), f"{px}", fill=(170, 176, 184, 255),
|
||||
font=small)
|
||||
cx += px + 16
|
||||
bx, by = x0 + 10, y0 + 86
|
||||
_checker(draw, (bx, by, bx + 128, by + 128), 8)
|
||||
out.alpha_composite(icon.resize((128, 128),
|
||||
Image.Resampling.NEAREST), (bx, by))
|
||||
draw.rectangle((bx - 1, by - 1, bx + 128, by + 128),
|
||||
outline=(120, 120, 128, 255))
|
||||
draw.text((bx + 134, by + 4), "32 px", fill=(255, 230, 120, 255),
|
||||
font=font)
|
||||
draw.text((bx + 134, by + 24), "x4", fill=(190, 196, 204, 255),
|
||||
font=small)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.convert("RGB").save(path)
|
||||
print(f"preview: {path}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--source", type=Path, default=SOURCE,
|
||||
help=f"16 px tileset (default: {SOURCE})")
|
||||
parser.add_argument("--no-preview", action="store_true",
|
||||
help="skip the two preview PNGs")
|
||||
args = parser.parse_args()
|
||||
|
||||
source_path = args.source.resolve()
|
||||
source = load_source(source_path)
|
||||
if len(ATLAS_SLOTS) != COUNT:
|
||||
raise SystemExit(f"ATLAS_SLOTS has {len(ATLAS_SLOTS)} entries, "
|
||||
f"expected {COUNT}")
|
||||
if len(TILE_NAMES) != COUNT:
|
||||
raise SystemExit(f"TILE_NAMES has {len(TILE_NAMES)} entries, "
|
||||
f"expected {COUNT}")
|
||||
|
||||
atlas = build_atlas(source)
|
||||
if atlas.size != (COLS * TILE, ROWS * TILE):
|
||||
raise SystemExit(f"atlas size {atlas.size} is not "
|
||||
f"{COLS * TILE}x{ROWS * TILE}")
|
||||
assert_hard_alpha(atlas, "file_icons.png")
|
||||
atlas.save(ATLAS)
|
||||
print(f"atlas: {ATLAS} ({atlas.width}x{atlas.height}, "
|
||||
f"{COLS}x{ROWS} @ {TILE}px)")
|
||||
|
||||
singles: dict[str, Image.Image] = {}
|
||||
for name, tile_id in SINGLE_ICONS.items():
|
||||
icon = build_single(source, tile_id)
|
||||
assert_hard_alpha(icon, name)
|
||||
target = BASE / f"{name}.png"
|
||||
icon.save(target)
|
||||
singles[name] = icon
|
||||
print(f"icon: {target} ({icon.width}x{icon.height}) "
|
||||
f"<- tile {tile_id:02d} {TILE_NAMES[tile_id]}")
|
||||
|
||||
if not args.no_preview:
|
||||
build_contact_sheet(source, source_path.with_name(
|
||||
"tileset1_preview.png"))
|
||||
build_atlas_preview(atlas, singles,
|
||||
BASE / "source" / "file_icons_preview.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
After Width: | Height: | Size: 275 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dj6scq4olnonj"
|
||||
path="res://.godot/imported/packet.png-2a847fa0ce4ca153b815c85ae5cddeab.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/packet.png"
|
||||
dest_files=["res://.godot/imported/packet.png-2a847fa0ce4ca153b815c85ae5cddeab.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dyryfdgvj0s6m"
|
||||
path="res://.godot/imported/_source_upgrades.png-5c2186a86af26623e1e1ba5a3e0ad2eb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/_source_upgrades.png"
|
||||
dest_files=["res://.godot/imported/_source_upgrades.png-5c2186a86af26623e1e1ba5a3e0ad2eb.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 475 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://05ucaefb4807"
|
||||
path="res://.godot/imported/lot_channel.png-86f52ea0f61c04f6a60ada643ca85ccc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_channel.png"
|
||||
dest_files=["res://.godot/imported/lot_channel.png-86f52ea0f61c04f6a60ada643ca85ccc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 846 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://brc1psekc17bc"
|
||||
path="res://.godot/imported/lot_folder.png-dd754300f7e865cf73009868f7d66e6b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_folder.png"
|
||||
dest_files=["res://.godot/imported/lot_folder.png-dd754300f7e865cf73009868f7d66e6b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 738 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://0x7o4yo8dvps"
|
||||
path="res://.godot/imported/lot_money.png-9b87cd53690d3369a84cabb7576571b2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_money.png"
|
||||
dest_files=["res://.godot/imported/lot_money.png-9b87cd53690d3369a84cabb7576571b2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 754 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dapkbkpx18mgy"
|
||||
path="res://.godot/imported/lot_shield.png-98b0b28b4edcfac112e9f4e5d43c5364.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_shield.png"
|
||||
dest_files=["res://.godot/imported/lot_shield.png-98b0b28b4edcfac112e9f4e5d43c5364.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 331 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1xbqwgle6adp"
|
||||
path="res://.godot/imported/lot_sla.png-9730f109736cf5b78256df880bad17bc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_sla.png"
|
||||
dest_files=["res://.godot/imported/lot_sla.png-9730f109736cf5b78256df880bad17bc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 386 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://drgl82ovu7vry"
|
||||
path="res://.godot/imported/lot_slots.png-8d0c9f2a6fd82fdcf134c18f77d41a10.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_slots.png"
|
||||
dest_files=["res://.godot/imported/lot_slots.png-8d0c9f2a6fd82fdcf134c18f77d41a10.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://stbfx7k5pt2s"
|
||||
path="res://.godot/imported/lot_speed.png-17cc7beb1294023e976429ca8555aaaa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_speed.png"
|
||||
dest_files=["res://.godot/imported/lot_speed.png-17cc7beb1294023e976429ca8555aaaa.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 405 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cixxo6hxeisdl"
|
||||
path="res://.godot/imported/lot_timer.png-9bb944960c84e22cd0cc36cadc18aa86.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_timer.png"
|
||||
dest_files=["res://.godot/imported/lot_timer.png-9bb944960c84e22cd0cc36cadc18aa86.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 396 B |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dc1sclulaata4"
|
||||
path="res://.godot/imported/lot_udp.png-111b745d551b21f3321882852d252022.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/icons/shop/lot_udp.png"
|
||||
dest_files=["res://.godot/imported/lot_udp.png-111b745d551b21f3321882852d252022.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -0,0 +1,2 @@
|
||||
# Source artwork is not imported by Godot: only the derived PNGs in
|
||||
# art/icons/ (atlas + six fallbacks) are loaded at runtime.
|
||||
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1vbo2277t4ix"
|
||||
path="res://.godot/imported/tutor_cat.png-ad3ee184ce969bcd494931d604daab36.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/mascot/tutor_cat.png"
|
||||
dest_files=["res://.godot/imported/tutor_cat.png-ad3ee184ce969bcd494931d604daab36.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -1,17 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Генератор звуков PEAK HOUR 97 (короткие 90s-style SFX + фоновый гул).
|
||||
"""Генератор звуков PEAK HOUR 97 (тёплые 90s SFX + фоновый гул).
|
||||
|
||||
Все ассеты синтезируются программно, без внешних файлов и без сторонних
|
||||
библиотек (только стандартная библиотека Python 3) — значит, нет проблем
|
||||
с лицензиями и генерация воспроизводима на любой машине.
|
||||
|
||||
Палитра: ночной узел файлового обмена, тёплая бумага, кассетный лоу-фай,
|
||||
реле и коммутатор. Мягкая сатурация, короткий комнатный хвост, механические
|
||||
транзиенты. Тональные сигналы — только прима/квинта/октава, без терций,
|
||||
чтобы не спорить с тональностью музыки. У подтверждений один «фамильный»
|
||||
FM-колокольчик, у отказов — один приглушённый деревянный язычок.
|
||||
|
||||
Запуск:
|
||||
python3 audio/generate_sfx.py
|
||||
|
||||
Результат:
|
||||
audio/sfx/*.wav — 18 одноразовых эффектов, 30..800 мс
|
||||
audio/music/hum.wav — гул серверной, 3 с, loop-friendly
|
||||
audio/sfx/*.wav — 21 одноразовый эффект, 30..800 мс
|
||||
audio/music/hum.wav — гул серверной, 3 с, loop-friendly (не изменён)
|
||||
|
||||
Формат WAV: 22050 Гц, 16 бит, моно, пик нормируется ровно к −6 dBFS
|
||||
(без клиппинга: 10^(-6/20) ≈ 0.501 < 1.0).
|
||||
@@ -36,27 +42,40 @@ MUSIC_DIR = os.path.join(HERE, "music")
|
||||
|
||||
# Длительности в секундах (SFX обязаны попадать в 30..800 мс).
|
||||
DUR = {
|
||||
"click": 0.045,
|
||||
"click": 0.048,
|
||||
"drag": 0.380,
|
||||
"drop": 0.140,
|
||||
"drop": 0.150,
|
||||
"drop_error": 0.240,
|
||||
"recv": 0.330,
|
||||
"request_ready": 0.450,
|
||||
"request_ready": 0.430,
|
||||
"minigame_win": 0.500,
|
||||
"minigame_fail": 0.500,
|
||||
"minigame_fail": 0.520,
|
||||
"modem": 0.800,
|
||||
"connect_fail": 0.420,
|
||||
"tick": 0.040,
|
||||
"tick": 0.038,
|
||||
"packet_loss": 0.190,
|
||||
"attack": 0.650,
|
||||
"attack_blocked": 0.320,
|
||||
"money_up": 0.430,
|
||||
"money_down": 0.320,
|
||||
"money_down": 0.340,
|
||||
"hour_end": 0.720,
|
||||
"run_end": 0.800,
|
||||
"ok": 0.200,
|
||||
"bad": 0.240,
|
||||
"stamp": 0.280,
|
||||
}
|
||||
HUM_DUR = 3.0
|
||||
|
||||
# Ноты: только прима / квинта / октава.
|
||||
C3 = 130.81
|
||||
G3 = 196.00
|
||||
C4 = 261.63
|
||||
G4 = 392.00
|
||||
C5 = 523.25
|
||||
G5 = 783.99
|
||||
C6 = 1046.50
|
||||
G6 = 1567.98
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- базовые ОСЦИЛЛЯТОРЫ
|
||||
|
||||
@@ -145,7 +164,7 @@ def rich(dur: float, freq: float, weights, bend=None, amp: float = 1.0) -> list:
|
||||
|
||||
# ---------------------------------------------------------------- ФИЛЬТРЫ (RBJ biquad)
|
||||
|
||||
def biquad(x: list, kind: float, fc: float, q: float = 0.707) -> list:
|
||||
def biquad(x: list, kind: str, fc: float, q: float = 0.707) -> list:
|
||||
"""Прямо-циклический биквад: 'lp' | 'hp' | 'bp'."""
|
||||
w0 = TWO_PI * fc / SR
|
||||
c = math.cos(w0)
|
||||
@@ -176,7 +195,7 @@ def periodic_filter(x: list, kind: str, fc: float, q: float = 0.707) -> list:
|
||||
|
||||
Прогоняем биквад по 4 копиям подряд и берём последнюю — состояние фильтра
|
||||
к её началу уже сошлось к периодическому режиму, поэтому первые и последние
|
||||
отсчёты совпадают по статистике и шум зациклевается без щелчка.
|
||||
отсчёты совпадают по статистике и шум зацикливается без щелчка.
|
||||
"""
|
||||
reps = 4
|
||||
y = biquad(list(x) * reps, kind, fc, q)
|
||||
@@ -285,169 +304,303 @@ def write_wav(path: str, x: list) -> None:
|
||||
|
||||
|
||||
def finish(x: list, fin: float = 0.001, fout: float = 0.010) -> list:
|
||||
x = remove_dc(x)
|
||||
x = fade(x, fin, fout)
|
||||
# DC убираем после fade: короткий низкий удар успевает дать ненулевое
|
||||
# среднее, если срезать его на нецелом числе периодов.
|
||||
x = remove_dc(x)
|
||||
return normalize(x)
|
||||
|
||||
|
||||
def limit(x: list, dur: float) -> list:
|
||||
"""Обрезать хвост по заявленной длительности (для комнатного хвоста)."""
|
||||
n = nsamp(dur)
|
||||
return x[:n] if len(x) > n else x
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ПАЛИТРА: КАССЕТА / КОМНАТА / ТЕМБРЫ
|
||||
|
||||
def soft_sat(x: list, drive: float = 1.6) -> list:
|
||||
"""Мягкая аналоговая сатурация: tanh без жёсткого клиппинга."""
|
||||
g = math.tanh(drive)
|
||||
if g <= 1e-9:
|
||||
return x
|
||||
return [math.tanh(drive * v) / g for v in x]
|
||||
|
||||
|
||||
def tape(x: list, wow: float = 0.0, flutter: float = 0.0,
|
||||
lp: float = 6000.0, hp: float = 55.0) -> list:
|
||||
"""Кассетная окраска: лёгкий wow/flutter, срез верха и низа."""
|
||||
y = biquad(biquad(x, "hp", hp, 0.7), "lp", lp, 0.7)
|
||||
if wow <= 0.0 and flutter <= 0.0:
|
||||
return y
|
||||
n = len(y)
|
||||
out = [0.0] * n
|
||||
for i in range(n):
|
||||
t = i / SR
|
||||
pos = (i + wow * math.sin(TWO_PI * 0.55 * t)
|
||||
+ flutter * math.sin(TWO_PI * 6.3 * t))
|
||||
if pos < 0.0:
|
||||
pos = 0.0
|
||||
j = int(pos)
|
||||
if j >= n - 1:
|
||||
out[i] = y[n - 1]
|
||||
else:
|
||||
fr = pos - j
|
||||
out[i] = y[j] * (1.0 - fr) + y[j + 1] * fr
|
||||
return out
|
||||
|
||||
|
||||
def room(x: list, mix: float = 0.14, decay: float = 0.12,
|
||||
damp: float = 0.35, fb: float = 0.70) -> list:
|
||||
"""Короткий комнатный хвост: 4 демпфированные гребёнки (6.8..15 мс)."""
|
||||
n = len(x) + nsamp(decay)
|
||||
dry = [0.0] * n
|
||||
dry[:len(x)] = x
|
||||
acc = [0.0] * n
|
||||
for d in (149, 197, 263, 331):
|
||||
line = [0.0] * d
|
||||
idx = 0
|
||||
lp = 0.0
|
||||
for i in range(n):
|
||||
v = line[idx]
|
||||
lp += damp * (v - lp)
|
||||
line[idx] = dry[i] + fb * lp
|
||||
acc[i] += v
|
||||
idx += 1
|
||||
if idx >= d:
|
||||
idx = 0
|
||||
inv = 1.0 / 4.0
|
||||
return [dry[i] * (1.0 - mix) + acc[i] * inv * mix for i in range(n)]
|
||||
|
||||
|
||||
def contact(rng: random.Random, dur: float = 0.004, lo: float = 500.0,
|
||||
hi: float = 2600.0, tau: float = 0.0014) -> list:
|
||||
"""Механический транзиент: очень короткий полосовой шум."""
|
||||
n = biquad(biquad(noise(dur, rng), "hp", lo, 0.7), "lp", hi, 0.8)
|
||||
return apply_env(n, env_exp(dur, tau))
|
||||
|
||||
|
||||
def warm_bell(dur: float, f: float, tau: float = 0.22, amp: float = 1.0,
|
||||
index: float = 1.1) -> list:
|
||||
"""Фамильный тембр подтверждений: мягкий FM-колокол.
|
||||
|
||||
Модулятор на октаву выше — боковые полосы остаются гармоническими,
|
||||
поэтому сигнал нейтрален к тональности музыки.
|
||||
"""
|
||||
n = nsamp(dur)
|
||||
out = [0.0] * n
|
||||
ph = 0.0
|
||||
for i in range(n):
|
||||
t = i / SR
|
||||
idx = index * math.exp(-t / max(1e-4, tau * 0.55))
|
||||
ph += TWO_PI * f / SR
|
||||
if ph >= TWO_PI:
|
||||
ph -= TWO_PI
|
||||
out[i] = amp * math.sin(ph + idx * math.sin(TWO_PI * f * 2.0 * t))
|
||||
out = [v * math.exp(-(i / SR) / tau) for i, v in enumerate(out)]
|
||||
return fade(out, 0.003, min(0.05, dur * 0.5))
|
||||
|
||||
|
||||
def muted_reed(dur: float, f: float, tau: float = 0.16, amp: float = 1.0,
|
||||
lp: float = 1500.0) -> list:
|
||||
"""Фамильный тембр отказов: приглушённый деревянный язычок.
|
||||
|
||||
Только нечётные гармоники (1/3/5) и низкий срез — без «аркадного» визга.
|
||||
"""
|
||||
n = nsamp(dur)
|
||||
out = [0.0] * n
|
||||
for mult, w, td in ((1.0, 1.0, tau), (3.0, 0.28, tau * 0.55),
|
||||
(5.0, 0.10, tau * 0.35)):
|
||||
part = tone(dur, f * mult, amp=w)
|
||||
part = apply_env(part, env_exp(dur, td))
|
||||
out = [a + b for a, b in zip(out, part)]
|
||||
out = biquad(out, "lp", lp, 0.8)
|
||||
out = soft_sat([v * 0.9 for v in out], 1.4)
|
||||
return fade(out, 0.004, min(0.04, dur * 0.5))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ЗВУКИ
|
||||
|
||||
def s_click(rng: random.Random) -> list:
|
||||
"""Сухой щелчок: короткий шумовой транзиент + звонкий пик + тело."""
|
||||
"""Мягкий щелчок клавиши/реле: контакт + деревянное тело."""
|
||||
out = silence(DUR["click"])
|
||||
tr = apply_env(biquad(noise(0.004, rng), "hp", 1400.0, 0.8), env_exp(0.004, 0.0012))
|
||||
place(out, tr, 0.0, 1.0)
|
||||
ping = apply_env(tone(0.020, 2750.0), env_exp(0.020, 0.0030))
|
||||
place(out, ping, 0.0, 0.55)
|
||||
body = apply_env(tone(0.034, 940.0), env_exp(0.034, 0.0065))
|
||||
place(out, body, 0.0, 0.45)
|
||||
return finish(out, 0.0005, 0.010)
|
||||
place(out, contact(rng, 0.004, 500.0, 2600.0, 0.0014), 0.0, 1.0)
|
||||
# второй контакт реле — совсем тихий
|
||||
place(out, contact(rng, 0.003, 700.0, 2200.0, 0.0012), 0.007, 0.40)
|
||||
body = apply_env(tri(0.035, 320.0), env_exp(0.035, 0.009))
|
||||
body = biquad(body, "lp", 1600.0, 0.7)
|
||||
place(out, body, 0.001, 0.55)
|
||||
ping = apply_env(tone(0.016, 1280.0), env_exp(0.016, 0.0045))
|
||||
place(out, ping, 0.0, 0.10)
|
||||
x = soft_sat(tape(out, lp=5600.0), 1.5)
|
||||
return finish(x, 0.0005, 0.010)
|
||||
|
||||
|
||||
def s_drag(rng: random.Random) -> list:
|
||||
"""Тихий шорох: полосовой шум с медленным «дыханием»."""
|
||||
n = noise(DUR["drag"], rng)
|
||||
n = biquad(biquad(n, "hp", 520.0, 0.7), "lp", 3600.0, 0.9)
|
||||
am = shape_random(DUR["drag"], rng, seg=0.025, lo=0.35, hi=1.0)
|
||||
"""Бумажный шорох: полосовой шум с «дыханием» и редкими тресками."""
|
||||
d = DUR["drag"]
|
||||
n = biquad(biquad(noise(d, rng), "hp", 240.0, 0.7), "lp", 2600.0, 0.8)
|
||||
am = shape_random(d, rng, seg=0.028, lo=0.35, hi=1.0)
|
||||
x = [v * a for v, a in zip(n, am)]
|
||||
return finish(x, 0.030, 0.080)
|
||||
for _ in range(7):
|
||||
at = rng.uniform(0.010, d - 0.020)
|
||||
ln = rng.uniform(0.002, 0.006)
|
||||
place(x, contact(rng, ln, 700.0, 3600.0, ln * 0.35), at,
|
||||
rng.uniform(0.10, 0.28))
|
||||
x = tape(x, wow=2.2, flutter=0.5, lp=4800.0)
|
||||
return finish(soft_sat(x, 1.3), 0.030, 0.080)
|
||||
|
||||
|
||||
def s_drop(rng: random.Random) -> list:
|
||||
"""Мягкий стук: нисходящий свип + приглушённый контакт."""
|
||||
out = silence(DUR["drop"])
|
||||
swp = tone(0.12, lambda t: 55.0 + 115.0 * math.exp(-t / 0.035))
|
||||
swp = apply_env(swp, env_exp(0.12, 0.045))
|
||||
place(out, swp, 0.0, 1.0)
|
||||
ct = apply_env(biquad(noise(0.010, rng), "lp", 750.0, 0.7), env_exp(0.010, 0.004))
|
||||
place(out, ct, 0.0, 0.7)
|
||||
return finish(out, 0.003, 0.035)
|
||||
"""Бумажно-деревянный стук: низкое тело + контакт + шелест бумаги."""
|
||||
d = DUR["drop"]
|
||||
out = silence(d)
|
||||
th = tone(0.145, lambda t: 88.0 + 62.0 * math.exp(-t / 0.020))
|
||||
th = apply_env(th, env_exp(0.145, 0.036))
|
||||
place(out, th, 0.0, 0.95)
|
||||
wood = contact(rng, 0.012, 200.0, 900.0, 0.004)
|
||||
place(out, wood, 0.0, 0.55)
|
||||
paper = biquad(noise(0.035, rng), "bp", 1700.0, 0.9)
|
||||
paper = apply_env(paper, env_exp(0.035, 0.012))
|
||||
place(out, paper, 0.0, 0.35)
|
||||
x = room(out, mix=0.10, decay=0.090)
|
||||
x = soft_sat(tape(x, lp=5000.0), 1.4)
|
||||
return finish(limit(x, d), 0.002, 0.025)
|
||||
|
||||
|
||||
def s_drop_error(rng: random.Random) -> list:
|
||||
"""Глухой удар «не туда»: низкий спад без ярких гармоник."""
|
||||
out = silence(DUR["drop_error"])
|
||||
swp = tone(0.22, lambda t: 40.0 + 60.0 * math.exp(-t / 0.06))
|
||||
swp = apply_env(swp, env_exp(0.22, 0.080))
|
||||
place(out, biquad(swp, "lp", 320.0, 0.7), 0.0, 1.0)
|
||||
thud = apply_env(biquad(noise(0.030, rng), "lp", 420.0, 0.6), env_exp(0.030, 0.012))
|
||||
place(out, thud, 0.0, 0.6)
|
||||
return finish(out, 0.004, 0.070)
|
||||
"""Отказ «не туда»: глухой удар и приглушённый нисходящий язычок."""
|
||||
d = DUR["drop_error"]
|
||||
out = silence(d)
|
||||
th = tone(0.200, lambda t: 58.0 + 40.0 * math.exp(-t / 0.050))
|
||||
th = apply_env(biquad(th, "lp", 260.0, 0.7), env_exp(0.200, 0.055))
|
||||
place(out, th, 0.0, 1.0)
|
||||
place(out, contact(rng, 0.008, 150.0, 1100.0, 0.0024), 0.0, 0.55)
|
||||
place(out, contact(rng, 0.007, 150.0, 1000.0, 0.0022), 0.062, 0.40)
|
||||
reed = muted_reed(0.140, G3, tau=0.090, lp=900.0)
|
||||
place(out, reed, 0.052, 0.42)
|
||||
x = room(out, mix=0.10, decay=0.100)
|
||||
x = soft_sat(tape(x, lp=3600.0), 1.3)
|
||||
return finish(limit(x, d), 0.003, 0.045)
|
||||
|
||||
|
||||
def s_recv(rng: random.Random) -> list:
|
||||
"""Короткий сигнал + запись: писк уведомления, второй писк, шум ленты."""
|
||||
out = silence(DUR["recv"])
|
||||
beep = apply_env(tone(0.100, 880.0), env_ar(0.100, 0.005, 0.020))
|
||||
place(out, beep, 0.0, 1.0)
|
||||
blip = apply_env(tone(0.070, 1760.0), env_ar(0.070, 0.004, 0.015))
|
||||
place(out, blip, 0.130, 0.75)
|
||||
tape = biquad(biquad(noise(DUR["recv"], rng), "hp", 2200.0, 0.7), "lp", 6500.0, 0.7)
|
||||
place(out, tape, 0.0, 0.10)
|
||||
return finish(out, 0.002, 0.030)
|
||||
"""Телеграфный двухнотный сигнал: прима/квинта с релейным контактом."""
|
||||
d = DUR["recv"]
|
||||
out = silence(d)
|
||||
for at, f, g in ((0.0, C5, 1.0), (0.115, G5, 0.86)):
|
||||
seg = 0.130
|
||||
pulse = [a + 0.35 * b for a, b in zip(tone(seg, f), tone(seg, f * 2.0))]
|
||||
pulse = apply_env(pulse, env_ar(seg, 0.005, 0.050))
|
||||
pulse = [v * math.exp(-(i / SR) / 0.160) for i, v in enumerate(pulse)]
|
||||
place(out, pulse, at, g)
|
||||
place(out, contact(rng, 0.004, 900.0, 3200.0, 0.0013), at, 0.30)
|
||||
hiss = biquad(biquad(noise(d, rng), "hp", 2000.0, 0.7), "lp", 6000.0, 0.7)
|
||||
place(out, hiss, 0.0, 0.025)
|
||||
x = room(out, mix=0.08, decay=0.080)
|
||||
x = soft_sat(tape(x, wow=1.0, lp=5600.0), 1.3)
|
||||
return finish(limit(x, d), 0.002, 0.030)
|
||||
|
||||
|
||||
def s_request_ready(rng: random.Random) -> list:
|
||||
"""Восходящий аккорд: три ноты вверх, мягкая атака."""
|
||||
"""Тёплый восходящий сигнал: прима → квинта → октава, FM-колокол."""
|
||||
del rng
|
||||
out = silence(DUR["request_ready"])
|
||||
notes = [(523.25, 0.0), (659.25, 0.130), (783.99, 0.260)]
|
||||
for i, (f, at) in enumerate(notes):
|
||||
seg = DUR["request_ready"] - at
|
||||
t = rich(seg, f, [(1.0, 1.0), (2.0, 0.30), (3.0, 0.12)])
|
||||
t = apply_env(t, env_ar(seg, 0.010, 0.12))
|
||||
t = [v * math.exp(-(i2 / SR) / 0.55) for i2, v in enumerate(t)]
|
||||
place(out, t, at, 1.0 - i * 0.12)
|
||||
return finish(out, 0.008, 0.090)
|
||||
d = DUR["request_ready"]
|
||||
out = silence(d)
|
||||
notes = ((C4, 0.000, 0.90), (G4, 0.105, 0.90), (C5, 0.215, 1.0))
|
||||
for f, at, g in notes:
|
||||
seg = d - at
|
||||
place(out, warm_bell(seg, f, tau=0.26, index=1.05), at, g)
|
||||
x = room(out, mix=0.14, decay=0.120)
|
||||
x = soft_sat(tape(x, wow=1.5, lp=5200.0), 1.4)
|
||||
return finish(limit(x, d), 0.006, 0.070)
|
||||
|
||||
|
||||
def s_minigame_win(rng: random.Random) -> list:
|
||||
"""Победа: восходящее арпеджио из четырёх нот."""
|
||||
"""Победа: короткая фраза тем же колоколом, C5 → G5 → C6."""
|
||||
del rng
|
||||
out = silence(DUR["minigame_win"])
|
||||
notes = [523.25, 659.25, 783.99, 1046.50]
|
||||
for i, f in enumerate(notes):
|
||||
at = i * 0.085
|
||||
seg = DUR["minigame_win"] - at
|
||||
t = tri(seg, f, amp=1.0)
|
||||
t = [v + 0.30 * math.sin(TWO_PI * f * 2.0 * (i2 / SR)) for i2, v in enumerate(t)]
|
||||
tau = 0.22 if i == len(notes) - 1 else 0.10
|
||||
t = apply_env(t, env_exp(seg, tau))
|
||||
t = fade(t, 0.003, 0.010)
|
||||
place(out, t, at, 1.0 - i * 0.10)
|
||||
return finish(out, 0.003, 0.070)
|
||||
d = DUR["minigame_win"]
|
||||
out = silence(d)
|
||||
notes = ((C5, 0.000, 0.82, 0.20), (G5, 0.105, 0.90, 0.22),
|
||||
(C6, 0.225, 1.0, 0.28))
|
||||
for f, at, g, tau in notes:
|
||||
seg = d - at
|
||||
place(out, warm_bell(seg, f, tau=tau, index=1.15), at, g)
|
||||
# тёплая опора на октаву ниже
|
||||
place(out, warm_bell(0.420, C3, tau=0.30, index=0.7), 0.010, 0.20)
|
||||
x = room(out, mix=0.16, decay=0.140)
|
||||
x = soft_sat(tape(x, wow=1.2, lp=5000.0), 1.5)
|
||||
return finish(limit(x, d), 0.004, 0.070)
|
||||
|
||||
|
||||
def s_minigame_fail(rng: random.Random) -> list:
|
||||
"""Провал: нисходящие ноты с изгибом и низкий гул в хвосте."""
|
||||
out = silence(DUR["minigame_fail"])
|
||||
notes = [392.00, 330.00, 262.00, 196.00]
|
||||
for i, f in enumerate(notes):
|
||||
at = i * 0.090
|
||||
seg = DUR["minigame_fail"] - at # ноты укладываются ровно в 500 мс
|
||||
bend = lambda t, _f=f, _s=seg: 1.0 - 0.08 * min(1.0, t / max(0.01, _s))
|
||||
t = rich(seg, f, [(1.0, 1.0), (2.0, 0.45), (3.0, 0.25)], bend=bend)
|
||||
t = apply_env(t, env_ar(seg, 0.006, 0.06))
|
||||
t = apply_env(t, env_exp(seg, 0.13))
|
||||
place(out, t, at, 1.0 - i * 0.10)
|
||||
# низкий вибрирующий «бзз» в конце
|
||||
buzz_d = 0.230
|
||||
buzz = square(buzz_d, 98.0, amp=1.0)
|
||||
am = [0.55 + 0.45 * math.sin(TWO_PI * 9.0 * (i / SR)) for i in range(len(buzz))]
|
||||
buzz = [v * a for v, a in zip(buzz, am)]
|
||||
buzz = apply_env(biquad(buzz, "lp", 900.0, 0.8), env_ar(buzz_d, 0.010, 0.06))
|
||||
place(out, buzz, 0.270, 0.55)
|
||||
return finish(out, 0.004, 0.060)
|
||||
"""Провал: та же «деревянная» семья, G4 → C4 → C3 (квинта и октава вниз)."""
|
||||
d = DUR["minigame_fail"]
|
||||
out = silence(d)
|
||||
notes = ((G4, 0.000, 0.95, 0.14), (C4, 0.110, 0.92, 0.14),
|
||||
(C3, 0.230, 1.0, 0.18))
|
||||
for f, at, g, tau in notes:
|
||||
seg = d - at
|
||||
place(out, muted_reed(seg, f, tau=tau, lp=1200.0), at, g)
|
||||
th = apply_env(tone(0.200, 70.0), env_exp(0.200, 0.055))
|
||||
place(out, th, 0.0, 0.40)
|
||||
place(out, contact(rng, 0.006, 300.0, 1600.0, 0.0018), 0.0, 0.35)
|
||||
x = room(out, mix=0.10, decay=0.120)
|
||||
x = soft_sat(tape(x, lp=3800.0), 1.4)
|
||||
return finish(limit(x, d), 0.004, 0.060)
|
||||
|
||||
|
||||
def s_modem(rng: random.Random) -> list:
|
||||
"""Модемный скриз 0.8 с: набор → свип → резкий цифровой шум → тон-шаги."""
|
||||
out = silence(DUR["modem"])
|
||||
"""Модем 0.8 с: набор → тёплый свип → FM-варблер → тон-шаги.
|
||||
|
||||
Никакого свистящего шума 1.8 кГц: «цифровой» характер даёт мягкий
|
||||
FM-варблер и стробированный шум ниже 2.5 кГц.
|
||||
"""
|
||||
d = DUR["modem"]
|
||||
out = silence(d)
|
||||
|
||||
# 1) DTMF-набор (два тона)
|
||||
def dtmf(seg: float, lo: float, hi: float) -> list:
|
||||
t = [a + 0.5 * b for a, b in zip(tone(seg, lo), tone(seg, hi))]
|
||||
return apply_env(t, env_ar(seg, 0.003, 0.010))
|
||||
return apply_env(biquad(t, "lp", 3000.0, 0.7), env_ar(seg, 0.003, 0.010))
|
||||
|
||||
place(out, dtmf(0.050, 697.0, 1209.0), 0.0, 0.9)
|
||||
place(out, dtmf(0.050, 770.0, 1336.0), 0.075, 0.9)
|
||||
place(out, dtmf(0.050, 697.0, 1209.0), 0.0, 0.55)
|
||||
place(out, dtmf(0.050, 770.0, 1336.0), 0.070, 0.55)
|
||||
|
||||
# 2) растущий несущий свип
|
||||
sweep = tone(0.150, lambda t: 350.0 + 1250.0 * (t / 0.150))
|
||||
sweep = apply_env(biquad(sweep, "lp", 4000.0, 0.7), env_ar(0.150, 0.010, 0.030))
|
||||
place(out, sweep, 0.140, 0.85)
|
||||
sweep = tone(0.130, lambda t: 320.0 + 680.0 * (t / 0.130))
|
||||
sweep = apply_env(biquad(sweep, "lp", 2200.0, 0.7), env_ar(0.130, 0.008, 0.030))
|
||||
place(out, sweep, 0.130, 0.60)
|
||||
|
||||
# 3) «скрип» цифрового канала: FM-носитель со случайными фазовыми срывами
|
||||
seg_d = 0.330
|
||||
# FM-варблер вместо визга: несущая ~1 кГц, мягкая девиация.
|
||||
seg_d = 0.300
|
||||
n = nsamp(seg_d)
|
||||
screech = [0.0] * n
|
||||
warble = [0.0] * n
|
||||
ph = 0.0
|
||||
reset_every = max(1, nsamp(0.007))
|
||||
gate = 1.0
|
||||
reset_every = max(1, nsamp(0.010))
|
||||
for i in range(n):
|
||||
t = i / SR
|
||||
if i % reset_every == 0:
|
||||
gate = rng.uniform(0.55, 1.0)
|
||||
f = 1800.0 + 620.0 * math.sin(TWO_PI * 87.0 * t) + rng.uniform(-260.0, 260.0)
|
||||
gate = rng.uniform(0.65, 1.0)
|
||||
f = 980.0 + 170.0 * math.sin(TWO_PI * 9.5 * t) + 90.0 * math.sin(TWO_PI * 31.0 * t)
|
||||
ph += TWO_PI * f / SR
|
||||
if ph >= TWO_PI:
|
||||
ph -= TWO_PI
|
||||
screech[i] = math.sin(ph) * gate * (0.62 + 0.38 * math.sin(TWO_PI * 95.0 * t))
|
||||
hiss = biquad(biquad(noise(seg_d, rng), "hp", 1500.0, 0.7), "lp", 5200.0, 0.8)
|
||||
hiss = [v * r for v, r in zip(hiss, shape_random(seg_d, rng, 0.006, 0.3, 1.0))]
|
||||
screech = [a * 0.75 + b * 0.45 for a, b in zip(screech, hiss)]
|
||||
screech = apply_env(screech, env_ar(seg_d, 0.006, 0.020))
|
||||
place(out, screech, 0.290, 0.95)
|
||||
warble[i] = math.sin(ph) * gate * (0.68 + 0.32 * math.sin(TWO_PI * 13.0 * t))
|
||||
hiss = biquad(biquad(noise(seg_d, rng), "hp", 500.0, 0.7), "lp", 2400.0, 0.8)
|
||||
hiss = [v * r for v, r in zip(hiss, shape_random(seg_d, rng, 0.008, 0.3, 1.0))]
|
||||
warble = [a * 0.80 + b * 0.30 for a, b in zip(warble, hiss)]
|
||||
warble = apply_env(warble, env_ar(seg_d, 0.010, 0.040))
|
||||
place(out, warble, 0.270, 0.75)
|
||||
|
||||
# 4) финальные тон-шаги (переключение двух частот каждые 12 мс)
|
||||
tail_d = DUR["modem"] - 0.620
|
||||
# финальные тон-шаги (переключение частот каждые 14 мс)
|
||||
tail_d = d - 0.580
|
||||
tn = nsamp(tail_d)
|
||||
tail = [0.0] * tn
|
||||
step = max(1, nsamp(0.012))
|
||||
ramp = max(1, nsamp(0.0015))
|
||||
step = max(1, nsamp(0.014))
|
||||
ramp = max(1, nsamp(0.0020))
|
||||
ph2 = 0.0
|
||||
for i in range(tn):
|
||||
f = 1650.0 if (i // step) % 2 == 0 else 1900.0
|
||||
f = 1150.0 if (i // step) % 2 == 0 else 1420.0
|
||||
ph2 += TWO_PI * f / SR
|
||||
if ph2 >= TWO_PI:
|
||||
ph2 -= TWO_PI
|
||||
@@ -458,137 +611,218 @@ def s_modem(rng: random.Random) -> list:
|
||||
elif pos > step - ramp:
|
||||
edge = (step - pos) / ramp
|
||||
tail[i] = math.sin(ph2) * edge
|
||||
tail = apply_env(tail, env_ar(tail_d, 0.002, 0.020))
|
||||
place(out, tail, 0.620, 0.85)
|
||||
return finish(out, 0.002, 0.015)
|
||||
tail = apply_env(biquad(tail, "lp", 2600.0, 0.7), env_ar(tail_d, 0.002, 0.030))
|
||||
place(out, tail, 0.580, 0.58)
|
||||
|
||||
x = soft_sat(tape(out, wow=1.8, flutter=1.0, lp=4600.0), 1.6)
|
||||
return finish(x, 0.002, 0.015)
|
||||
|
||||
|
||||
def s_connect_fail(rng: random.Random) -> list:
|
||||
"""«бзз» — глухой прерывистый сигнал ошибки."""
|
||||
out = silence(DUR["connect_fail"])
|
||||
pulse_d = 0.100
|
||||
"""«Линия занята»: три приглушённых импульса на квинте, без квадрата."""
|
||||
d = DUR["connect_fail"]
|
||||
out = silence(d)
|
||||
for k in range(3):
|
||||
p = square(pulse_d, 140.0, amp=1.0)
|
||||
grit = biquad(noise(pulse_d, rng), "lp", 1300.0, 0.7)
|
||||
p = [a + 0.18 * b for a, b in zip(p, grit)]
|
||||
p = apply_env(biquad(p, "lp", 2600.0, 0.7), env_ar(pulse_d, 0.004, 0.012))
|
||||
place(out, p, k * 0.120, 1.0 - k * 0.12)
|
||||
return finish(out, 0.003, 0.040)
|
||||
at = k * 0.115
|
||||
f = G3 if k % 2 == 0 else (G3 * 1.5)
|
||||
seg = 0.100
|
||||
pulse = rich(seg, f, [(1.0, 1.0), (2.0, 0.20), (3.0, 0.08)])
|
||||
pulse = apply_env(biquad(pulse, "lp", 1500.0, 0.8), env_ar(seg, 0.012, 0.030))
|
||||
pulse = apply_env(pulse, env_exp(seg, 0.090))
|
||||
place(out, pulse, at, 0.85 - k * 0.08)
|
||||
place(out, contact(rng, 0.004, 400.0, 1800.0, 0.0015), at, 0.30)
|
||||
low = apply_env(tone(0.380, G3 / 2.0), env_exp(0.380, 0.160))
|
||||
place(out, low, 0.0, 0.30)
|
||||
x = room(out, mix=0.10, decay=0.100)
|
||||
x = soft_sat(tape(x, lp=3400.0), 1.4)
|
||||
return finish(limit(x, d), 0.003, 0.040)
|
||||
|
||||
|
||||
def s_tick(rng: random.Random) -> list:
|
||||
"""Тиканье: очень короткий сухой щелчок (< 5 с)."""
|
||||
out = silence(DUR["tick"])
|
||||
tr = apply_env(biquad(noise(0.002, rng), "hp", 2500.0, 0.8), env_exp(0.002, 0.0006))
|
||||
place(out, tr, 0.0, 1.0)
|
||||
ping = apply_env(tone(0.010, 3600.0), env_exp(0.010, 0.0018))
|
||||
place(out, ping, 0.0, 0.50)
|
||||
body = apply_env(tone(0.024, 1700.0), env_exp(0.024, 0.0040))
|
||||
place(out, body, 0.0, 0.45)
|
||||
return finish(out, 0.0003, 0.008)
|
||||
"""Тиканье: очень короткий мягкий контакт, тише click (см. trim в Audio)."""
|
||||
d = DUR["tick"]
|
||||
out = silence(d)
|
||||
place(out, contact(rng, 0.0025, 600.0, 2000.0, 0.0010), 0.0, 1.0)
|
||||
body = apply_env(tri(0.022, 520.0), env_exp(0.022, 0.005))
|
||||
place(out, biquad(body, "lp", 1400.0, 0.7), 0.0, 0.50)
|
||||
place(out, apply_env(tone(0.018, 190.0), env_exp(0.018, 0.006)), 0.0, 0.20)
|
||||
x = soft_sat(tape(out, lp=5000.0), 1.4)
|
||||
return finish(x, 0.0003, 0.008)
|
||||
|
||||
|
||||
def s_packet_loss(rng: random.Random) -> list:
|
||||
"""Потери пакетов: россыпь коротких тресков."""
|
||||
out = silence(DUR["packet_loss"])
|
||||
for _ in range(14):
|
||||
at = rng.uniform(0.0, DUR["packet_loss"] - 0.014)
|
||||
ln = rng.uniform(0.003, 0.011)
|
||||
b = biquad(biquad(noise(ln, rng), "hp", 1500.0, 0.7), "lp", 6000.0, 0.7)
|
||||
b = apply_env(b, env_exp(ln, max(0.0008, ln * 0.4)))
|
||||
place(out, b, at, rng.uniform(0.45, 1.0))
|
||||
return finish(out, 0.001, 0.015)
|
||||
"""Потери пакетов: мягкие трески и короткие провалы статики."""
|
||||
d = DUR["packet_loss"]
|
||||
out = silence(d)
|
||||
bed = biquad(biquad(noise(d, rng), "hp", 500.0, 0.7), "lp", 3000.0, 0.8)
|
||||
gate = shape_random(d, rng, seg=0.012, lo=0.0, hi=1.0)
|
||||
gate = [1.0 if v > 0.45 else 0.0 for v in gate]
|
||||
place(out, [v * g for v, g in zip(bed, gate)], 0.0, 0.10)
|
||||
for _ in range(11):
|
||||
at = rng.uniform(0.005, d - 0.018)
|
||||
ln = rng.uniform(0.002, 0.007)
|
||||
place(out, contact(rng, ln, 700.0, 3200.0, ln * 0.40), at,
|
||||
rng.uniform(0.30, 1.0))
|
||||
x = room(out, mix=0.06, decay=0.060)
|
||||
x = soft_sat(tape(x, lp=4600.0), 1.4)
|
||||
return finish(limit(x, d), 0.001, 0.015)
|
||||
|
||||
|
||||
def s_attack(rng: random.Random) -> list:
|
||||
"""Тревожный сигнал: двухтоновая сирена."""
|
||||
out = silence(DUR["attack"])
|
||||
period = 0.080
|
||||
k = 0
|
||||
"""Аналоговая тревога: чередование примы/квинты, низкий гул, без визга."""
|
||||
d = DUR["attack"]
|
||||
out = silence(d)
|
||||
period = 0.095
|
||||
t0 = 0.0
|
||||
while t0 < DUR["attack"] - 0.010:
|
||||
seg = min(period, DUR["attack"] - t0)
|
||||
f = 880.0 if k % 2 == 0 else 770.0
|
||||
t = rich(seg, f, [(1.0, 1.0), (3.0, 0.35), (5.0, 0.18)])
|
||||
t = apply_env(biquad(t, "lp", 3200.0, 0.7), env_ar(seg, 0.004, 0.014))
|
||||
place(out, t, t0, 1.0)
|
||||
low = apply_env(tone(seg, 110.0), env_ar(seg, 0.004, 0.020))
|
||||
place(out, low, t0, 0.45)
|
||||
k = 0
|
||||
while t0 < d - 0.020:
|
||||
seg = min(0.082, d - t0)
|
||||
f = G4 if k % 2 == 0 else (G4 * 1.5)
|
||||
v = rich(seg, f, [(1.0, 1.0), (2.0, 0.28), (3.0, 0.10)])
|
||||
v = apply_env(biquad(v, "lp", 1700.0, 0.8), env_ar(seg, 0.012, 0.030))
|
||||
place(out, v, t0, 0.85)
|
||||
place(out, apply_env(tone(seg, f * 1.5), env_ar(seg, 0.012, 0.030)),
|
||||
t0, 0.10)
|
||||
t0 += period
|
||||
k += 1
|
||||
return finish(out, 0.003, 0.030)
|
||||
drone = [a + b for a, b in zip(tone(d, 55.0), tone(d, 110.0, 0.35))]
|
||||
drone = apply_env(drone, env_ar(d, 0.020, 0.100))
|
||||
place(out, drone, 0.0, 0.30)
|
||||
x = room(out, mix=0.10, decay=0.100)
|
||||
x = soft_sat(tape(x, wow=2.5, lp=4000.0), 1.6)
|
||||
return finish(limit(x, d), 0.003, 0.030)
|
||||
|
||||
|
||||
def s_attack_blocked(rng: random.Random) -> list:
|
||||
"""Металлический удар: неармоничные обертоны."""
|
||||
out = silence(DUR["attack_blocked"])
|
||||
partials = [(430.0, 1.0, 0.060), (713.0, 0.70, 0.080), (1097.0, 0.60, 0.065),
|
||||
(1621.0, 0.45, 0.050), (2411.0, 0.30, 0.035)]
|
||||
"""Мягкий металлический удар: гармонические партиалы C4/G4/C5 + тело."""
|
||||
d = DUR["attack_blocked"]
|
||||
out = silence(d)
|
||||
partials = ((G4, 1.00, 0.340), (G4 * 1.5, 0.62, 0.270),
|
||||
(G4 * 2.0, 0.42, 0.210), (G4 * 3.0, 0.18, 0.130))
|
||||
for f, w, tau in partials:
|
||||
t = apply_env(tone(0.300, f), env_exp(0.300, tau))
|
||||
t = apply_env(tone(0.310, f), env_exp(0.310, tau))
|
||||
place(out, t, 0.0, w)
|
||||
strike = apply_env(biquad(noise(0.007, rng), "hp", 2000.0, 0.7), env_exp(0.007, 0.0022))
|
||||
place(out, strike, 0.0, 1.0)
|
||||
body = apply_env(tone(0.120, 90.0), env_exp(0.120, 0.030))
|
||||
place(out, body, 0.0, 0.7)
|
||||
return finish(out, 0.001, 0.030)
|
||||
strike = contact(rng, 0.008, 300.0, 3200.0, 0.0022)
|
||||
place(out, strike, 0.0, 0.70)
|
||||
thud = apply_env(tone(0.180, 80.0), env_exp(0.180, 0.050))
|
||||
place(out, thud, 0.0, 0.60)
|
||||
x = room(out, mix=0.12, decay=0.120)
|
||||
x = soft_sat(tape(x, lp=4400.0), 1.5)
|
||||
return finish(limit(x, d), 0.001, 0.030)
|
||||
|
||||
|
||||
def s_money_up(rng: random.Random) -> list:
|
||||
"""Касса: «ka-ching» — щелчки механики и звон."""
|
||||
out = silence(DUR["money_up"])
|
||||
for at, g in ((0.0, 1.0), (0.130, 0.7)):
|
||||
clack = biquad(biquad(noise(0.007, rng), "hp", 1000.0, 0.7), "lp", 4200.0, 0.7)
|
||||
clack = apply_env(clack, env_exp(0.007, 0.0022))
|
||||
place(out, clack, at, g)
|
||||
mech = apply_env(tone(0.060, 380.0), env_exp(0.060, 0.016))
|
||||
place(out, mech, 0.0, 0.6)
|
||||
for f, w, tau in ((1570.0, 1.0, 0.22), (2350.0, 0.62, 0.15),
|
||||
(3140.0, 0.42, 0.11), (4720.0, 0.22, 0.07)):
|
||||
t = apply_env(tone(0.360, f), env_exp(0.360, tau))
|
||||
place(out, t, 0.060, w)
|
||||
return finish(out, 0.001, 0.030)
|
||||
"""Касса в лоу-фае: щелчки механики, тёплый «ka-ching» на квинте."""
|
||||
d = DUR["money_up"]
|
||||
out = silence(d)
|
||||
slide = biquad(noise(0.160, rng), "bp", 900.0, 0.7)
|
||||
slide = apply_env(slide, env_ar(0.160, 0.040, 0.080))
|
||||
place(out, slide, 0.0, 0.16)
|
||||
for at, g in ((0.0, 0.90), (0.085, 0.65)):
|
||||
place(out, contact(rng, 0.008, 400.0, 3200.0, 0.0022), at, g)
|
||||
mech = apply_env(tri(0.060, 210.0), env_exp(0.060, 0.018))
|
||||
place(out, mech, 0.0, 0.45)
|
||||
place(out, warm_bell(0.340, C6, tau=0.150, index=0.85), 0.070, 0.75)
|
||||
place(out, warm_bell(0.340, G6, tau=0.120, index=0.70), 0.070, 0.40)
|
||||
place(out, warm_bell(0.340, C3, tau=0.240, index=0.65), 0.070, 0.18)
|
||||
x = room(out, mix=0.10, decay=0.110)
|
||||
x = soft_sat(tape(x, lp=5600.0), 1.5)
|
||||
return finish(limit(x, d), 0.001, 0.030)
|
||||
|
||||
|
||||
def s_money_down(rng: random.Random) -> list:
|
||||
"""Глухой вычет: сползающий вниз тон."""
|
||||
out = silence(DUR["money_down"])
|
||||
swp = tone(0.300, lambda t: 180.0 + 240.0 * math.exp(-t / 0.09))
|
||||
swp = apply_env(biquad(swp, "lp", 950.0, 0.7), env_exp(0.300, 0.100))
|
||||
place(out, swp, 0.0, 1.0)
|
||||
thud = apply_env(biquad(noise(0.020, rng), "lp", 520.0, 0.6), env_exp(0.020, 0.008))
|
||||
place(out, thud, 0.0, 0.7)
|
||||
return finish(out, 0.003, 0.060)
|
||||
"""Гильотина кассы: «ш-ш-ш» лезвия, сползающий тон и деревянный стук."""
|
||||
d = DUR["money_down"]
|
||||
out = silence(d)
|
||||
blade = biquad(noise(0.180, rng), "bp", 1600.0, 0.8)
|
||||
blade = apply_env(blade, env_exp(0.180, 0.040))
|
||||
place(out, blade, 0.020, 0.50)
|
||||
swp = tone(0.220, lambda t: 75.0 + 150.0 * math.exp(-t / 0.070))
|
||||
swp = apply_env(biquad(swp, "lp", 700.0, 0.7), env_exp(0.220, 0.080))
|
||||
place(out, swp, 0.0, 0.90)
|
||||
place(out, contact(rng, 0.010, 200.0, 1400.0, 0.0030), 0.190, 0.70)
|
||||
thud = apply_env(tone(0.140, 68.0), env_exp(0.140, 0.040))
|
||||
place(out, thud, 0.190, 0.75)
|
||||
x = room(out, mix=0.10, decay=0.100)
|
||||
x = soft_sat(tape(x, lp=4200.0), 1.5)
|
||||
return finish(limit(x, d), 0.003, 0.050)
|
||||
|
||||
|
||||
def s_hour_end(rng: random.Random) -> list:
|
||||
"""Сигнал смены: колокольные две ноты."""
|
||||
"""Сигнал смены: интеркомный колокол, G5 → C5 (квинта вниз)."""
|
||||
del rng
|
||||
out = silence(DUR["hour_end"])
|
||||
for at, f in ((0.0, 783.99), (0.260, 1046.50)):
|
||||
for mult, w, tau in ((1.0, 1.0, 0.34), (2.76, 0.35, 0.18), (5.4, 0.15, 0.10)):
|
||||
seg = DUR["hour_end"] - at
|
||||
t = apply_env(tone(seg, f * mult), env_exp(seg, tau))
|
||||
place(out, t, at, w)
|
||||
return finish(out, 0.003, 0.100)
|
||||
d = DUR["hour_end"]
|
||||
out = silence(d)
|
||||
for at, f, g in ((0.0, G5, 1.0), (0.270, C5, 0.95)):
|
||||
seg = d - at
|
||||
place(out, warm_bell(seg, f, tau=0.240, index=1.6), at, g)
|
||||
place(out, warm_bell(0.500, C3, tau=0.340, index=0.8), 0.010, 0.25)
|
||||
x = room(out, mix=0.18, decay=0.160)
|
||||
x = biquad(x, "hp", 220.0, 0.7) # маленький интеркомный динамик
|
||||
x = soft_sat(tape(x, wow=1.0, lp=4200.0), 1.4)
|
||||
return finish(limit(x, d), 0.003, 0.100)
|
||||
|
||||
|
||||
def s_run_end(rng: random.Random) -> list:
|
||||
"""Длинный спад конца забега: удар + низ + уходящий аккорд."""
|
||||
out = silence(DUR["run_end"])
|
||||
impact = biquad(biquad(noise(0.030, rng), "hp", 300.0, 0.7), "lp", 4200.0, 0.7)
|
||||
"""Конец забега: удар, низкий выдох и тёплый аккорд C3/G3/C4."""
|
||||
d = DUR["run_end"]
|
||||
out = silence(d)
|
||||
impact = biquad(biquad(noise(0.030, rng), "hp", 250.0, 0.7), "lp", 3200.0, 0.7)
|
||||
impact = apply_env(impact, env_exp(0.030, 0.010))
|
||||
place(out, impact, 0.0, 0.9)
|
||||
low = tone(0.500, lambda t: 38.0 + 92.0 * math.exp(-t / 0.11))
|
||||
low = apply_env(low, env_exp(0.500, 0.150))
|
||||
place(out, low, 0.0, 1.0)
|
||||
for f in (130.81, 196.00, 261.63):
|
||||
t = rich(0.790, f, [(1.0, 1.0), (2.0, 0.40), (3.0, 0.18)])
|
||||
t = apply_env(t, env_exp(0.790, 0.360))
|
||||
place(out, t, 0.010, 0.75)
|
||||
shim = apply_env(tone(0.500, 1180.0), env_exp(0.500, 0.220))
|
||||
place(out, shim, 0.0, 0.20)
|
||||
return finish(out, 0.004, 0.150)
|
||||
place(out, impact, 0.0, 0.80)
|
||||
low = tone(0.600, lambda t: 38.0 + 92.0 * math.exp(-t / 0.110))
|
||||
low = apply_env(low, env_exp(0.600, 0.150))
|
||||
place(out, low, 0.0, 0.90)
|
||||
for f, g in ((C3, 0.55), (G3, 0.42), (C4, 0.50)):
|
||||
t = warm_bell(0.740, f, tau=0.340, index=0.80)
|
||||
place(out, t, 0.010, g)
|
||||
place(out, warm_bell(0.500, C5, tau=0.220, index=0.65), 0.050, 0.20)
|
||||
x = room(out, mix=0.20, decay=0.180)
|
||||
x = soft_sat(tape(x, wow=1.4, lp=4400.0), 1.5)
|
||||
return finish(limit(x, d), 0.004, 0.150)
|
||||
|
||||
|
||||
def s_ok(rng: random.Random) -> list:
|
||||
"""Мягкое подтверждение: короткая фраза колоколом C5 → G5."""
|
||||
del rng
|
||||
d = DUR["ok"]
|
||||
out = silence(d)
|
||||
for f, at, g in ((C5, 0.0, 0.85), (G5, 0.065, 1.0)):
|
||||
seg = d - at
|
||||
place(out, warm_bell(seg, f, tau=0.110, index=0.90), at, g)
|
||||
x = room(out, mix=0.08, decay=0.070)
|
||||
x = soft_sat(tape(x, lp=5200.0), 1.3)
|
||||
return finish(limit(x, d), 0.003, 0.030)
|
||||
|
||||
|
||||
def s_bad(rng: random.Random) -> list:
|
||||
"""Мягкий отказ: две низкие деревянные ноты G3 → C3, без «бзз»."""
|
||||
d = DUR["bad"]
|
||||
out = silence(d)
|
||||
for f, at, g in ((G3, 0.0, 0.90), (C3, 0.085, 1.0)):
|
||||
seg = d - at
|
||||
place(out, muted_reed(seg, f, tau=0.105, lp=1100.0), at, g)
|
||||
place(out, apply_env(tone(0.150, 65.0), env_exp(0.150, 0.045)), 0.0, 0.30)
|
||||
x = room(out, mix=0.07, decay=0.080)
|
||||
x = soft_sat(tape(x, lp=2900.0), 1.3)
|
||||
return finish(limit(x, d), 0.003, 0.035)
|
||||
|
||||
|
||||
def s_stamp(rng: random.Random) -> list:
|
||||
"""Печать/штамп: бумажный удар, деревянное тело и короткий хвост."""
|
||||
d = DUR["stamp"]
|
||||
out = silence(d)
|
||||
th = tone(0.200, lambda t: 70.0 + 45.0 * math.exp(-t / 0.015))
|
||||
th = apply_env(th, env_exp(0.200, 0.040))
|
||||
place(out, th, 0.0, 1.0)
|
||||
wood = contact(rng, 0.015, 180.0, 1000.0, 0.0040)
|
||||
place(out, wood, 0.0, 0.70)
|
||||
crunch = biquad(noise(0.060, rng), "bp", 1200.0, 0.8)
|
||||
crunch = apply_env(crunch, env_exp(0.060, 0.015))
|
||||
place(out, crunch, 0.004, 0.40)
|
||||
x = room(out, mix=0.10, decay=0.100)
|
||||
x = soft_sat(tape(x, lp=4200.0), 1.5)
|
||||
return finish(limit(x, d), 0.002, 0.035)
|
||||
|
||||
|
||||
def s_hum(rng: random.Random) -> list:
|
||||
@@ -640,6 +874,9 @@ SFX_GENERATORS = {
|
||||
"money_down": s_money_down,
|
||||
"hour_end": s_hour_end,
|
||||
"run_end": s_run_end,
|
||||
"ok": s_ok,
|
||||
"bad": s_bad,
|
||||
"stamp": s_stamp,
|
||||
}
|
||||
|
||||
|
||||
@@ -660,6 +897,9 @@ def check_sfx(name: str, x: list) -> list:
|
||||
errs.append("пик %.2f dBFS" % db)
|
||||
if max(abs(v) for v in x) > 1.0:
|
||||
errs.append("клип")
|
||||
dc = sum(x) / max(1, len(x))
|
||||
if abs(dc) > 0.001:
|
||||
errs.append("DC %.4f" % dc)
|
||||
return errs
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://c5e6gil6gxxmp"
|
||||
path="res://.godot/imported/defeat.mp3-7e698f993d537d59b939608a0abcf708.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/music/defeat.mp3"
|
||||
dest_files=["res://.godot/imported/defeat.mp3-7e698f993d537d59b939608a0abcf708.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://b6ua70k24xsp7"
|
||||
path="res://.godot/imported/game.mp3-3720d552177761471b052a3420216bb1.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/music/game.mp3"
|
||||
dest_files=["res://.godot/imported/game.mp3-3720d552177761471b052a3420216bb1.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://b6vxk0i2pd0of"
|
||||
path="res://.godot/imported/low_sla.mp3-a6f69b93336356d63c353431bcf1405a.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/music/low_sla.mp3"
|
||||
dest_files=["res://.godot/imported/low_sla.mp3-a6f69b93336356d63c353431bcf1405a.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://bxdgubpqsktv3"
|
||||
path="res://.godot/imported/magazine.mp3-b6f09f5db14feb1428451d6c639c4105.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/music/magazine.mp3"
|
||||
dest_files=["res://.godot/imported/magazine.mp3-b6f09f5db14feb1428451d6c639c4105.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://dp3n8od21isy8"
|
||||
path="res://.godot/imported/menu.mp3-c0c7e41802fae0234a014a5e97694acb.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/music/menu.mp3"
|
||||
dest_files=["res://.godot/imported/menu.mp3-c0c7e41802fae0234a014a5e97694acb.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://denqdlq1w1njm"
|
||||
path="res://.godot/imported/bad.wav-9f1e2a623126c4b4b35ec8ba5cfa4126.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/sfx/bad.wav"
|
||||
dest_files=["res://.godot/imported/bad.wav-9f1e2a623126c4b4b35ec8ba5cfa4126.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dwc4yqps6p1p7"
|
||||
path="res://.godot/imported/ok.wav-dbf7928cddffd4f6fe92ad7a1655b0de.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/sfx/ok.wav"
|
||||
dest_files=["res://.godot/imported/ok.wav-dbf7928cddffd4f6fe92ad7a1655b0de.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dpebsgc1k2kfu"
|
||||
path="res://.godot/imported/stamp.wav-482ea9bc9409e806d9433c6217eb3bc6.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/sfx/stamp.wav"
|
||||
dest_files=["res://.godot/imported/stamp.wav-482ea9bc9409e806d9433c6217eb3bc6.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
@@ -1,123 +1,653 @@
|
||||
{
|
||||
"_meta": {
|
||||
"file": "balance.json",
|
||||
"version": "1.1",
|
||||
"rules": "docs/GDD.md (компактная сводка) + docs/DECISIONS.md (D01–D17)",
|
||||
"note": "null = параметр не задан в постановке, уточнить до кода (см. _todo). Никаких выдуманных чисел.",
|
||||
"_todo": [
|
||||
"attacks.junk.spawn_probability — не задан в спецификации (явно null)",
|
||||
"income_target_by_hour: часы 4,6,7,8 — интерполяция, уточнить",
|
||||
"difficulty.h2 packet_timer/request_timer: формула даёт 26.325/50.15 (19.5×1.35 и 29.5×1.7 — у запросов отдельный grace)"
|
||||
]
|
||||
"version": "2.0",
|
||||
"rules": "PEAK_HOUR_V2_SHOP_FILES_MINIGAMES.md §2, §3, §6, §8 + Appendix A",
|
||||
"note": "v2: 8 lot families, 3 offers, rerolls, dense send-minigame selection. Legacy sections (difficulty/spawn/progression/timers/connections/channel) kept for the not-yet-rewritten HourDirector. New code reads constants/hours/grid/shop_rules/shop/minigames/attacks/payout/sla."
|
||||
},
|
||||
"constants": {
|
||||
"hour_seconds": 300,
|
||||
"run_hours": 9,
|
||||
"sla_start": 128,
|
||||
"sla_max": 200,
|
||||
"money_start": 150,
|
||||
"buffer_base": 4,
|
||||
"buffer_per_upgrade": 2,
|
||||
"buffer_max": 8,
|
||||
"slots_start": 2,
|
||||
"slots_max": 4,
|
||||
"max_sections": 6,
|
||||
"files_per_folder": 16,
|
||||
"money_start": 50,
|
||||
"sla_start": 45,
|
||||
"sla_base": 45,
|
||||
"sla_per_level": 30,
|
||||
"sla_hard_cap": 165,
|
||||
"sla_regen_per_hour": 12,
|
||||
"slots_start": 1,
|
||||
"slots_max": 5,
|
||||
"channel_capacity": 10,
|
||||
"channel_per_level": 6,
|
||||
"channel_capacity_max": 46,
|
||||
"buffer_base": 6,
|
||||
"buffer_per_level": 1,
|
||||
"buffer_max": 12,
|
||||
"request_timer": 30,
|
||||
"request_timer_per_level": 7,
|
||||
"request_timer_max": 65,
|
||||
"packet_timer": 20,
|
||||
"folder_cols": 5,
|
||||
"folder_rows": 4,
|
||||
"folders_start": 2,
|
||||
"folders_max": 6,
|
||||
"folder_new_cost": 180,
|
||||
"folder_price_start": 150,
|
||||
"folder_price_step": 100,
|
||||
"max_sections": 1,
|
||||
"section_start": "C",
|
||||
"folders_per_section": 12,
|
||||
"files_per_folder": 48,
|
||||
"send_window_visible_cards": 4,
|
||||
"send_scroll_card_height_px": 56,
|
||||
"required_name_char_limit": 12,
|
||||
"file_cooldown_after_send_sec": 20,
|
||||
"required_per_request": { "min": 1, "max": 3 }
|
||||
"file_cooldown_after_send_sec": 0,
|
||||
"required_per_request": { "min": 1, "max": 4 },
|
||||
"spawn_window_ratio": 0.75,
|
||||
"spawn_jitter": 0.2,
|
||||
"buffer_per_upgrade": 1,
|
||||
"udp_success_base": 0.40,
|
||||
"udp_success_max": 0.80,
|
||||
"udp_fail_sla": -5,
|
||||
"udp_retry_cooldown_sec": 4,
|
||||
"udp_request_ratio": 0.35,
|
||||
"send_quality_min": 0.45,
|
||||
"file_expire_base": 0.03,
|
||||
"file_expire_step": 0.05,
|
||||
"file_expire_cap": 0.55
|
||||
},
|
||||
"economy": {
|
||||
"start_stats": {
|
||||
"money": 50,
|
||||
"sla": 45,
|
||||
"sla_cap": 45,
|
||||
"slots": 1,
|
||||
"channel_capacity": 10,
|
||||
"buffer": 6,
|
||||
"request_timer": 30,
|
||||
"packet_timer": 20,
|
||||
"folder_grid": [5, 4],
|
||||
"money_mult": 1.0,
|
||||
"speed_mult": 1.0
|
||||
},
|
||||
"formulas": {
|
||||
"max_sla": "clamp(45 + 30 * sla_level, 45, 165)",
|
||||
"sla_regen": "sla = min(sla_cap, sla + 12) в начале каждого часа",
|
||||
"slots": "1 + slot_level (max 5)",
|
||||
"channel_capacity": "10 + 6 * channel_level",
|
||||
"buffer": "min(12, 6 + channel_level)",
|
||||
"request_timer": "min(65, 30 + 7 * timer_level)",
|
||||
"packet_timer": "20 (не покупается)",
|
||||
"speed": "pow(0.90, speed_level)",
|
||||
"money_mult": "1 + 0.05 * money_level",
|
||||
"folder_dims": "[[5,4],[6,4],[7,5],[8,5],[8,6],[9,6]][folder_level]",
|
||||
"transfer_time": "base_time_sec * pow(0.90, speed_level) * max(1.0, load / capacity)",
|
||||
"protocol_time": "UDP ×0.8, TCP ×1.0 (только отправка)"
|
||||
}
|
||||
},
|
||||
"types": {
|
||||
"text": {
|
||||
"name_ru": "Текст",
|
||||
"ext": ".txt",
|
||||
"icon": "▤",
|
||||
"recv_time": 1.0,
|
||||
"send_time": 1.0,
|
||||
"recv_fee": 2,
|
||||
"base_pay": 20,
|
||||
"weight": 6,
|
||||
"minigame_coeff": 1.0,
|
||||
"sla_loss": 2
|
||||
"ext": "txt",
|
||||
"icon": "text",
|
||||
"size_kb": 64,
|
||||
"cells": 1,
|
||||
"base_time_sec": 3,
|
||||
"base_pay": 18,
|
||||
"recv_fee": 1,
|
||||
"weight": 1,
|
||||
"minigame_coeff": 1,
|
||||
"sla_loss": 3,
|
||||
"shapes": ["O1"]
|
||||
},
|
||||
"sound": {
|
||||
"name_ru": "Звук",
|
||||
"ext": ".wav",
|
||||
"icon": "♪",
|
||||
"recv_time": 1.5,
|
||||
"send_time": 2.0,
|
||||
"recv_fee": 3,
|
||||
"base_pay": 35,
|
||||
"weight": 10,
|
||||
"minigame_coeff": 2.0,
|
||||
"sla_loss": 4
|
||||
"ext": "mp3",
|
||||
"icon": "sound",
|
||||
"size_kb": 3072,
|
||||
"cells": 2,
|
||||
"base_time_sec": 5,
|
||||
"base_pay": 30,
|
||||
"recv_fee": 2,
|
||||
"weight": 2,
|
||||
"minigame_coeff": 2,
|
||||
"sla_loss": 6,
|
||||
"shapes": ["I2H", "I2V"]
|
||||
},
|
||||
"photo": {
|
||||
"name_ru": "Фото",
|
||||
"ext": ".jpg",
|
||||
"icon": "▣",
|
||||
"recv_time": 2.0,
|
||||
"send_time": 3.0,
|
||||
"recv_fee": 5,
|
||||
"base_pay": 55,
|
||||
"weight": 14,
|
||||
"minigame_coeff": 3.0,
|
||||
"sla_loss": 8
|
||||
"ext": "jpg",
|
||||
"icon": "photo",
|
||||
"size_kb": 5120,
|
||||
"cells": 3,
|
||||
"base_time_sec": 8,
|
||||
"base_pay": 44,
|
||||
"recv_fee": 4,
|
||||
"weight": 4,
|
||||
"minigame_coeff": 4,
|
||||
"sla_loss": 11,
|
||||
"shapes": ["I3H", "I3V", "L3a", "L3b", "L3c", "L3d"]
|
||||
},
|
||||
"video": {
|
||||
"name_ru": "Видео",
|
||||
"ext": ".mp4",
|
||||
"icon": "▶",
|
||||
"recv_time": 3.0,
|
||||
"send_time": 5.0,
|
||||
"recv_fee": 8,
|
||||
"base_pay": 90,
|
||||
"weight": 22,
|
||||
"minigame_coeff": 4.0,
|
||||
"sla_loss": 16
|
||||
"ext": "mp4",
|
||||
"icon": "video",
|
||||
"size_kb": 20480,
|
||||
"cells": 4,
|
||||
"base_time_sec": 12,
|
||||
"base_pay": 69,
|
||||
"recv_fee": 6,
|
||||
"weight": 8,
|
||||
"minigame_coeff": 8,
|
||||
"sla_loss": 21,
|
||||
"shapes": ["I4H", "I4V", "O4", "T4a", "T4b", "T4c", "T4d", "L4a", "L4b", "L4c", "L4d", "J4a", "J4b", "J4c", "J4d", "S4", "Z4"]
|
||||
}
|
||||
},
|
||||
"hours": [
|
||||
{
|
||||
"hour": 1,
|
||||
"duration": 120,
|
||||
"files": 8,
|
||||
"active_requests": 2,
|
||||
"attacks": 0,
|
||||
"income_target": 100,
|
||||
"send_interval": 20,
|
||||
"type_weights": { "text": 1.0, "sound": 0.0, "photo": 0.0, "video": 0.0 },
|
||||
"request_mix": { "1": 1.0, "2": 0.0, "3": 0.0, "4": 0.0 },
|
||||
"request_type_weights": { "text": 1.0, "sound": 0.0, "photo": 0.0, "video": 0.0 }
|
||||
},
|
||||
{
|
||||
"hour": 2,
|
||||
"duration": 150,
|
||||
"files": 8,
|
||||
"active_requests": 2,
|
||||
"attacks": 1,
|
||||
"income_target": 220,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.80, "sound": 0.20, "photo": 0.0, "video": 0.0 },
|
||||
"request_mix": { "1": 0.30, "2": 0.70, "3": 0.0, "4": 0.0 },
|
||||
"request_type_weights": { "text": 0.80, "sound": 0.20, "photo": 0.0, "video": 0.0 }
|
||||
},
|
||||
{
|
||||
"hour": 3,
|
||||
"duration": 180,
|
||||
"files": 9,
|
||||
"active_requests": 2,
|
||||
"attacks": 1,
|
||||
"income_target": 380,
|
||||
"send_interval": 18,
|
||||
"type_weights": { "text": 0.50, "sound": 0.50, "photo": 0.0, "video": 0.0 },
|
||||
"request_mix": { "1": 0.20, "2": 0.80, "3": 0.0, "4": 0.0 },
|
||||
"request_type_weights": { "text": 0.50, "sound": 0.50, "photo": 0.0, "video": 0.0 }
|
||||
},
|
||||
{
|
||||
"hour": 4,
|
||||
"duration": 210,
|
||||
"files": 10,
|
||||
"active_requests": 3,
|
||||
"attacks": 2,
|
||||
"income_target": 560,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.40, "sound": 0.30, "photo": 0.30, "video": 0.0 },
|
||||
"request_mix": { "1": 0.0, "2": 0.70, "3": 0.30, "4": 0.0 },
|
||||
"request_type_weights": { "text": 0.40, "sound": 0.30, "photo": 0.30, "video": 0.0 }
|
||||
},
|
||||
{
|
||||
"hour": 5,
|
||||
"duration": 240,
|
||||
"files": 11,
|
||||
"active_requests": 3,
|
||||
"attacks": 2,
|
||||
"income_target": 760,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.35, "sound": 0.35, "photo": 0.30, "video": 0.0 },
|
||||
"request_mix": { "1": 0.0, "2": 0.55, "3": 0.45, "4": 0.0 },
|
||||
"request_type_weights": { "text": 0.35, "sound": 0.35, "photo": 0.30, "video": 0.0 }
|
||||
},
|
||||
{
|
||||
"hour": 6,
|
||||
"duration": 270,
|
||||
"files": 11,
|
||||
"active_requests": 3,
|
||||
"attacks": 3,
|
||||
"income_target": 980,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.30, "sound": 0.30, "photo": 0.30, "video": 0.10 },
|
||||
"request_mix": { "1": 0.0, "2": 0.50, "3": 0.35, "4": 0.15 },
|
||||
"request_type_weights": { "text": 0.30, "sound": 0.30, "photo": 0.30, "video": 0.10 }
|
||||
},
|
||||
{
|
||||
"hour": 7,
|
||||
"duration": 300,
|
||||
"files": 12,
|
||||
"active_requests": 4,
|
||||
"attacks": 3,
|
||||
"income_target": 1200,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.22, "sound": 0.23, "photo": 0.30, "video": 0.25 },
|
||||
"request_mix": { "1": 0.0, "2": 0.60, "3": 0.15, "4": 0.25 },
|
||||
"request_type_weights": { "text": 0.22, "sound": 0.23, "photo": 0.30, "video": 0.25 }
|
||||
},
|
||||
{
|
||||
"hour": 8,
|
||||
"duration": 330,
|
||||
"files": 12,
|
||||
"active_requests": 4,
|
||||
"attacks": 4,
|
||||
"income_target": 1450,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.15, "sound": 0.25, "photo": 0.35, "video": 0.25 },
|
||||
"request_mix": { "1": 0.0, "2": 0.60, "3": 0.20, "4": 0.20 },
|
||||
"request_type_weights": { "text": 0.15, "sound": 0.25, "photo": 0.35, "video": 0.25 }
|
||||
},
|
||||
{
|
||||
"hour": 9,
|
||||
"duration": 360,
|
||||
"files": 12,
|
||||
"active_requests": 4,
|
||||
"attacks": 4,
|
||||
"income_target": 1700,
|
||||
"send_interval": 24,
|
||||
"type_weights": { "text": 0.10, "sound": 0.15, "photo": 0.30, "video": 0.45 },
|
||||
"request_mix": { "1": 0.0, "2": 0.70, "3": 0.10, "4": 0.20 },
|
||||
"request_type_weights": { "text": 0.10, "sound": 0.15, "photo": 0.30, "video": 0.45 }
|
||||
}
|
||||
],
|
||||
"payout": {
|
||||
"speed_base": 0.6,
|
||||
"speed_bonus": 0.4,
|
||||
"quality_base": 0.5,
|
||||
"quality_bonus": 0.5,
|
||||
"multi_step": 0.15,
|
||||
"protocol_mult": { "TCP": 1.15, "UDP": 0.85 },
|
||||
"udp_time_mult": 0.8,
|
||||
"streak_step": 0.02,
|
||||
"streak_cap": 10,
|
||||
"money_mult_step": 0.05,
|
||||
"formula": "payout = int(round(float(base_sum) * speed * quality * multi * protocol * streak * money_mult))",
|
||||
"speed_formula": "speed = 0.6 + 0.4 * clamp(time_left / timer_total, 0, 1)",
|
||||
"quality_formula": "quality = 0.5 + 0.5 * q",
|
||||
"multi_formula": "multi = 1 + 0.15 * (n - 1)",
|
||||
"streak_formula": "streak = 1 + 0.02 * min(10, streak)",
|
||||
"money_mult_formula": "money_mult = 1 + 0.05 * money_level",
|
||||
"note": "Только float-арифметика: никаких int/int и никакого ^ для степени."
|
||||
},
|
||||
"sla": {
|
||||
"start": 45,
|
||||
"regen_per_hour": 12,
|
||||
"defeat_at_zero": true,
|
||||
"timeout_fine_base": 14,
|
||||
"timeout_fine_per_hour": 3,
|
||||
"timeout_fine_formula": "-(14 + 3*hour) SLA",
|
||||
"wrong_attach_fine": -2,
|
||||
"lost_file_fine": -5,
|
||||
"buffer_overflow_fine": -5,
|
||||
"reject_money": -8,
|
||||
"reject_sla": -7,
|
||||
"connect_miss_sla": -6,
|
||||
"folder_destroyed_fine": -5,
|
||||
"sla_lose": "SLA <= 0 во время часа = поражение забега"
|
||||
},
|
||||
"grid": {
|
||||
"gap": 6,
|
||||
"cell_min": 56,
|
||||
"cell_max": 104,
|
||||
"view_w": 558,
|
||||
"view_h": 470,
|
||||
"folder_cols": 5,
|
||||
"folder_rows": 4,
|
||||
"folder_dims": [[5, 4], [6, 4], [7, 5], [8, 5], [8, 6], [9, 6]],
|
||||
"table_cols": 4,
|
||||
"table_rows": 6,
|
||||
"cell_formula": "cell = clamp(floor(min((view_w - gap*(cols-1))/cols, (view_h - gap*(rows-1))/rows)), 56, 104)",
|
||||
"shapes": {
|
||||
"O1": { "cells": [[0, 0]], "w": 1, "h": 1 },
|
||||
"I2H": { "cells": [[0, 0], [1, 0]], "w": 2, "h": 1 },
|
||||
"I2V": { "cells": [[0, 0], [0, 1]], "w": 1, "h": 2 },
|
||||
"I3H": { "cells": [[0, 0], [1, 0], [2, 0]], "w": 3, "h": 1 },
|
||||
"I3V": { "cells": [[0, 0], [0, 1], [0, 2]], "w": 1, "h": 3 },
|
||||
"L3a": { "cells": [[0, 0], [0, 1], [1, 1]], "w": 2, "h": 2 },
|
||||
"L3b": { "cells": [[0, 0], [1, 0], [0, 1]], "w": 2, "h": 2 },
|
||||
"L3c": { "cells": [[1, 0], [0, 1], [1, 1]], "w": 2, "h": 2 },
|
||||
"L3d": { "cells": [[0, 0], [1, 0], [1, 1]], "w": 2, "h": 2 },
|
||||
"I4H": { "cells": [[0, 0], [1, 0], [2, 0], [3, 0]], "w": 4, "h": 1 },
|
||||
"I4V": { "cells": [[0, 0], [0, 1], [0, 2], [0, 3]], "w": 1, "h": 4 },
|
||||
"O4": { "cells": [[0, 0], [1, 0], [0, 1], [1, 1]], "w": 2, "h": 2 },
|
||||
"T4a": { "cells": [[0, 0], [1, 0], [2, 0], [1, 1]], "w": 3, "h": 2 },
|
||||
"T4b": { "cells": [[0, 1], [1, 1], [2, 1], [1, 0]], "w": 3, "h": 2 },
|
||||
"T4c": { "cells": [[0, 0], [0, 1], [1, 1], [0, 2]], "w": 2, "h": 3 },
|
||||
"T4d": { "cells": [[1, 0], [1, 1], [0, 1], [1, 2]], "w": 2, "h": 3 },
|
||||
"L4a": { "cells": [[0, 0], [0, 1], [0, 2], [1, 2]], "w": 2, "h": 3 },
|
||||
"L4b": { "cells": [[0, 0], [1, 0], [2, 0], [0, 1]], "w": 3, "h": 2 },
|
||||
"L4c": { "cells": [[0, 0], [1, 0], [1, 1], [1, 2]], "w": 2, "h": 3 },
|
||||
"L4d": { "cells": [[2, 0], [0, 1], [1, 1], [2, 1]], "w": 3, "h": 2 },
|
||||
"J4a": { "cells": [[1, 0], [1, 1], [1, 2], [0, 2]], "w": 2, "h": 3 },
|
||||
"J4b": { "cells": [[0, 0], [1, 0], [2, 0], [2, 1]], "w": 3, "h": 2 },
|
||||
"J4c": { "cells": [[0, 0], [0, 1], [1, 1], [2, 1]], "w": 3, "h": 2 },
|
||||
"J4d": { "cells": [[0, 0], [0, 1], [0, 2], [1, 0]], "w": 2, "h": 3 },
|
||||
"S4": { "cells": [[1, 0], [2, 0], [0, 1], [1, 1]], "w": 3, "h": 2 },
|
||||
"Z4": { "cells": [[0, 0], [1, 0], [1, 1], [2, 1]], "w": 3, "h": 2 }
|
||||
},
|
||||
"type_shapes": {
|
||||
"text": ["O1"],
|
||||
"sound": ["I2H", "I2V"],
|
||||
"photo": ["I3H", "I3V", "L3a", "L3b", "L3c", "L3d"],
|
||||
"video": ["I4H", "I4V", "O4", "T4a", "T4b", "T4c", "T4d", "L4a", "L4b", "L4c", "L4d", "J4a", "J4b", "J4c", "J4d", "S4", "Z4"]
|
||||
}
|
||||
},
|
||||
"shop_rules": {
|
||||
"offers": 3,
|
||||
"unlock_hour": 2,
|
||||
"reroll_costs": [40, 80, 120],
|
||||
"max_rerolls": 3,
|
||||
"last_offer_weight": 0.5,
|
||||
"pity_hours": 2,
|
||||
"pity_weight": 1.5,
|
||||
"note": "Веса берутся из shop[].weight_by_hour (9 значений, индекс = hour-1). Фазы H2-3/H4-6/H7-9 — таблица §3.1 — источник правды."
|
||||
},
|
||||
"shop": [
|
||||
{
|
||||
"id": "lot_slots",
|
||||
"name_ru": "Порты коммутатора",
|
||||
"desc_ru": "Ещё один порт: приём и отправка могут идти параллельно.",
|
||||
"effect": "connection_slots",
|
||||
"value": 1,
|
||||
"max_level": 4,
|
||||
"prices": [90, 200, 400, 700],
|
||||
"level_names": ["Второй порт", "Третий порт", "Четвёртый порт", "Магистральный порт"],
|
||||
"weight_by_hour": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.8, 0.8, 0.8]
|
||||
},
|
||||
{
|
||||
"id": "lot_speed",
|
||||
"name_ru": "Оптоволокно",
|
||||
"desc_ru": "Скорость приёма и отправки +10% за уровень (умножается).",
|
||||
"effect": "transfer_speed",
|
||||
"value": 0.10,
|
||||
"max_level": 6,
|
||||
"prices": [55, 75, 100, 135, 180, 245],
|
||||
"level_names": ["Витая пара", "Экранированная пара", "Оптоволокно", "Гигабитная линия", "Магистраль", "Тёмное волокно"],
|
||||
"weight_by_hour": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.8, 0.8, 0.8]
|
||||
},
|
||||
{
|
||||
"id": "lot_sla",
|
||||
"name_ru": "Резервный ИБП",
|
||||
"desc_ru": "Держит узел живым: кап SLA +30 и мгновенное восстановление +30.",
|
||||
"effect": "sla_cap",
|
||||
"value": 30,
|
||||
"max_level": 4,
|
||||
"prices": [80, 160, 270, 430],
|
||||
"level_names": ["Резервный ИБП", "Горячий резерв", "Двойное питание", "Автономный контур"],
|
||||
"weight_by_hour": [0.8, 0.8, 0.8, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
|
||||
},
|
||||
{
|
||||
"id": "lot_shield",
|
||||
"name_ru": "Заземление",
|
||||
"desc_ru": "Автоматически гасит атаку: 70% награды, без штрафа.",
|
||||
"effect": "shield_charge",
|
||||
"value": 1,
|
||||
"max_level": 3,
|
||||
"prices": [100, 220, 410],
|
||||
"level_names": ["Заземление", "Громоотвод", "Защитный контур"],
|
||||
"weight_by_hour": [0.3, 0.3, 0.3, 0.8, 0.8, 0.8, 1.0, 1.0, 1.0]
|
||||
},
|
||||
{
|
||||
"id": "lot_money",
|
||||
"name_ru": "Корпоративный контракт",
|
||||
"desc_ru": "Оплата за контейнеры +5% за уровень.",
|
||||
"effect": "money_mult",
|
||||
"value": 0.05,
|
||||
"max_level": 8,
|
||||
"prices": [70, 95, 130, 170, 220, 285, 365, 465],
|
||||
"level_names": ["Тарифный план", "Корпоративный контракт", "SLA-пакет", "Госзаказ", "Экспортный контракт", "Франшиза", "Монополия", "Золотая жила"],
|
||||
"weight_by_hour": [0.6, 0.6, 0.6, 1.0, 1.0, 1.0, 1.2, 1.2, 1.2]
|
||||
},
|
||||
{
|
||||
"id": "lot_folder",
|
||||
"name_ru": "Складской стеллаж",
|
||||
"desc_ru": "Больше места под фигуры: 20 → 24 клетки на первом уровне (до 54).",
|
||||
"effect": "folder_cells",
|
||||
"value": 1,
|
||||
"max_level": 5,
|
||||
"prices": [50, 100, 180, 290, 440],
|
||||
"level_names": ["Стеллаж", "Секция стеллажа", "Складская линия", "Автоматизированный склад", "Ангар"],
|
||||
"weight_by_hour": [1.0, 1.0, 1.0, 0.9, 0.9, 0.9, 0.8, 0.8, 0.8]
|
||||
},
|
||||
{
|
||||
"id": "lot_channel",
|
||||
"name_ru": "Пропускная способность",
|
||||
"desc_ru": "Канал +6 и буфер приёма +1 за уровень.",
|
||||
"effect": "channel_capacity",
|
||||
"value": 6,
|
||||
"buffer_value": 1,
|
||||
"max_level": 6,
|
||||
"prices": [45, 70, 95, 130, 175, 230],
|
||||
"level_names": ["Канал 56k", "ISDN", "ADSL", "Выделенная линия", "Оптика", "Магистраль"],
|
||||
"weight_by_hour": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 0.9, 0.9]
|
||||
},
|
||||
{
|
||||
"id": "lot_timer",
|
||||
"name_ru": "Регламент обработки",
|
||||
"desc_ru": "Больше времени на поиск файлов и прикрепление: 30 → 37 с (до 65).",
|
||||
"effect": "request_timer",
|
||||
"value": 7,
|
||||
"max_level": 5,
|
||||
"prices": [45, 75, 115, 165, 230],
|
||||
"level_names": ["Регламент", "Продление сессии", "Гибкий график", "Приоритетная обработка", "Выделенный менеджер"],
|
||||
"weight_by_hour": [1.0, 1.0, 1.0, 0.9, 0.9, 0.9, 0.7, 0.7, 0.7]
|
||||
},
|
||||
{
|
||||
"id": "lot_udp",
|
||||
"name_ru": "UDP-повторитель",
|
||||
"desc_ru": "Надёжность UDP +10% за уровень: 40% → 80%.",
|
||||
"effect": "udp_success",
|
||||
"value": 0.10,
|
||||
"max_level": 4,
|
||||
"prices": [60, 120, 210, 350],
|
||||
"weight_by_hour": [0.8, 0.8, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
|
||||
}
|
||||
],
|
||||
"minigames": {
|
||||
"selection": {
|
||||
"order": ["dots", "sound", "text", "video", "photo", "maze"],
|
||||
"achievable_sums": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 32],
|
||||
"max_index": 26,
|
||||
"tier_max_rank": [7, 13],
|
||||
"rank_games": ["text", "sound", "dots", "photo", "maze", "dots", "photo", "video", "dots", "text", "video", "maze", "photo", "sound", "video", "text", "maze", "sound", "video", "sound", "photo", "dots", "dots", "maze", "maze", "photo", "video"],
|
||||
"formula": "S=sum(coeff по 1-4 файлам); rank=index of S in achievable_sums (или ближайшей меньшей); tier=1 при rank<=7, 2 при 8<=rank<=13, иначе 3; id=rank_games[rank]; S=0 → dots/T1",
|
||||
"note": "6 игр отправки (wires удалена), 3 ступени, ровно 27 достижимых сумм коэффициентов для 1-4 файлов. Таблица rank_games задаёт игру по rank напрямую (без modulo)."
|
||||
},
|
||||
"games": {
|
||||
"dots": {
|
||||
"name_ru": "Одна линия",
|
||||
"kind": "send",
|
||||
"tiers": [
|
||||
{ "grid": [4, 3], "obstacles": 2, "undo_penalty": 0.04 },
|
||||
{ "grid": [4, 4], "obstacles": 4, "undo_penalty": 0.04 },
|
||||
{ "grid": [5, 4], "obstacles": 6, "undo_penalty": 0.03 }
|
||||
]
|
||||
},
|
||||
"sound": {
|
||||
"name_ru": "Уровни",
|
||||
"kind": "send",
|
||||
"tiers": [
|
||||
{ "channels": 4, "tolerance": 0.11, "drift": 0.0, "drift_hz": 0.0 },
|
||||
{ "channels": 5, "tolerance": 0.08, "drift": 0.02, "drift_hz": 0.15 },
|
||||
{ "channels": 6, "tolerance": 0.06, "drift": 0.03, "drift_hz": 0.25 }
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"name_ru": "Чужое слово",
|
||||
"kind": "send",
|
||||
"tiers": [
|
||||
{ "words": 15, "aliens": 2, "lines": 2, "alien_kind": "foreign_language+typos" },
|
||||
{ "words": 19, "aliens": 3, "lines": 3, "alien_kind": "foreign_language+typos+semantic" },
|
||||
{ "words": 24, "aliens": 4, "lines": 3, "alien_kind": "foreign_language+typos+semantic+archaic" }
|
||||
]
|
||||
},
|
||||
"video": {
|
||||
"name_ru": "Раскадровка",
|
||||
"kind": "send",
|
||||
"tiers": [
|
||||
{ "frames": 4, "slots": 4, "decoys": 0, "mirror": false },
|
||||
{ "frames": 5, "slots": 4, "decoys": 1, "mirror": false },
|
||||
{ "frames": 6, "slots": 5, "decoys": 2, "mirror": true }
|
||||
]
|
||||
},
|
||||
"photo": {
|
||||
"name_ru": "Кадр",
|
||||
"kind": "send",
|
||||
"tiers": [
|
||||
{ "speed": 140, "frame_w": 180, "frame_h": 120, "shots": 4, "retarget_min": 0.6, "retarget_max": 1.0 },
|
||||
{ "speed": 200, "frame_w": 150, "frame_h": 100, "shots": 5, "retarget_min": 0.5, "retarget_max": 0.8 },
|
||||
{ "speed": 260, "frame_w": 120, "frame_h": 80, "shots": 6, "retarget_min": 0.35, "retarget_max": 0.65 }
|
||||
]
|
||||
},
|
||||
"maze": {
|
||||
"name_ru": "Лабиринт",
|
||||
"kind": "send",
|
||||
"tiers": [
|
||||
{ "cols": 11, "rows": 4, "width": 440, "height": 160, "speed": 470, "collision_penalty": 0.05, "dead_end_penalty": 0.02 },
|
||||
{ "cols": 13, "rows": 4, "width": 494, "height": 152, "speed": 520, "collision_penalty": 0.04, "dead_end_penalty": 0.02 },
|
||||
{ "cols": 15, "rows": 5, "width": 520, "height": 180, "speed": 560, "collision_penalty": 0.04, "dead_end_penalty": 0.02 }
|
||||
]
|
||||
},
|
||||
"junk": {
|
||||
"name_ru": "Фильтр",
|
||||
"kind": "attack",
|
||||
"tiers": [
|
||||
{ "cards": 9, "junk": 2, "hints": 1, "threshold": 0.6 },
|
||||
{ "cards": 10, "junk": 3, "hints": 2, "threshold": 0.65 },
|
||||
{ "cards": 11, "junk": 4, "hints": 2, "threshold": 0.7 },
|
||||
{ "cards": 12, "junk": 5, "hints": 3, "threshold": 0.75 }
|
||||
]
|
||||
},
|
||||
"ddos": {
|
||||
"name_ru": "Щит",
|
||||
"kind": "attack",
|
||||
"tiers": [
|
||||
{ "packets": 8, "speed": 110, "shield_width": 260, "interval": 0.55, "threshold": 0.6 },
|
||||
{ "packets": 10, "speed": 140, "shield_width": 220, "interval": 0.45, "threshold": 0.65 },
|
||||
{ "packets": 12, "speed": 170, "shield_width": 180, "interval": 0.35, "threshold": 0.7 },
|
||||
{ "packets": 14, "speed": 200, "shield_width": 150, "interval": 0.28, "threshold": 0.75 }
|
||||
]
|
||||
},
|
||||
"phishing": {
|
||||
"name_ru": "Паспорт",
|
||||
"kind": "attack",
|
||||
"tiers": [
|
||||
{ "clients": 3, "checks": ["name"], "threshold": 0.75 },
|
||||
{ "clients": 4, "checks": ["name", "department"], "threshold": 0.75 },
|
||||
{ "clients": 5, "checks": ["name", "department", "id"], "threshold": 0.8 },
|
||||
{ "clients": 6, "checks": ["name", "department", "id", "photo", "expiry"], "threshold": 0.8 }
|
||||
]
|
||||
},
|
||||
"ransomware": {
|
||||
"name_ru": "Карантин",
|
||||
"kind": "attack",
|
||||
"tiers": [
|
||||
{ "cards": 12, "exe": 1, "threshold": 0.5, "masking": "extension_visible" },
|
||||
{ "cards": 16, "exe": 2, "threshold": 0.55, "masking": "extension_small" },
|
||||
{ "cards": 20, "exe": 2, "threshold": 0.6, "masking": "document_icon" },
|
||||
{ "cards": 24, "exe": 3, "threshold": 0.65, "masking": "similar_names" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"attacks": {
|
||||
"schedule": [0, 1, 1, 2, 2, 3, 3, 4, 4],
|
||||
"tier_formula": "clamp(1 + floor((hour - 2) / 2.0), 1, 4)",
|
||||
"spawn_window": {
|
||||
"start_ratio": 0.25,
|
||||
"end_ratio": 0.85,
|
||||
"start_min_sec": 20,
|
||||
"start_max_sec": 60,
|
||||
"end_margin_sec": 20
|
||||
},
|
||||
"shield_reward_scale": 0.7,
|
||||
"money_scale": 1.0,
|
||||
"kinds": {
|
||||
"junk": {
|
||||
"name_ru": "Мусорный пакет",
|
||||
"minigame": "junk",
|
||||
"weight_by_hour": [0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
|
||||
"tiers": [
|
||||
{ "reward_money": 5, "reward_sla": 2, "fail_money": -10, "fail_sla": -7, "threshold": 0.6 },
|
||||
{ "reward_money": 10, "reward_sla": 3, "fail_money": -15, "fail_sla": -10, "threshold": 0.65 },
|
||||
{ "reward_money": 15, "reward_sla": 4, "fail_money": -20, "fail_sla": -12, "threshold": 0.7 },
|
||||
{ "reward_money": 20, "reward_sla": 5, "fail_money": -25, "fail_sla": -16, "threshold": 0.75 }
|
||||
]
|
||||
},
|
||||
"ddos": {
|
||||
"name_ru": "DDoS",
|
||||
"minigame": "ddos",
|
||||
"weight_by_hour": [0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
|
||||
"tiers": [
|
||||
{ "reward_money": 30, "reward_sla": 4, "fail_money": -10, "fail_sla": -20, "channel_debuff": 0.4, "debuff_sec": 15, "threshold": 0.6 },
|
||||
{ "reward_money": 40, "reward_sla": 5, "fail_money": -15, "fail_sla": -27, "channel_debuff": 0.4, "debuff_sec": 15, "threshold": 0.65 },
|
||||
{ "reward_money": 50, "reward_sla": 6, "fail_money": -20, "fail_sla": -34, "channel_debuff": 0.4, "debuff_sec": 15, "threshold": 0.7 },
|
||||
{ "reward_money": 60, "reward_sla": 7, "fail_money": -25, "fail_sla": -40, "channel_debuff": 0.4, "debuff_sec": 15, "threshold": 0.75 }
|
||||
]
|
||||
},
|
||||
"phishing": {
|
||||
"name_ru": "Фишинг",
|
||||
"minigame": "phishing",
|
||||
"weight_by_hour": [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
|
||||
"tiers": [
|
||||
{ "reward_money": 20, "reward_sla": 3, "fail_money": -40, "fail_sla": -14, "file_lock_sec": 60, "send_penalty_sec": 5, "threshold": 0.75 },
|
||||
{ "reward_money": 30, "reward_sla": 4, "fail_money": -50, "fail_sla": -16, "file_lock_sec": 60, "send_penalty_sec": 5, "threshold": 0.75 },
|
||||
{ "reward_money": 40, "reward_sla": 5, "fail_money": -60, "fail_sla": -20, "file_lock_sec": 60, "send_penalty_sec": 5, "threshold": 0.8 },
|
||||
{ "reward_money": 50, "reward_sla": 6, "fail_money": -70, "fail_sla": -24, "file_lock_sec": 60, "send_penalty_sec": 5, "threshold": 0.8 }
|
||||
]
|
||||
},
|
||||
"ransomware": {
|
||||
"name_ru": "Шифровальщик",
|
||||
"minigame": "ransomware",
|
||||
"weight_by_hour": [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0],
|
||||
"tiers": [
|
||||
{ "reward_money": 25, "reward_sla": 3, "fail_money": -30, "fail_sla": -14, "folder_block_sec": 15, "threshold": 0.5 },
|
||||
{ "reward_money": 35, "reward_sla": 4, "fail_money": -40, "fail_sla": -16, "folder_block_sec": 15, "threshold": 0.55 },
|
||||
{ "reward_money": 45, "reward_sla": 5, "fail_money": -50, "fail_sla": -20, "folder_block_sec": 15, "threshold": 0.6 },
|
||||
{ "reward_money": 55, "reward_sla": 6, "fail_money": -60, "fail_sla": -24, "folder_block_sec": 15, "threshold": 0.65 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"difficulty": [
|
||||
{ "hour": 1, "recv_interval": 12, "send_interval": 22, "max_requests": 2, "buffer": 4, "packet_timer": 30, "request_timer": 60, "attacks": 0 },
|
||||
{ "hour": 2, "recv_interval": 11, "send_interval": 20, "max_requests": 2, "buffer": 4, "packet_timer": 26.325, "request_timer": 50.15, "attacks": 1 },
|
||||
{ "hour": 3, "recv_interval": 10, "send_interval": 18, "max_requests": 3, "buffer": 5, "packet_timer": 22.8, "request_timer": 40.6, "attacks": 1 },
|
||||
{ "hour": 4, "recv_interval": 9, "send_interval": 17, "max_requests": 3, "buffer": 5, "packet_timer": 18.5, "request_timer": 28.5, "attacks": 2 },
|
||||
{ "hour": 5, "recv_interval": 8, "send_interval": 15, "max_requests": 4, "buffer": 6, "packet_timer": 18, "request_timer": 28, "attacks": 2 },
|
||||
{ "hour": 6, "recv_interval": 7.5, "send_interval": 14, "max_requests": 4, "buffer": 6, "packet_timer": 17.5, "request_timer": 27.5, "attacks": 3 },
|
||||
{ "hour": 7, "recv_interval": 7, "send_interval": 13, "max_requests": 4, "buffer": 6, "packet_timer": 17, "request_timer": 27, "attacks": 3 },
|
||||
{ "hour": 8, "recv_interval": 6.5, "send_interval": 12, "max_requests": 4, "buffer": 7, "packet_timer": 16.5, "request_timer": 26.5, "attacks": 4 },
|
||||
{ "hour": 9, "recv_interval": 6, "send_interval": 12, "max_requests": 4, "buffer": 7, "packet_timer": 16, "request_timer": 26, "attacks": 4 }
|
||||
{ "hour": 1, "recv_interval": 12, "send_interval": 22, "max_requests": 2, "buffer": 4, "packet_timer": 20, "request_timer": 30, "attacks": 0 },
|
||||
{ "hour": 2, "recv_interval": 11, "send_interval": 20, "max_requests": 5, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 1 },
|
||||
{ "hour": 3, "recv_interval": 10, "send_interval": 18, "max_requests": 7, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 1 },
|
||||
{ "hour": 4, "recv_interval": 9, "send_interval": 17, "max_requests": 9, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 2 },
|
||||
{ "hour": 5, "recv_interval": 8, "send_interval": 15, "max_requests": 10, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 2 },
|
||||
{ "hour": 6, "recv_interval": 7.5, "send_interval": 14, "max_requests": 11, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 3 },
|
||||
{ "hour": 7, "recv_interval": 7, "send_interval": 13, "max_requests": 11, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 3 },
|
||||
{ "hour": 8, "recv_interval": 6.5, "send_interval": 12, "max_requests": 12, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 4 },
|
||||
{ "hour": 9, "recv_interval": 6, "send_interval": 12, "max_requests": 12, "buffer": 6, "packet_timer": 20, "request_timer": 30, "attacks": 4 }
|
||||
],
|
||||
"difficulty_rules": {
|
||||
"table_end_hour": 8,
|
||||
"after_end_hour": "значения часа 8; +5% к скорости спавна каждые 2 часа",
|
||||
"spawn_speedup_after_end": 0.05,
|
||||
"table_end_hour": 9,
|
||||
"after_end_hour": "значения часа 9",
|
||||
"spawn_speedup_after_end": 0.0,
|
||||
"spawn_speedup_period_hours": 2,
|
||||
"type_availability": "час 1 — text; час 2 — text и sound; час 3 — +photo; час 4+ — все 4 (D23); атаки — с часа 2 (attack_junk)",
|
||||
"timer_grace": "packet_timer: grace h1 1.5, h2 1.35, h3 1.2, h4+ 1.0 (Economy.TIMER_GRACE). request_timer: отдельный grace h1 2.0, h2 1.7, h3 1.4, h4+ 1.0 (Economy.REQUEST_TIMER_GRACE). Формула: (base − 0.5*(h−1))*grace + 4*level. h2: packet 19.5*1.35=26.325, request 29.5*1.7=50.15."
|
||||
"type_availability": "веса типов — hours[].type_weights (единственный источник правды для приёма) и совпадают с hours[].request_type_weights (запросы); H1 только text, H2 text/sound, photo с H4, video с H6",
|
||||
"timer_grace": "v2: таймеры не масштабируются по часу (packet 20, request 30+7*level, cap 65)"
|
||||
},
|
||||
"progression": {
|
||||
"unlocks": {
|
||||
"shop": {"run_hour": 2, "label": "Магазин"},
|
||||
"search": {"meta": "survive_hour_3", "label": "Поиск"},
|
||||
"shield": {"meta": "survive_hour_5", "label": "Щит"},
|
||||
"minigame_text": {"run_hour": 1},
|
||||
"minigame_sound": {"run_hour": 2},
|
||||
"minigame_photo": {"run_hour": 3},
|
||||
"minigame_video": {"run_hour": 4},
|
||||
"minigame_advanced": {"meta": "complete_run", "label": "Продвинутые мини-игры"},
|
||||
"attack_junk": {"run_hour": 2},
|
||||
"attack_ddos": {"run_hour": 2},
|
||||
"attack_phishing": {"run_hour": 3},
|
||||
"attack_ransomware": {"run_hour": 4}
|
||||
"shop": { "run_hour": 2, "label": "Магазин" },
|
||||
"search": { "meta": "survive_hour_3", "label": "Поиск" },
|
||||
"shield": { "meta": "survive_hour_5", "label": "Щит" },
|
||||
"minigame_text": { "run_hour": 1 },
|
||||
"minigame_sound": { "run_hour": 2 },
|
||||
"minigame_photo": { "run_hour": 3 },
|
||||
"minigame_video": { "run_hour": 4 },
|
||||
"attack_junk": { "run_hour": 2 },
|
||||
"attack_ddos": { "run_hour": 2 },
|
||||
"attack_phishing": { "run_hour": 3 },
|
||||
"attack_ransomware": { "run_hour": 4 }
|
||||
},
|
||||
"meta_rewards": {
|
||||
"survive_hour_3": {"min_hours": 3, "unlocks": ["search"]},
|
||||
"survive_hour_5": {"min_hours": 5, "unlocks": ["shield"]},
|
||||
"complete_run": {"min_hours": 9, "unlocks": ["minigame_advanced"]}
|
||||
"survive_hour_3": { "min_hours": 3, "unlocks": ["search"] },
|
||||
"survive_hour_5": { "min_hours": 5, "unlocks": ["shield"] },
|
||||
"complete_run": { "min_hours": 9, "unlocks": ["minigame_advanced"] }
|
||||
}
|
||||
},
|
||||
"timers": {
|
||||
@@ -127,34 +657,20 @@
|
||||
"minigame_timeout": null,
|
||||
"delete_long_press_sec": 0.5
|
||||
},
|
||||
"sla": {
|
||||
"recv_bonus": 0.5,
|
||||
"send_bonus": 1.0,
|
||||
"reflect_bonus": 1.0,
|
||||
"timeout_fine_base": 10,
|
||||
"timeout_fine_per_hour": 2,
|
||||
"timeout_fine_formula": "-(10 + 2*hour) — таймаут и подобные штрафы (D12)",
|
||||
"wrong_attach_fine": -2,
|
||||
"lost_file_fine": -5,
|
||||
"buffer_overflow_fine": -5,
|
||||
"sla_lose": "SLA <= 0"
|
||||
},
|
||||
"payout": {
|
||||
"base_pay": "см. types.*.base_pay (значения в types)",
|
||||
"recv_fee": "см. types.*.recv_fee (значения в types)",
|
||||
"speed_formula": "speed = 0.6 + 0.4*(time_left / timer_total)",
|
||||
"quality_formula": "quality = 0.5 + 0.5*mini_quality",
|
||||
"multi_formula": "multi = 1 + 0.1*(n - 1)",
|
||||
"protocol_mult": { "TCP": 1.15, "UDP": 1.0 },
|
||||
"streak_formula": "streak = 1 + 0.02*min(10, streak)",
|
||||
"income_target_by_hour": {
|
||||
"hours": [1, 2, 3, 5, 9],
|
||||
"approx": [500, 800, 1200, 1800, 2200],
|
||||
"note": "ожидаемо ±10%; часы 4,6,7,8 — интерполяция, уточнить"
|
||||
}
|
||||
"spawn": {
|
||||
"type_pool_by_hour": { "1": ["text", "sound"], "2": ["text", "sound", "photo"], "3+": ["text", "sound", "photo", "video"] },
|
||||
"malicious_ratio": 0.0,
|
||||
"required_count_by_hour": {
|
||||
"1": { "1": 0.7, "2": 0.3, "3": 0.0 },
|
||||
"2": { "1": 0.6, "2": 0.35, "3": 0.05 },
|
||||
"3+": { "1": 0.5, "2": 0.4, "3": 0.1 }
|
||||
},
|
||||
"required_size_distribution": { "1": 0.5, "2": 0.4, "3": 0.1 },
|
||||
"required_count_note": "legacy; v2 читает hours[].request_mix",
|
||||
"attack_schedule": "attacks.schedule (0/1/1/2/2/3/3/4/4), окно 60–240 с часа"
|
||||
},
|
||||
"connections": {
|
||||
"W_formula": "W = clamp(0.30 - 0.005*(hour-1) - 0.005*successes + 0.05*zone_upgrade_level, 0.18, 0.36)",
|
||||
"W_formula": "legacy (полоска соединения удалена в v2)",
|
||||
"W_base": 0.3,
|
||||
"W_hour_coef": -0.005,
|
||||
"W_success_coef": -0.005,
|
||||
@@ -169,103 +685,14 @@
|
||||
"CHANNEL_BG_PER_HOUR": 0,
|
||||
"SLOT_LOAD": 0,
|
||||
"BG_note": "фоновый шум и занятые слоты НЕ грузят канал; load = только активные передачи (loading/sending)",
|
||||
"CHANNEL_CAPACITY": 80,
|
||||
"capacity_per_upgrade": 25,
|
||||
"capacity_upgrade_max_level": 4,
|
||||
"formula": "load = sum(weight(active_transfers)); factor = max(1.0, load / CHANNEL_CAPACITY); actual_time = base_time * factor. idle load = 0"
|
||||
},
|
||||
"attacks": {
|
||||
"junk": {
|
||||
"name_ru": "Мусорный пакет",
|
||||
"spawn_probability": null,
|
||||
"duration_sec": 15,
|
||||
"tell": "серая иконка ?, размер 0 Б, нет таймера",
|
||||
"effect": "авто-занос через duration_sec, занимает место в приёме; уничтожить можно как malicious в любой фазе (D02)",
|
||||
"reward_money": 5,
|
||||
"reward_sla": 1,
|
||||
"fail_money": -10,
|
||||
"fail_sla": -5,
|
||||
"filter_disabled_sec": 10
|
||||
},
|
||||
"ddos": {
|
||||
"name_ru": "DDoS",
|
||||
"cards_min": 8,
|
||||
"cards_max": 12,
|
||||
"spawn_window_sec": 2.0,
|
||||
"shield_hold_sec": 1.5,
|
||||
"tell": "8–12 идентичных карточек без типа и таймера",
|
||||
"effect": "волна карточек; отбивается удержанием щита",
|
||||
"reward_money": 30,
|
||||
"reward_sla": 2,
|
||||
"fail_sla": -15,
|
||||
"channel_debuff": 0.4,
|
||||
"channel_debuff_sec": 15,
|
||||
"packets_lost": 2,
|
||||
"recv_disabled_sec": 5
|
||||
},
|
||||
"phishing": {
|
||||
"name_ru": "Фишинг",
|
||||
"reward_money": 20,
|
||||
"reward_sla": 2,
|
||||
"fail_money": -40,
|
||||
"fail_sla": -10,
|
||||
"file_lock_sec": 60,
|
||||
"send_disabled_sec": 5,
|
||||
"timeout_money": -20,
|
||||
"timeout_sla": -5,
|
||||
"timeout_lock_sec": 30,
|
||||
"tell": "client_id формата id_XXXX с неверным символом (например id_7F3К — кириллическая К), серый аватар",
|
||||
"effect": "фальшивый запрос/пакет"
|
||||
},
|
||||
"ransomware": {
|
||||
"name_ru": "Шифровальщик",
|
||||
"reward_money": 25,
|
||||
"reward_sla": 2,
|
||||
"folder_block_delay_sec": 3,
|
||||
"auto_unlock_sec": 30,
|
||||
"auto_unlock_money": -30,
|
||||
"auto_unlock_sla": -10,
|
||||
"tell": "расширение .exe, красный квадрат 4×4",
|
||||
"effect": "файл становится locked (lock_until), кликабелен для уничтожения (D08)"
|
||||
}
|
||||
},
|
||||
"shop": [
|
||||
{ "id": "shop_slot3", "name_ru": "Третий слот", "effect": "connection_slots", "value": 1, "price": 250, "max_level": 1 },
|
||||
{ "id": "shop_slot4", "name_ru": "Четвёртый слот", "effect": "connection_slots", "value": 1, "price": 450, "max_level": 1 },
|
||||
{ "id": "shop_buffer", "name_ru": "Буфер +2", "effect": "buffer_slots", "value": 2, "price": 200, "max_level": 2 },
|
||||
{ "id": "shop_timer", "name_ru": "Таймер пакетов +4 с", "effect": "packet_timer", "value": 4, "unit": "сек (аддитивно)", "price": 350, "max_level": 3 },
|
||||
{ "id": "shop_zone", "name_ru": "Зона +5%", "effect": "zone_width", "value": 0.05, "unit": "доля W (аддитивно в формуле D04)", "price": 300, "max_level": 3 },
|
||||
{ "id": "shop_channel", "name_ru": "Канал +25", "effect": "channel_capacity", "value": 25, "price": 250, "max_level": 4 },
|
||||
{ "id": "shop_section", "name_ru": "Новая секция", "effect": "sections", "value": 1, "price": 300, "max_level": 5 },
|
||||
{ "id": "shop_search", "name_ru": "Поиск", "effect": "search_highlight", "value": 1, "price": 250, "max_level": 1 },
|
||||
{ "id": "shop_shield", "name_ru": "Щит", "effect": "shield", "value": 1, "price": 400, "max_level": 1 },
|
||||
{ "id": "shop_sla", "name_ru": "SLA +24", "effect": "sla_add", "value": 24, "price": 300, "max_level": 3 }
|
||||
],
|
||||
"shop_reserve": {
|
||||
"items": ["scanner", "autosort"],
|
||||
"note": "резерв — в прототип НЕ входят (D13); позиций в shop НЕТ"
|
||||
},
|
||||
"spawn": {
|
||||
"type_pool_by_hour": {
|
||||
"1": ["text"],
|
||||
"2": ["text", "sound"],
|
||||
"3+": ["text", "sound", "photo", "video"]
|
||||
},
|
||||
"malicious_ratio": 0.0,
|
||||
"required_count_by_hour": {
|
||||
"1": { "1": 1.0 },
|
||||
"2": { "1": 0.75, "2": 0.25 },
|
||||
"3+": { "1": 0.6, "2": 0.3, "3": 0.1 }
|
||||
},
|
||||
"required_size_distribution": { "1": 0.6, "2": 0.3, "3": 0.1 },
|
||||
"required_count_note": "required_count_by_hour — распределение по часу; required_size_distribution — fallback/час 3+",
|
||||
"attack_schedule": "с часа 2, далее по difficulty.attacks, окно 60–240 с часа, не во время мини-игры"
|
||||
"CHANNEL_CAPACITY": 10,
|
||||
"capacity_per_upgrade": 6,
|
||||
"capacity_upgrade_max_level": 6,
|
||||
"formula": "load = sum(weight(active_transfers)); factor = max(1.0, load / capacity); actual_time = base_time_sec * pow(0.90, speed_level) * factor"
|
||||
},
|
||||
"minigame_selection": {
|
||||
"coeff": { "text": 1.0, "sound": 2.0, "photo": 3.0, "video": 4.0 },
|
||||
"difficulty_formula": "sum(coeff(required)) / n",
|
||||
"thresholds": { "text": "< 1.5", "sound": "< 2.5", "photo": "< 3.5", "else": "video" },
|
||||
"call": "MiniGame.start(type, difficulty)",
|
||||
"coeff": { "text": 1, "sound": 2, "photo": 4, "video": 8 },
|
||||
"formula": "см. minigames.selection.formula (27 достижимых сумм для 1-4 файлов, 3 ступени, rank_games)",
|
||||
"quality_stub": 1.0
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,10 @@
|
||||
"retry_no_effect": "Качество прежнее — улучшить нельзя",
|
||||
"delete_prep_only": "Обычные файлы удаляются только в подготовке",
|
||||
"money_short": "Не хватает средств",
|
||||
"folder_need_money": "НУЖНО {cost} $",
|
||||
"folder_limit": "ДОСТИГНУТ ЛИМИТ ПАПОК",
|
||||
"delete_file_confirm": "Удалить файл?",
|
||||
"delete_file_detail": "Файл «{name}» будет уничтожен без штрафа SLA.",
|
||||
"save_restored": "Прогресс смена восстановлен"
|
||||
},
|
||||
"shop": {
|
||||
@@ -87,7 +91,75 @@
|
||||
"shop_channel": "Ёмкость канала +25",
|
||||
"shop_section": "Новая секция хранения",
|
||||
"shop_search": "Поиск нужных файлов",
|
||||
"shop_shield": "Щит (отражения атак)"
|
||||
"shop_shield": "Щит (отражения атак)",
|
||||
"title_offers": "МАГАЗИН · 3 ПРЕДЛОЖЕНИЯ",
|
||||
"subtitle": "Новые предложения после каждого часа",
|
||||
"reroll": "ОБНОВИТЬ АССОРТИМЕНТ · %d $",
|
||||
"reroll_limit": "ЛИМИТ ОБНОВЛЕНИЙ",
|
||||
"level": "УРОВЕНЬ %d/%d",
|
||||
"now": "СЕЙЧАС",
|
||||
"after": "ПОСЛЕ",
|
||||
"not_enough": "НЕ ХВАТАЕТ %d $",
|
||||
"maxed": "МАКСИМУМ",
|
||||
"all_bought": "ВСЁ КУПЛЕНО",
|
||||
"lock": "ЗАКРЕПИТЬ",
|
||||
"unlock": "ОТКРЕПИТЬ",
|
||||
"lot_slots": "Порты коммутатора",
|
||||
"lot_speed": "Оптоволокно",
|
||||
"lot_sla": "Резервный ИБП",
|
||||
"lot_shield": "Заземление",
|
||||
"lot_money": "Корпоративный контракт",
|
||||
"lot_folder": "Складской стеллаж",
|
||||
"lot_channel": "Пропускная способность",
|
||||
"lot_timer": "Регламент обработки"
|
||||
},
|
||||
"sort": {
|
||||
"title": "СТОЛ СОРТИРОВКИ",
|
||||
"hint": "Перетащите файлы, чтобы разложить по папкам",
|
||||
"full": "СТОЛ ЗАПОЛНЕН",
|
||||
"return_blocked": "На столе остались файлы, места нет",
|
||||
"empty": "Здесь можно разложить файлы после часа пика"
|
||||
},
|
||||
"grid": {
|
||||
"cells": "%d / %d клеток",
|
||||
"free": "свободно %d клеток",
|
||||
"busy": "занято %d из %d · свободно %d",
|
||||
"empty_folder": "ПУСТО · перетащите файл или пакет",
|
||||
"pack": "УПАКОВАТЬ"
|
||||
},
|
||||
"packet": {
|
||||
"shape": "ЗАЙМЁТ %d КЛЕТОК",
|
||||
"no_space": "НЕТ МЕСТА В ПАПКЕ"
|
||||
},
|
||||
"request": {
|
||||
"minigame": "МИНИ-ИГРА: %s · T%d",
|
||||
"difficulty": "СЛОЖНОСТЬ: %d",
|
||||
"waiting_slot": "ОЖИДАНИЕ СЛОТА"
|
||||
},
|
||||
"minigame": {
|
||||
"quality": "КАЧЕСТВО %d%%",
|
||||
"surrender": "СДАТЬСЯ",
|
||||
"skip": "ПРОПУСТИТЬ",
|
||||
"no_timer": "БЕЗ ТАЙМЕРА",
|
||||
"continue": "ПРОДОЛЖИТЬ",
|
||||
"clean": "ЧИСТО",
|
||||
"normal": "НОРМАЛЬНО",
|
||||
"weak": "СЛАБО",
|
||||
"dots": "Одна линия",
|
||||
"sound": "Уровни",
|
||||
"text": "Чужое слово",
|
||||
"video": "Раскадровка",
|
||||
"photo": "Кадр",
|
||||
"maze": "Лабиринт",
|
||||
"junk": "Фильтр",
|
||||
"ddos": "Щит",
|
||||
"phishing": "Паспорт",
|
||||
"ransomware": "Карантин"
|
||||
},
|
||||
"hour": {
|
||||
"next_offers": "3 НОВЫХ ЛОТА В МАГАЗИНЕ",
|
||||
"sla_regen": "SLA восстановлен: +%d",
|
||||
"all_slots_busy": "ВСЕ КАНАЛЫ ЗАНЯТЫ"
|
||||
},
|
||||
"attack": {
|
||||
"junk": {
|
||||
|
||||