diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 9867c1a..8572125 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -20,6 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Rust run: | curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target wasm32-unknown-unknown @@ -28,8 +30,20 @@ jobs: # падало по таймауту, а сохранение вешало «Complete job» на пару минут. Инструмент собирается заново. - name: Форк Horizon run: nice -n 19 python3 tools/airdef.py fork - # Чистая симуляция тестируется нативно (хост-таргет раннера, без WASM): регрессии из sim/regress.rs - # и сценарии tests/scenarios.rs (NaN, налёт кончается, ТЗМ не зависают). Нужен форк: SDK подключён путём. + - name: Проверка закреплённого checkout + run: python3 -m unittest discover -s tools -p test_engine_checkout.py + - name: Проверка generated assets + run: | + python3 -m pip install --user -r tools/requirements.txt + python3 tools/gen_catalog.py --check + python3 -m unittest discover -s tools -p test_sound_catalog.py + python3 tools/gen_models.py --check + python3 -m unittest discover -s tools -p test_model_bounds.py + - name: Тесты ядра без SDK + working-directory: airdef-core + run: | + HOST=$(rustc -vV | sed -n 's/^host: //p') + nice -n 19 cargo test --target "$HOST" - name: Тесты мода working-directory: airdef-mod run: | @@ -46,7 +60,9 @@ jobs: SHA: ${{ github.sha }} run: | set -e - mkdir -p out && cp airdef-mod/target/horizon-mod/airdef.hmod out/airdef.hmod + mkdir -p out + cp airdef-mod/target/horizon-mod/airdef.hmod out/airdef.hmod + cp airdef-mod/target/horizon-mod/build-identity.json out/build-identity.json BODY=$(printf 'Сборка %s (%s).\n\n- airdef.hmod — мод, для всех ОС.\n\nИгра-форк Horizon (нужна для мода): собрать у себя — python3 tools/airdef.py all, или скачать из большого релиза v* (Windows), или запустить вручную workflow game (Linux) / desktop (macOS) с этим тегом.\nКак запускать: docs/INSTALL.md.' "$TAG" "$SHA") DATA=$(BODY="$BODY" python3 -c 'import json,os;print(json.dumps({"tag_name":os.environ["TAG"],"target_commitish":os.environ["SHA"],"name":os.environ["TAG"],"body":os.environ["BODY"]}))') if [ -z "$TOKEN" ]; then echo "::error::нет токена: добавь секрет RELEASE_TOKEN"; exit 1; fi @@ -57,3 +73,4 @@ jobs: fi ID=$(python3 -c 'import json;print(json.load(open("release.json"))["id"])') curl -sf -X POST -H "Authorization: token $TOKEN" -F "attachment=@out/airdef.hmod" "$API/releases/$ID/assets?name=airdef.hmod" > /dev/null + curl -sf -X POST -H "Authorization: token $TOKEN" -F "attachment=@out/build-identity.json" "$API/releases/$ID/assets?name=build-identity.json" > /dev/null diff --git a/.gitea/workflows/desktop.yml b/.gitea/workflows/desktop.yml index 6b7e98c..4662d0c 100644 --- a/.gitea/workflows/desktop.yml +++ b/.gitea/workflows/desktop.yml @@ -41,6 +41,7 @@ jobs: PY - uses: actions/checkout@v4 with: + submodules: recursive ref: refs/tags/${{ inputs.tag }} - name: Проверка коммита env: @@ -69,6 +70,10 @@ jobs: D=airdef-macos-arm64 mkdir -p "$D" cp horizon/target/fast/horizon-client horizon/target/fast/horizon-ctl out/airdef.hmod dist/play.sh docs/INSTALL.md "$D/" + cp airdef-mod/target/horizon-mod/build-identity.json "$D/" + cp -R docs/licenses "$D/" + cp horizon/THIRD_PARTY_NOTICES.md "$D/licenses/HORIZON-THIRD-PARTY-NOTICES.md" + cp horizon/crates/horizon-client/src/acoustics/THIRD_PARTY_SPR.md "$D/licenses/THIRD_PARTY_SPR.md" tar czf "out/$D.tar.gz" "$D" - name: В релиз env: diff --git a/.gitea/workflows/game.yml b/.gitea/workflows/game.yml index 6505179..1f991a3 100644 --- a/.gitea/workflows/game.yml +++ b/.gitea/workflows/game.yml @@ -49,6 +49,7 @@ jobs: PY - uses: actions/checkout@v4 with: + submodules: recursive ref: refs/tags/${{ inputs.tag }} - name: Проверка коммита env: @@ -82,6 +83,10 @@ jobs: D=airdef-linux-x86_64 mkdir -p "$D" out cp horizon/target/fast/horizon-client horizon/target/fast/horizon-ctl airdef-mod/target/horizon-mod/airdef.hmod dist/play.sh docs/INSTALL.md "$D/" + cp airdef-mod/target/horizon-mod/build-identity.json "$D/" + cp -R docs/licenses "$D/" + cp horizon/THIRD_PARTY_NOTICES.md "$D/licenses/HORIZON-THIRD-PARTY-NOTICES.md" + cp horizon/crates/horizon-client/src/acoustics/THIRD_PARTY_SPR.md "$D/licenses/THIRD_PARTY_SPR.md" tar czf "out/$D.tar.gz" "$D" # RELEASE_ID взят в шаге «Проверка релиза» (релиз build-N, созданный build.yml) curl -sf -X POST -H "Authorization: token $TOKEN" -F "attachment=@out/$D.tar.gz" "$API/releases/$RELEASE_ID/assets?name=$D.tar.gz" > /dev/null diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index ec97ad7..5d2abf6 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -37,6 +37,7 @@ jobs: if [ "$FREE" -lt 12 ]; then echo "::error::на сервере свободно ${FREE} ГБ, нужно хотя бы 12"; exit 1; fi - uses: actions/checkout@v4 with: + submodules: recursive ref: refs/tags/${{ inputs.tag || github.ref_name }} - name: Инструменты run: | @@ -74,6 +75,10 @@ jobs: T=horizon/target/x86_64-pc-windows-gnu/fast mkdir -p "$D" out cp "$T/horizon-client.exe" "$T/horizon-ctl.exe" airdef-mod/target/horizon-mod/airdef.hmod dist/play.bat docs/INSTALL.md "$D/" + cp airdef-mod/target/horizon-mod/build-identity.json "$D/" + cp -R docs/licenses "$D/" + cp horizon/THIRD_PARTY_NOTICES.md "$D/licenses/HORIZON-THIRD-PARTY-NOTICES.md" + cp horizon/crates/horizon-client/src/acoustics/THIRD_PARTY_SPR.md "$D/licenses/THIRD_PARTY_SPR.md" zip -qr "out/$D.zip" "$D" cp airdef-mod/target/horizon-mod/airdef.hmod out/ - name: Релиз diff --git a/.gitea/workflows/wasm-budget.yml b/.gitea/workflows/wasm-budget.yml new file mode 100644 index 0000000..7024340 --- /dev/null +++ b/.gitea/workflows/wasm-budget.yml @@ -0,0 +1,27 @@ +# Opt-in heavy sandbox runtime tier; separate from the routine WASM contract. +name: wasm-budget +on: + workflow_dispatch: +env: + CARGO_BUILD_JOBS: "1" + CARGO_INCREMENTAL: "0" +jobs: + budget: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Rust + run: | + curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target wasm32-unknown-unknown + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: Actual WASM budget scenario + run: nice -n 19 python3 tools/airdef.py test-budgets + - name: Preserve machine-readable provenance + if: always() + uses: actions/upload-artifact@v4 + with: + name: wasm-budget-provenance + path: .horizon-cache/wasm-budget-report.json + if-no-files-found: warn diff --git a/.gitea/workflows/wasm-contract.yml b/.gitea/workflows/wasm-contract.yml new file mode 100644 index 0000000..7d233cf --- /dev/null +++ b/.gitea/workflows/wasm-contract.yml @@ -0,0 +1,20 @@ +# Heavy sandbox runtime is opt-in on the shared 2-core runner. +name: wasm-contract +on: + workflow_dispatch: +env: + CARGO_BUILD_JOBS: "1" + CARGO_INCREMENTAL: "0" +jobs: + contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Rust + run: | + curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target wasm32-unknown-unknown + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: Actual WASM contract + run: nice -n 19 python3 tools/airdef.py test-wasm diff --git a/.gitignore b/.gitignore index c022fc4..0322357 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -/horizon/ run-home/ .horizon-cache/ airdef-mod/target/ +airdef-core/target/ __pycache__/ /out/ .DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..83d7a73 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "horizon"] + path = horizon + url = https://gitea.maoniao.team/chexit/horizon-airdef.git diff --git a/HORIZON_COMMIT b/HORIZON_COMMIT deleted file mode 100644 index 31aff13..0000000 --- a/HORIZON_COMMIT +++ /dev/null @@ -1 +0,0 @@ -b6c936f82b08798057161bdfa577e3aac53fb91e diff --git a/README.md b/README.md index 17aafab..81da661 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ # AirDef — дальнобойные дроны и ПВО для Horizon -Мод для Horizon (Minecraft 26.3 на Rust): Шахед-136, Герань-3, Patriot и Гепард с 3D-моделями, -наведением ракет, слежением башен, падением сбитых дронов и эффектами взрывов. +Клиентский sandbox-мод для Horizon (Minecraft 26.3 на Rust): налёты дронов и ракет, +эшелонированная ПВО, раздельные батареи, наземная логистика, разведка, РЭБ, повреждения, +повторы и экспорт видео. Характеристики и временные масштабы игровые; это не тренажёр реальных комплексов. -Работает на **своём форке Horizon** (`horizon/`, ветка `airdef`): в нём добавлен API отрисовки +Работает на **[своём форке Horizon](https://gitea.maoniao.team/chexit/horizon-airdef)** +(`horizon/` — сабмодуль, закреплённый SHA): в нём добавлен API отрисовки 3D-моделей, которого нет в основном Horizon. +Рабочая ветка — **`kimi-work`**. Подробный проверенный объём и оставшаяся приёмка: +[состояние задач](docs/IMPLEMENTATION-STATUS.md). Для передачи разработки: +[технический журнал и handoff](docs/CODEX-HANDOFF.md). Открытый issue не означает отсутствие +реализации: по просьбе владельца агенты оставляют комментарии, но не закрывают задачи. + ![Перехваты](screenshots/intercepts.jpg) ## Запуск @@ -16,10 +23,21 @@ Коротко, из исходников: ```bash -python3 tools/airdef.py all # скачать Horizon, наложить патчи форка, собрать игру и мод, установить мод +git switch kimi-work +git pull --ff-only +git submodule update --init --recursive # также выполняется командой all +python3 tools/airdef.py all # получить закреплённый движок, собрать игру и мод, установить мод python3 tools/airdef.py play # запустить (аргументы игры — после: --world "Имя мира") ``` +`all` синхронизирует URL сабмодуля из `.gitmodules`, включая обновление со старого upstream. +Продолжение 4 ноября: [модели, границы ядра, общий каталог, API29 и массовая акустика](docs/CONTINUATION-2026-11-04.md). + +Диагностика в игре: `/airdef capabilities` показывает доступность API, принятие запроса и +подготовку дыма, акустики и захвата именно этого мода. Подготовка не означает готовый GPU-кадр или MP4. +Для разработчика: `python3 tools/airdef.py test-wasm` — лёгкий ABI-тест; +`python3 tools/airdef.py test-budgets` — отдельный тяжёлый сценарий с JSON-отчётом. + Для разработки: `./install.sh` перегенерирует модели (`tools/gen_models.py`, нужен Pillow) и звуки (`tools/gen_sounds.py`, нужны numpy и ffmpeg), собирает и ставит мод. Сгенерированные файлы лежат в репозитории, так что для обычной сборки нужен только Rust. @@ -52,10 +70,10 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает то волнами по типам), дальность старта, цели и **ПУСК**. Цели: - *только объекты* — всё летит по отмеченным объектам (или по игроку, если их нет); - *объекты и ПВО* — треть ударных средств бьёт по случайным комплексам; -- *умный противник* — планировщик знает, где стоят комплексы и что видят их РЛС: ведёт дроны и крылатые ракеты +- *умный противник* — планировщик использует наблюдённые контакты и последние известные позиции комплексов: ведёт дроны и крылатые ракеты маршрутами в обход зон поражения, низко, за рельефом и через мёртвые зоны; «Герберы» прилетают на 15 с раньше, чтобы оттянуть ракеты; «Ланцеты» идут по Гепардам и Панцирям, Х-22/Циркон и часть баллистики — по дальнобойным - комплексам; на каждую РЛС дальнего обнаружения идут 2–3 быстрых ударника (Х-22, Циркон, Искандер, Кинжал, затем Х-101, + комплексам; на наблюдённые РЛС дальнего обнаружения выделяются 2–3 быстрых ударника (Х-22, Циркон, Искандер, Кинжал, затем Х-101, Герань-3, Ланцет, Шахед; не больше четверти налёта); всё остальное стартует со сдвигом так, чтобы прилететь одновременно. После налёта приходит отчёт: сколько сбито и прорвалось, ракет ПВО потрачено, комплексов потеряно, урон объектам. @@ -72,16 +90,39 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает то **Сохранение.** Комплексы, объекты, их повреждения и настройки сохраняются отдельно для каждого мира (и для каждого сервера); уничтоженный комплекс остаётся остовом и после перезахода, пока его не отремонтируют. +Налёт в воздухе тоже сохраняется: дроны, очередь их запуска и незавершённые задания на маршрут +восстанавливаются, и перезаход не обрывает перенацеливание. Ракеты-перехватчики в воздухе и площадки +запуска не сохраняются; сценарий — стартовое состояние и налёт не несёт. -**Сеть ПВО.** Каждая единица делится целями со всеми в радиусе 2,25 км (линия связи): РЛС дальнего обнаружения -даёт раннее предупреждение всем, пусковая без своей РЛС (P1-SUN) стреляет по чужим данным. Пушкам (Гепард, +**Сеть ПВО.** Единицы обмениваются измеренными треками в пределах игровой связи; повреждение связи +сокращает её дальность. Можно поставить несколько С-400/С-500 без собственной РЛС и общие радары +поиска, сопровождения и мачтового обзора: N → «Разместить элементы батареи…». +Поисковый радар даёт подсказку; огневой должен сам измерить цель, прежде чем разрешить пуск. +Старая сборная конфигурация сохранена. [Команды, источники и ограничения](docs/BATTERIES-AND-COLD-LAUNCH.md). +P1-SUN также использует чужие данные. Пушкам (Гепард, пушки Панциря) нужна своя цель: они стреляют только по тому, что видят сами. Уничтоженная РЛС — дыра в сети. +**Радиомолчание.** `/airdef radar <номер> off|on` или кнопка в карточке техники на карте +выключает/включает собственный радар. Приём разрешённых внешних треков сохраняется; чужой трек +не заменяет обязательное собственное сопровождение. Выключение не стирает старые сведения противника: +он продолжает использовать последнюю наблюдённую позицию, а пуски и РЭБ могут снова раскрыть технику. + **Логистика.** Пока нет ни одного склада, магазины пополняются сами, как раньше. Как только поставлен склад, боекомплект считается: комплекс без ракет пишет «ЖДЁТ БК»; в 60 м от склада он перезаряжается прямо с него, а дальние обслуживают **ТЗМ**: заправляются на складе (2 перезарядки), сами едут к ждущим комплексам, ближайшим первым, и возвращаются. Запас склада — 30 перезарядок, он виден в сводке. Склад и ТЗМ детонируют всем грузом. +Ударная **RM48U у С-400** — отдельный условный игровой боеприпас, который делит четыре ячейки +с ракетами ПВО. `/airdef ground-stock <склад> <количество>` задаёт конечный начальный запас +sandbox-сценария, `/airdef ground-load <С-400>` загружает одну ракету рядом со складом либо +создаёт заявку на доставку ТЗМ. Груз едет физически; снятая ракета ПВО возвращается на склад. +Заявка и груз сохраняются в пути. `/airdef ground-unload <С-400>` выгружает ракету у склада, +а вдали создаёт заявку на обратный вывоз ТЗМ. Одна ракета резервируется от пуска до забора; +`/airdef ground-cancel <С-400>` отменяет ещё ожидающую доставку или вывоз. +После забора ракета находится в машине и едет на склад. Отмена заявки не возвращает уже вывезенный груз в ПУ. +Без склада этот ударный боеприпас сам не появляется. Бук и остальные комплексы такого профиля пока не имеют. +Порядок подготовки, разведка и номера двух списков: [как ударить по ПУ](docs/GROUND-STRIKES.md). + **Перемещение.** **Пульт перемещения**: навестись на технику (выбрать), затем на точку на земле — она сворачивается (С-400 и другие с вертикальным пуском дольше, Гепард и Панцирь быстро), едет по рельефу и разворачивается; всё это время не стреляет и не светит РЛС. Или `/airdef move <номер>` — к тому месту, где ты стоишь. @@ -95,9 +136,12 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает то **Умный противник учится.** Он запоминает, откуда его налёты сбивали, а откуда они прорывались, и следующий налёт ведёт с лучшего направления; помнит комплексы, выдавшие себя пуском, и станции РЭБ, и во время налёта перенацеливает ещё не взлетевшие Ланцеты и Шахеды на них; если цель-комплекс уничтожен, дроны идут к -следующему известному. Дроны под обстрелом прижимаются к земле. +следующему известному. Маршруты к новым целям достраиваются по бюджету планировщика в следующих тиках: +необработанное задание не теряется, старый безопасный участок остаётся до готовности нового, а дрон без +маршрута летит прямо только до ближайшего бюджета. Дроны под обстрелом прижимаются к земле. -**Ночь.** Ночью пушки попадают втрое хуже, если цель не освещена. Освещают **прожекторы** (луч ведёт ближайшую +**Ночь.** Исправные пушки с собственным свежим радарным сопровождением не теряют точность из-за темноты. +Цели освещают **прожекторы** (луч ведёт ближайшую цель из сети, приоритет — цели пушек) и **осветительные ракеты**: пушечные комплексы пускают их сами (раз в 25 с на цель в темноте), игрок — **осветительным пистолетом** или `/airdef flare`. Ракета висит ~30 с и освещает всё вокруг на ~60–90 м. @@ -110,6 +154,10 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает то тонмаппинг (ванильный диапазон не трогается, сжимается только то, что ярче). Источники передаются движку новым API мода `render.set-lights` (до 512, плавно интерполируются), режим — `render.set-lighting`. +В HD дымовые следы ракет немного уплотнены; число сегментов и время появления/выгорания сохранены. +Это относится к ракетам ПВО, RM48U, крылатым, сверхзвуковым и баллистическим средствам; +дым пожаров и слабая дымка винтовых дронов остались прежними. + **Сетка J** после сбития ещё 3,5 с держит камеру на сбитой цели, и комплекс в это время не пропадает из сетки. **Повреждения.** Взрыв рядом с комплексом повреждает его: он хуже поворачивается, хуже видит и попадает, @@ -118,6 +166,11 @@ API мода `render.set-lights` (до 512, плавно интерполиру пострадать. Объекты (Целеуказатель или `/airdef target here`) имеют прочность, она видна в сводке. Ремонт — кнопкой в меню или `/airdef repair`. +Известные стены и рельеф ослабляют **непрямой** урон корпусу, узлам и объектам; проёмы дают +частичное воздействие. Используется геометрия, известная перед разрушением. При неизвестной или +устаревшей геометрии сохраняется прежний урон без защиты; поздний ответ его повторно не наносит. +Прямое вооружённое попадание этим ослаблением не отменяется. + **Монитор комплекса:** клавиша **K** листает комплексы по очереди, после последнего монитор скрывается. Слева вверху показано: - состояние комплекса (поиск, сопровождение, доворот, захват, пуск, огонь, перезарядка, огонь запрещён); - его РЛС: цели, по которым он работает, красным, остальные серым, текущая цель жёлтым; @@ -157,12 +210,10 @@ API мода `render.set-lights` (до 512, плавно интерполиру - `/airdef import` — перенести в текущий мир комплексы и цели из старой версии мода (до 0.2.x они были общими для всех миров); - `/airdef raid` — налёт, настроенный в меню; `/airdef target here` — отметить объект под ногами; `/airdef repair` — ремонт; - `/airdef lastchance ` — «последний шанс»: комплекс стреляет и по классу вне ролей, если цель вот-вот попадёт (по умолчанию вкл; сохраняется); -- `/airdef sites` — известные пусковые площадки (ПУ) и ударные ракеты; `/airdef strike <номер ПУ>` — ответный удар по ПУ (#101, «удары по ПУ»). Как это играется: - - **Разведка без волшебства.** Площадку находит только работающая РЛС: либо пуск в её досягаемости (низкая цель: радиогоризонт и холмы решают) даёт точечное наблюдение, либо собственный трек вылетевшей цели «указывает назад» лучом в пару градусов, а время полёта с момента пуска (тревоги) на скорости цели даёт дальность (±8% и секунда времени пуска); у баллистической ракеты дуга точнее. Две РЛС, держащие одну цель, дают два наблюдения. Наблюдения сводятся методом наименьших квадратов, ответ всегда с кругом ошибки (90%), он сужается с каждым пуском. Если места не получилось (круг шире 800 блоков или есть только лучи вдоль одной линии), известен только **азимут** (на скопе метка «N?» на краю), удар невозможен. С местом: ромб на скопе (круг ошибки пунктиром) или метка на краю; в панели строка «Известных ПУ…». Далёкие площадки (километры) открываются грубо (сотни блоков) и бить их почти бесполезно, пока не накопятся пуски. - - **Удар.** Ударных ракет мало (3 в запасе, максимум 4, новая за 5 минут), дальность до 4500 блоков; стартуют оттуда, где вы стоите, летят 4 блока в тик (2 км за 25 с) в точку *оценки*. На подлёте ракету может сбить ПВО противника: у ПУ её нет (40%), лёгкая (35%, сбивает в 30%) или тяжёлая (25%, 65%), у баллистических батарей тяжёлая чаще; узнаёте только по итогу. Разброс ракеты 14 блоков; решает истинное расстояние до ПУ: до 35 блоков — **уничтожена** (запас сгорел и рвётся, отложенные запуски отменены, огонь и дымящийся остов, взрыв общим кодом), до 90 — **подавлена** на 30 с (пуски отложены, не отменены), дальше — **промах**, воронка рядом, ПУ цела: очередь за попадание в соседний грунт не списывается. Плохо известную ПУ (круг в сотни блоков) бить почти бесполезно. - - **Вне карты.** ПУ без земли (нет загруженной карты) тоже находится: азимут и дальность по трекам, но не рисуется. Удар, который попал бы, прекращает её пуски («результат не виден»); промах теряется. - - **Цена выбора.** Ракета запаса не вернётся, пока летит, налёт идёт: близкие площадки не остановить, далёкие (крылатые и баллистические за 2–4 км) успеваете, но для этого нужны РЛС с обзором в сторону налёта и риск промаха или ПВО противника. Отчёт налёта называет сорванные пуски: «Налёт сорван ударами по ПУ (№…): не взлетело N из заявленных M». - - Запись: удар попадает в журнал повтора (строка и взрыв/огонь на карте), сами ПУ в повторе не рисуются. Запас ракет входит в сохранение и сценарий (`strikes <запас> <тиков до следующей>`). Все числа механики в одной таблице `TUNE` в `src/sim/strike.rs`. +- `/airdef sites` — известные пусковые площадки (ПУ). РЛС с прямой видимостью может заметить сам пуск и получить неточную позицию; несколько наблюдённых пусков уточняют её. Обнаруженный позже дрон даёт только слабое предположение об обратном курсе: скрытые координаты площадки и время невидимого старта не используются. Повторные треки одного полёта не создают ложную точность. +- `/airdef strike <номер ПУ>` — ручной удар по разведанной площадке из готового физического С-400 с отдельно загруженной RM48U (игровой профиль, #118). Четыре ячейки делятся с боезапасом ПВО; приказ резервирует носитель и ячейку, открывает крышку и списывает одну ударную ракету при выбросе. Выбирается готовый комплекс с наибольшим остатком RM48U, затем ближайший к цели. Есть открытие ТПК, выброс, отложенное включение двигателя и разгон, ограниченная игровая траектория, столкновения и запись пуска. Несуществующее «ПВО площадки» не перехватывает ракету по броску вероятности. +- `/airdef ground-stock <номер склада> <количество>` — задать остаток RM48U для sandbox-сценария (по умолчанию 0). `/airdef ground-load <номер С-400>` загружает одну ракету у склада либо заказывает ТЗМ; `ground-unload` возвращает её у склада либо заказывает вывоз; `ground-cancel` отменяет ожидающую заявку. Доставка и обратный вывоз через ТЗМ реализованы, запас и груз сохраняются. [Пошаговый пример](docs/GROUND-STRIKES.md). Совместимость профиля основана на [публикации ГУР](https://gur.gov.ua/content/warsanctions-hur-mo-rozkryvaie-budovu-komponentnu-bazu-ta-pidpryiemstva-kooperatsii-z-vyrobnytstva-udarnykh-raket-rm48u.html); это сообщение ведомства, не независимая сертификация. Дальность, форма полёта и оформление условны для Minecraft. +- `/airdef deploy s400_launcher` и `s500_launcher` — отдельные пусковые без своей РЛС; `radar_search`, `radar_track`, `radar_height` — обзорная, сопровождающая и мачтовая игровые РЛС. Они также доступны через меню N → «Разместить элементы батареи…». `/airdef battery` показывает состав и связь; `/airdef battery <номер> integrated|launcher` меняет конфигурацию, `/airdef radar <номер> multi|search|engagement|height` — роль отдельной РЛС. Обзорная РЛС передаёт подсказки; станция сопровождения должна сама получить свежее измерение, чтобы разрешить пуск. Старые С-400/С-500 сохраняют упрощённую сборную конфигурацию. [Устройство игровой батареи и пуска](docs/BATTERIES-AND-COLD-LAUNCH.md). - `/airdef roles` — показать, по каким классам целей работает каждый комплекс; `/airdef roles <классы|default>` — задать (классы через запятую: `drone`, `jet`, `cruise`, `supersonic`, `ballistic` или `all`), например `/airdef roles s400 cruise,ballistic`; - `/airdef doctrine` — доктрины комплексов (#47; в меню N: «Доктрины…»): `/airdef doctrine <номер|all> reserve ` — держать K ракет для срочных целей (баллистика или до удара меньше 12 с); `salvo eco|normal|heavy` — сколько перехватчиков на цель (только базовые / как обычно / на один больше); `sector <центр°> <полуширина°>` или `sector off` — сектор ответственности (0° — север); `guard <объект№>[,<№>]` или `guard off` — стрелять только по тому, что идёт на охраняемые объекты (отметка: `/airdef target here`). По умолчанию ограничений нет; последний шанс сильнее доктрины; `/airdef why <номер>` называет, какая настройка остановила или разрешила пуск; сохраняется с миром и сценариями. Карта охраны и пробелов в ней — вместе с картой (#48). - `/airdef deploy ` — поставить комплекс в 8 блоках перед собой; @@ -205,7 +256,7 @@ API мода `render.set-lights` (до 512, плавно интерполиру | Гепард | 2 × 35 мм, башня и стволы со слежением и упреждением, 160 очередей | 135 (слежение с 195) | | Панцирь-С1 | 12 × 57Э6 и 2 × 30 мм; модуль поворачивается, пакеты и стволы поднимаются | ракеты 25–225, пушки 90 | | С-400 | 4 × 48Н6, вертикальный холодный старт, РЛС обзора на прицепе | 30–720 | -| С-500 | 4 × 77Н6, холодный старт, самые быстрые ракеты | 40–1050 | +| С-500 | 2 × 77Н6, холодный старт, самые быстрые ракеты | 40–1050 | | IRIS-T SLM | 8 ракет, вертикальный пуск, быстрые и манёвренные | 15–600 | | NASAMS | 6 × AMRAAM, наводимая пусковая на прицепе | 10–450 | | Бук-М2 | 4 × 9М317 на направляющих, башня с РЛС подсвета | 20–675 | @@ -219,7 +270,16 @@ API мода `render.set-lights` (до 512, плавно интерполиру **Согласование огня.** Комплексы делят цели между собой: на обычную цель идёт одна ракета, на баллистическую две; чем ближе цель к своей точке попадания, тем больше перехватчиков разрешено на неё сразу (за ~12 с до удара ещё один, за ~5 с — ещё один). Комплекс, у которого кончились ракеты или идёт перезарядка, цель не держит; тот, кто не смог навестись за ~2 с, отпускает её другим. Пушки расходятся по разным целям. Первыми бьют по самым опасным (быстрым и тяжёлым) целям, при этом ложные -«Герберы» выглядят для них как настоящие. Шанс поражения падает, если цель быстрее перехватчика. +«Герберы» выглядят для них как настоящие. Геометрия встречи проверяется по движению обоих объектов +за тик, а остаточный шанс учитывает фактическую скорость перехватчика. + +**Энергетика перехватчика.** Сначала ракета выходит из ТПК без двигателя: у С-400 это 1,0 с, +у С-500 — 1,2 с; только потом в её текущей точке зажигается факел, и тяга плавно нарастает. +После выброса ракета разгоняется, но двигатель работает конечное игровое время. После выгорания +ракета летит по инерции, теряет скорость и манёвренность; при недостаточной скорости сопровождение +срывается. Факел, звук двигателя и новый дым прекращаются при выгорании и в live, и в повторе; +уже созданный дым продолжает рассеиваться. У ракет с горячим (рельсовым) пуском тяга тоже +нарастает плавно, но холодного выброса нет; электрический P1-SUN не получает ракетную струю. **Последний шанс** (`/airdef lastchance on|off`, по умолчанию вкл). Комплекс стреляет и по классу целей вне своих ролей (С-400, С-500 и Patriot по умолчанию не бьют дроны), если цель долетит до своей точки меньше чем за 10 с и на ней ещё нет @@ -270,16 +330,18 @@ API мода `render.set-lights` (до 512, плавно интерполиру ## Устройство ``` -HORIZON_COMMIT коммит апстрима Horizon, на который накладываются патчи -horizon-patches/ изменения форка (git format-patch); tools/airdef.py fork собирает из них horizon/ -horizon/ форк (не в репозитории; ветка airdef). Что в нём меняется: +.gitmodules URL отдельного репозитория движка +horizon/ git submodule: точный SHA форка Horizon. Что в нём меняется: crates/horizon-mod-sdk/wit/horizon.wit render.set-models, spawn-particles, play-sound, query-ground / ground crates/horizon-mods/src/runtime.rs хост этих функций crates/horizon-client/src/game/mod_models.rs интерполяция и отрисовка моделей, частицы crates/horizon-client/src/gfx/mod_post.rs пост-проход мода: render.set-post / set-params -airdef-mod/ сам мод: Rust → WebAssembly - src/sim.rs симуляция: дроны, ракеты, ПВО, эффекты (20 тиков/с) +airdef-core/ SDK-free сборка общего ядра; использует те же исходники sim, без копии +airdef-mod/ адаптер: Rust → WebAssembly + src/sim/mod.rs симуляция: дроны, ракеты, ПВО, эффекты (20 тиков/с) + src/sim/battery.rs конфигурации пусковых и роли общей РЛС + src/sim/launch.rs резервирование ячеек и анимация крышек ТПК src/lib.rs предметы, команды, сохранение, передача в движок src/panel.rs сводка, src/radar.rs — радар, src/monitor.rs — монитор комплекса assets/airdef/sounds звуки (генерирует tools/gen_sounds.py) @@ -299,11 +361,17 @@ run-home/ тестовая игровая папка (для пров Модели — обычные блочные JSON-модели, по одной на часть: корпус, винт, башня, стволы. Их генерирует `tools/gen_models.py`. Координаты элементов должны быть в пределах −16..32, иначе Horizon отбросит модель. -Рельеф мод узнаёт через `render.query-ground`: присылает колонки, а движок отвечает высотой по карте -высот на следующем тике. +Рельеф и коллизии приходят асинхронно через ground/ray/section queries. Высотной карты недостаточно +для точного попадания: известные блоки и формы проверяются отдельно; ожидание/Unknown не считается +доказанным свободным путём. Историческое отображение повтора отделено от live-геометрии симуляции. ## Известные ограничения +- Для [#122](https://gitea.maoniao.team/chexit/airdef/issues/122) воспроизведено и исправлено исчерпание fuel при массовых взрывах: + каждый взрыв больше не обходит всю карту высот. Проверка на конкретном Windows-компьютере ещё нужна; + прирост FPS этим исправлением не обещается. - Мод клиентский: дроны и ПВО видит только тот, у кого он стоит. Мультиплеер не поддерживается. - Окно Horizon на macOS не рисует кадры, пока оно перекрыто другими окнами (это поведение движка). - Шейдер работает, только если не включён шейдер-пак Horizon (они взаимоисключающие). + +Разработка и обновление закреплённого движка: [docs/ENGINE.md](docs/ENGINE.md). diff --git a/airdef-core/Cargo.lock b/airdef-core/Cargo.lock new file mode 100644 index 0000000..83b2dd8 --- /dev/null +++ b/airdef-core/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "airdef-core" +version = "0.2.0" +dependencies = [ + "glam", +] + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" diff --git a/airdef-core/Cargo.toml b/airdef-core/Cargo.toml new file mode 100644 index 0000000..58dbf1e --- /dev/null +++ b/airdef-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "airdef-core" +version = "0.2.0" +edition = "2024" +publish = false + +[dependencies] +glam = "0.30" + +[features] +test-fixtures = [] + +[workspace] diff --git a/airdef-core/README.md b/airdef-core/README.md new file mode 100644 index 0000000..cf53cba --- /dev/null +++ b/airdef-core/README.md @@ -0,0 +1,13 @@ +# AirDef headless simulation + +This package compiles the same `airdef-mod/src/sim` source as the game adapter, with `glam` as its only dependency. A checkout does not need the Horizon submodule to build or run it: + +```sh +cargo test --manifest-path airdef-core/Cargo.toml +cargo run --manifest-path airdef-core/Cargo.toml --release --example sim_run -- --list +cargo run --manifest-path airdef-core/Cargo.toml --release --example sim_run -- --scenario big-smart +``` + +The shared scenario harness runs for both packages. Adapter integration tests for layout and smoke stay in `airdef-mod`; `core_boundary` verifies the public type facade and deterministic combat trace. + +This is an incremental boundary: the simulation still produces rendering and audio declarations and formats some UI text. Moving those outputs and diagnostics to adapters, remain follow-up work. The mod re-exports `airdef_core::sim` as its existing public facade. diff --git a/airdef-core/examples/intel_bench.rs b/airdef-core/examples/intel_bench.rs new file mode 100644 index 0000000..4e4a4b6 --- /dev/null +++ b/airdef-core/examples/intel_bench.rs @@ -0,0 +1,8 @@ +//! The enemy-picture bench (#127) without building the Horizon SDK. +extern crate airdef_core as airdef; +#[path = "../../airdef-mod/examples/intel_bench.rs"] +mod bench; + +fn main() { + bench::main(); +} diff --git a/airdef-core/examples/sim_run.rs b/airdef-core/examples/sim_run.rs new file mode 100644 index 0000000..b4ecf3d --- /dev/null +++ b/airdef-core/examples/sim_run.rs @@ -0,0 +1,8 @@ +//! Run the same native scenario CLI without building the Horizon SDK. +extern crate airdef_core as airdef; +#[path = "../../airdef-mod/examples/sim_run/main.rs"] +mod runner; + +fn main() { + runner::main(); +} diff --git a/airdef-core/src/lib.rs b/airdef-core/src/lib.rs new file mode 100644 index 0000000..52287f1 --- /dev/null +++ b/airdef-core/src/lib.rs @@ -0,0 +1,6 @@ +//! SDK-independent build of the same simulation used by the AirDef adapter. +//! +//! The source remains shared while the domain/presentation boundary is extracted; +//! no generated copy can drift from the mod's simulation. +#[path = "../../airdef-mod/src/sim/mod.rs"] +pub mod sim; diff --git a/airdef-core/tests/scenarios.rs b/airdef-core/tests/scenarios.rs new file mode 100644 index 0000000..6521c30 --- /dev/null +++ b/airdef-core/tests/scenarios.rs @@ -0,0 +1,166 @@ +//! The mod's headless scenarios run against the SDK-independent package too. +extern crate airdef_core as airdef; +#[path = "../../airdef-mod/examples/sim_run/harness.rs"] +mod harness; + +fn check(sc: harness::Scenario) -> harness::Report { + let r = harness::run(&sc); + let problems = r.problems(); + assert!(problems.is_empty(), "scenario {} failed: {problems:?}\n{}", r.name, r.text()); + assert!(r.launched > 0); + r +} + +#[test] +fn big_smart_raid_on_61_systems() { + let r = check(harness::big_smart()); + assert_eq!(r.units, 61); + assert!(r.fired > 0 && r.ground_queries > 0); +} + +#[test] +fn logistics_trucks_depot_and_raid() { + let r = check(harness::logistics()); + assert!(r.truck_busy_ticks > 0 && r.fired > 0); +} + +#[test] +fn ew_and_adaptive_enemy_over_three_raids() { + assert_eq!(check(harness::ew_adaptive()).raids_ended, 3); +} + +#[test] +fn same_seed_same_result() { + let a = harness::run(&harness::logistics()); + let b = harness::run(&harness::logistics()); + assert_eq!((a.ticks_run, a.launched, a.kills, a.hits, a.fired, a.ground_queries), (b.ticks_run, b.launched, b.kills, b.hits, b.fired, b.ground_queries)); +} + +#[test] +fn overlapping_mass_strikes_on_flat_world() { + use airdef::sim::{Aim, Approach, DroneKind as D, Ground, Out, RaidOrder, Sam, Sim}; + use glam::DVec3; + let sc = harness::big_smart(); + let terrain = harness::Terrain::Flat(70); + let mut sim = Sim::new(sc.seed); + sim.damage = false; + for unit in &sc.units { + let id = sim.id(); + sim.sams.push(Sam::new(id, unit.kind, DVec3::new(unit.pos.x, 70.0, unit.pos.z), unit.heading)); + } + sim.targets = vec![DVec3::new(0.0, 70.0, 0.0)]; + sim.target_health = vec![100.0]; + // Exact composition of the menu's "Массированный удар" preset. + let order = RaidOrder { kinds: vec![(D::Gerbera, 8), (D::Shahed, 12), (D::Geran3, 4), (D::Kh101, 4), (D::Kalibr, 4), (D::Kh22, 2), (D::Iskander, 2)], approach: Approach::AllSides, aim: Aim::Smart, dist: 4000.0 }; + let mut ground = Ground::default(); + let mut pending = Vec::new(); + let mut out = Out::default(); + let targets = sim.targets.clone(); + assert_eq!(sim.order_raid(&order, &targets, &mut out), 36); + let mut second = false; + let mut peak = 0; + for now in 1..=12000 { + if !second && !sim.planning() { + assert_eq!(sim.order_raid(&order, &targets, &mut out), 36); + second = true; + } + ground.answer(std::mem::take(&mut pending), now); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + let wanted = ground.take_wanted(); + pending = wanted.into_iter().map(|(x, z)| (x, z, 70)).collect(); + let sections = ground.world.take_wanted(now); + ground.world.serve(&terrain, sections, now); + peak = peak.max(sim.drones.iter().filter(|d| d.alive()).count()); + assert!(sim.drones.iter().all(|d| d.pos.is_finite() && d.vel.is_finite())); + if sim.launched >= 72 && peak > 36 { break; } + } + eprintln!("overlapping mass strikes: launched {}, peak airborne {peak}", sim.launched); + assert!(second && sim.launched >= 72, "second raid failed to launch; launched {}", sim.launched); + assert!(peak > 36, "raids never overlapped; peak {peak}"); +} + +#[test] +fn overlapping_oreshnik_splits_are_recorded() { + use airdef::sim::{Aim, Approach, DroneKind, Ground, Out, RaidOrder, Sim}; + use glam::DVec3; + let mut sim = Sim::new(1); + sim.damage = false; + let targets = vec![DVec3::new(0.0, 70.0, 0.0)]; + sim.targets = targets.clone(); + sim.target_health = vec![100.0]; + let order = RaidOrder { kinds: vec![(DroneKind::Oreshnik, 40)], approach: Approach::Sector, aim: Aim::Objects, dist: 2600.0 }; + let terrain = harness::Terrain::Flat(70); + let mut ground = Ground::default(); + let mut pending = Vec::new(); + let mut out = Out::default(); + assert_eq!(sim.order_raid(&order, &targets, &mut out), 40); + let mut second = false; + let mut peak = 0; + let mut peak_subs = 0; + for now in 1..=10000 { + if !second && !sim.planning() { + assert_eq!(sim.order_raid(&order, &targets, &mut out), 40); + second = true; + } + ground.answer(std::mem::take(&mut pending), now); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + pending = ground.take_wanted().into_iter().map(|(x, z)| (x, z, 70)).collect(); + let sections = ground.world.take_wanted(now); + ground.world.serve(&terrain, sections, now); + peak = peak.max(sim.drones.iter().filter(|d| d.alive()).count()); + peak_subs = peak_subs.max(sim.drones.iter().filter(|d| d.alive() && d.kind == DroneKind::OreshnikSub).count()); + assert!(sim.drones.iter().all(|d| d.pos.is_finite() && d.vel.is_finite())); + if second && sim.launched >= 80 && !sim.replays.is_empty() { break; } + } + eprintln!("overlapping oreshnik: launched {}, peak airborne {peak}, peak submunitions {peak_subs}, replays {}", sim.launched, sim.replays.len()); + assert_eq!(sim.launched, 2880); + assert!(peak_subs > 36, "expected many simultaneous submunitions"); + assert!(!sim.replays.is_empty(), "recording did not finish"); +} + +#[test] +fn two_maximum_menu_raids_with_answered_flat_terrain() { + use airdef::sim::{Aim, Approach, Ground, Out, RaidOrder, Sam, Sim, catalog::RAID_KINDS}; + use glam::DVec3; + let sc = harness::big_smart(); + let terrain = harness::Terrain::Flat(70); + let mut sim = Sim::new(sc.seed); + for unit in &sc.units { + let id = sim.id(); + sim.sams.push(Sam::new(id, unit.kind, DVec3::new(unit.pos.x, 70.0, unit.pos.z), unit.heading)); + } + let targets = vec![DVec3::new(0.0, 70.0, 0.0)]; + sim.targets = targets.clone(); + sim.target_health = vec![100.0]; + // The maximum selectable by the menu: 40 for each raid kind. + let order = RaidOrder { kinds: RAID_KINDS.iter().copied().map(|kind| (kind, 40)).collect(), approach: Approach::AllSides, aim: Aim::Smart, dist: 300.0 }; + let expected = RAID_KINDS.len() as u32 * 40; + let mut ground = Ground::default(); + let mut pending = Vec::new(); + let mut out = Out::default(); + assert_eq!(sim.order_raid(&order, &targets, &mut out), expected); + let mut second = false; + let mut overlap = false; + let mut peak = 0; + for now in 1..=5000 { + if !second && !sim.planning() { + overlap = !sim.queue.is_empty() || sim.drones.iter().any(|d| d.alive()); + assert_eq!(sim.order_raid(&order, &targets, &mut out), expected); + second = true; + } + ground.answer(std::mem::take(&mut pending), now); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + pending = ground.take_wanted().into_iter().map(|(x, z)| (x, z, 70)).collect(); + let sections = ground.world.take_wanted(now); + ground.world.serve(&terrain, sections, now); + peak = peak.max(sim.drones.iter().filter(|d| d.alive()).count()); + assert!(sim.drones.iter().all(|d| d.pos.is_finite() && d.vel.is_finite())); + if second && sim.launched >= expected * 2 && !sim.replays.is_empty() { break; } + } + eprintln!("max menu raids: requested {}, launched {}, peak {peak}, replays {}", expected * 2, sim.launched, sim.replays.len()); + assert!(second && overlap && sim.launched >= expected * 2 && peak > 40); + assert!(!sim.replays.is_empty(), "raid recording did not finish"); +} diff --git a/airdef-mod/Cargo.lock b/airdef-mod/Cargo.lock index d8c630a..1270314 100644 --- a/airdef-mod/Cargo.lock +++ b/airdef-mod/Cargo.lock @@ -6,10 +6,18 @@ version = 4 name = "airdef" version = "0.2.0" dependencies = [ + "airdef-core", "glam", "horizon-mod-sdk", ] +[[package]] +name = "airdef-core" +version = "0.2.0" +dependencies = [ + "glam", +] + [[package]] name = "anyhow" version = "1.0.104" diff --git a/airdef-mod/Cargo.toml b/airdef-mod/Cargo.toml index 3368bf3..616e673 100644 --- a/airdef-mod/Cargo.toml +++ b/airdef-mod/Cargo.toml @@ -9,9 +9,13 @@ description = "AirDef: long-range drones and air defence for the AirDef fork of crate-type = ["cdylib", "rlib"] [dependencies] +airdef-core = { path = "../airdef-core" } horizon-mod-sdk = { path = "../horizon/crates/horizon-mod-sdk" } glam = "0.30" +[dev-dependencies] +airdef-core = { path = "../airdef-core", features = ["test-fixtures"] } + [profile.release] opt-level = "s" lto = true diff --git a/airdef-mod/assets/airdef/blockstates/radar_mast.json b/airdef-mod/assets/airdef/blockstates/radar_mast.json new file mode 100644 index 0000000..193aa0b --- /dev/null +++ b/airdef-mod/assets/airdef/blockstates/radar_mast.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "airdef:block/radar_mast" + } + } +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/blockstates/radar_mast_burnt.json b/airdef-mod/assets/airdef/blockstates/radar_mast_burnt.json new file mode 100644 index 0000000..80b0021 --- /dev/null +++ b/airdef-mod/assets/airdef/blockstates/radar_mast_burnt.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "airdef:block/radar_mast_burnt" + } + } +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/blockstates/rm48u.json b/airdef-mod/assets/airdef/blockstates/rm48u.json new file mode 100644 index 0000000..34f9362 --- /dev/null +++ b/airdef-mod/assets/airdef/blockstates/rm48u.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "airdef:block/rm48u" + } + } +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/blockstates/tpk_cap.json b/airdef-mod/assets/airdef/blockstates/tpk_cap.json new file mode 100644 index 0000000..2a23228 --- /dev/null +++ b/airdef-mod/assets/airdef/blockstates/tpk_cap.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "airdef:block/tpk_cap" + } + } +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/blockstates/tpk_cap_burnt.json b/airdef-mod/assets/airdef/blockstates/tpk_cap_burnt.json new file mode 100644 index 0000000..06f7da1 --- /dev/null +++ b/airdef-mod/assets/airdef/blockstates/tpk_cap_burnt.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "airdef:block/tpk_cap_burnt" + } + } +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/lang/en_us.json b/airdef-mod/assets/airdef/lang/en_us.json index 757995d..273340f 100644 --- a/airdef-mod/assets/airdef/lang/en_us.json +++ b/airdef-mod/assets/airdef/lang/en_us.json @@ -30,10 +30,13 @@ "block.airdef.s400_tubes": "ТПК 48Н6", "block.airdef.s500_chassis": "Шасси С-500", "block.airdef.s500_tubes": "ТПК 77Н6", + "block.airdef.tpk_cap": "Крышка ТПК (анимация)", + "block.airdef.radar_mast": "Радиомачта (секция высоты)", "block.airdef.radar_trailer": "Прицеп РЛС", "block.airdef.radar_panel": "РЛС обзора", "block.airdef.m57e6": "57Э6", "block.airdef.m48n6": "48Н6", + "block.airdef.rm48u": "RM48U (игровой профиль)", "block.airdef.m77n6": "77Н6", "block.airdef.kalibr": "Калибр", "block.airdef.kh22": "Х-22", @@ -76,6 +79,8 @@ "block.airdef.s400_tubes_burnt": "ТПК 48Н6 (остов)", "block.airdef.s500_chassis_burnt": "Шасси С-500 (остов)", "block.airdef.s500_tubes_burnt": "ТПК 77Н6 (остов)", + "block.airdef.tpk_cap_burnt": "Крышка ТПК (анимация) (остов)", + "block.airdef.radar_mast_burnt": "Радиомачта (секция высоты) (остов)", "block.airdef.irist_chassis_burnt": "Шасси IRIS-T SLM (остов)", "block.airdef.irist_tubes_burnt": "Пакет IRIS-T (остов)", "block.airdef.nasams_trailer_burnt": "Прицеп NASAMS (остов)", diff --git a/airdef-mod/assets/airdef/lang/ru_ru.json b/airdef-mod/assets/airdef/lang/ru_ru.json index 757995d..273340f 100644 --- a/airdef-mod/assets/airdef/lang/ru_ru.json +++ b/airdef-mod/assets/airdef/lang/ru_ru.json @@ -30,10 +30,13 @@ "block.airdef.s400_tubes": "ТПК 48Н6", "block.airdef.s500_chassis": "Шасси С-500", "block.airdef.s500_tubes": "ТПК 77Н6", + "block.airdef.tpk_cap": "Крышка ТПК (анимация)", + "block.airdef.radar_mast": "Радиомачта (секция высоты)", "block.airdef.radar_trailer": "Прицеп РЛС", "block.airdef.radar_panel": "РЛС обзора", "block.airdef.m57e6": "57Э6", "block.airdef.m48n6": "48Н6", + "block.airdef.rm48u": "RM48U (игровой профиль)", "block.airdef.m77n6": "77Н6", "block.airdef.kalibr": "Калибр", "block.airdef.kh22": "Х-22", @@ -76,6 +79,8 @@ "block.airdef.s400_tubes_burnt": "ТПК 48Н6 (остов)", "block.airdef.s500_chassis_burnt": "Шасси С-500 (остов)", "block.airdef.s500_tubes_burnt": "ТПК 77Н6 (остов)", + "block.airdef.tpk_cap_burnt": "Крышка ТПК (анимация) (остов)", + "block.airdef.radar_mast_burnt": "Радиомачта (секция высоты) (остов)", "block.airdef.irist_chassis_burnt": "Шасси IRIS-T SLM (остов)", "block.airdef.irist_tubes_burnt": "Пакет IRIS-T (остов)", "block.airdef.nasams_trailer_burnt": "Прицеп NASAMS (остов)", diff --git a/airdef-mod/assets/airdef/models/block/buk_chassis.json b/airdef-mod/assets/airdef/models/block/buk_chassis.json index a3082c4..695c8c2 100644 --- a/airdef-mod/assets/airdef/models/block/buk_chassis.json +++ b/airdef-mod/assets/airdef/models/block/buk_chassis.json @@ -1,30 +1,25 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21": "airdef:block/ru_green_s21", - "ru_green_s31": "airdef:block/ru_green_s31", - "ru_green_s23": "airdef:block/ru_green_s23", - "ru_green": "airdef:block/ru_green", + "pilot_olive": "airdef:block/pilot_olive", "dark_metal": "airdef:block/dark_metal", - "bare_nozzle": "airdef:block/bare_nozzle", "track": "airdef:block/track", - "track_side_s31": "airdef:block/track_side_s31", - "ru_green_s13": "airdef:block/ru_green_s13", - "bare_lamp": "airdef:block/bare_lamp", - "mk_ru2": "airdef:block/mk_ru2", - "particle": "airdef:block/ru_green_s21" + "hub": "airdef:block/hub", + "metal": "airdef:block/metal", + "bare_nozzle": "airdef:block/bare_nozzle", + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -10.0, + -9.0, 4.0, - -14.0 + -13.0 ], "to": [ - 26.0, - 15.0, - 32.0 + 25.0, + 13.0, + 30.0 ], "faces": { "north": { @@ -32,36 +27,36 @@ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -70,7 +65,7 @@ 16, 16 ], - "texture": "#ru_green_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -79,88 +74,88 @@ 16, 16 ], - "texture": "#ru_green_s23" - } - } - }, - { - "from": [ - -10.0, - 4.0, - -16.0 - ], - "to": [ - 26.0, - 10.0, - -14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_green_s21" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_green_s21" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_green" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_green" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_green_s21" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" } } }, { "from": [ -9.0, - 10.0, - -15.0 + 4.0, + -16.0 ], "to": [ 25.0, - 14.0, - -12.0 + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 ], "faces": { "north": { @@ -170,7 +165,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -179,43 +174,43 @@ 16, 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "up": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "down": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" } }, "rotation": { @@ -223,21 +218,89 @@ "axis": "x", "origin": [ 8.0, - 12.0, - -13.5 + 11.0, + -11.5 ] } }, { "from": [ -8.0, - 15.0, - 22.0 + 13.0, + -7.0 ], "to": [ 24.0, - 16.0, - 32.0 + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 ], "faces": { "north": { @@ -262,7 +325,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal" @@ -271,7 +334,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal" @@ -281,7 +344,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal" }, @@ -290,7 +353,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal" } @@ -298,13 +361,13 @@ }, { "from": [ - -5.0, - 15.2, - 24.0 + -13.0, + 0.0, + -15.0 ], "to": [ - 21.0, - 15.8, + -9.0, + 8.5, 31.0 ], "faces": { @@ -312,873 +375,3253 @@ "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "south": { "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "east": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "west": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "up": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "down": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle" + "texture": "#track" } } }, { "from": [ - -1.0, + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -12.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -13.5 + ], + "to": [ + -13.02, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -13.5 + ], + "to": [ + 29.08, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -12.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -6.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -0.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -0.75 + ], + "to": [ + -13.02, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -0.75 + ], + "to": [ + 29.08, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 5.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 4.5 + ], + "to": [ + 29.08, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 5.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 11.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 11.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 10.5 + ], + "to": [ + -13.02, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 10.5 + ], + "to": [ + 29.08, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 11.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 11.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 17.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 17.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 16.5 + ], + "to": [ + 29.08, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 17.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 17.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 23.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -7.0, 15.0, -10.0 ], "to": [ - 9.0, - 16.0, - -4.0 + 2.0, + 15.7, + -5.0 ], "faces": { "north": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], - "texture": "#dark_metal" + "texture": "#metal" }, "south": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], - "texture": "#dark_metal" + "texture": "#metal" }, "east": { "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], - "texture": "#dark_metal" + "texture": "#metal" }, "west": { "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], - "texture": "#dark_metal" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 10.0, - 6.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 6.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -14.0, - 0.0, - -15.0 - ], - "to": [ - -10.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - } - } - }, - { - "from": [ - 26.0, - 0.0, - -15.0 - ], - "to": [ - 30.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - } - } - }, - { - "from": [ - -14.5, - 5.0, - -15.0 - ], - "to": [ - -10.0, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, + 9.0, 5.0 ], - "texture": "#ru_green" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_green" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13" - } - } - }, - { - "from": [ - 26.0, - 5.0, - -15.0 - ], - "to": [ - 30.5, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, + 9.0, 5.0 ], - "texture": "#ru_green" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_green" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13" - } - } - }, - { - "from": [ - -4.0, - 11.0, - -16.0 - ], - "to": [ - -1.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 17.0, - 11.0, - -16.0 - ], - "to": [ - 20.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 26.0, - 11.0, - 0.0 - ], - "to": [ - 26.12, - 14.5, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - } - } - }, - { - "from": [ - -10.120000000000001, - 11.0, - 0.0 - ], - "to": [ - -10.0, - 14.5, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" + "texture": "#metal" } } }, { "from": [ 3.0, - 16.0, - 2.0 + 14.0, + 23.0 ], "to": [ - 7.0, - 17.4, - 8.0 + 20.0, + 14.5, + 28.0 ], "faces": { "north": { "uv": [ 0, 0, - 4.0, - 1.3999999999999986 + 16, + 0.5 ], - "texture": "#dark_metal" + "texture": "#bare_nozzle" }, "south": { "uv": [ 0, 0, - 4.0, - 1.3999999999999986 + 16, + 0.5 ], - "texture": "#dark_metal" + "texture": "#bare_nozzle" }, "east": { "uv": [ 0, 0, - 6.0, - 1.3999999999999986 + 5.0, + 0.5 ], - "texture": "#dark_metal" + "texture": "#bare_nozzle" }, "west": { "uv": [ 0, 0, - 6.0, - 1.3999999999999986 + 5.0, + 0.5 ], - "texture": "#dark_metal" + "texture": "#bare_nozzle" }, "up": { "uv": [ 0, 0, - 4.0, - 6.0 + 16, + 5.0 ], - "texture": "#dark_metal" + "texture": "#bare_nozzle" }, "down": { "uv": [ 0, 0, - 4.0, - 6.0 + 16, + 5.0 ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 9.0, - 16.0, - 2.0 - ], - "to": [ - 13.0, - 17.4, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 5.0, - 16.0, - -8.0 - ], - "to": [ - 11.0, - 17.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 6.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 6.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#bare_lamp" + "texture": "#bare_nozzle" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/buk_chassis_burnt.json b/airdef-mod/assets/airdef/models/block/buk_chassis_burnt.json index af9de19..92ed4b1 100644 --- a/airdef-mod/assets/airdef/models/block/buk_chassis_burnt.json +++ b/airdef-mod/assets/airdef/models/block/buk_chassis_burnt.json @@ -1,30 +1,25 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21_b": "airdef:block/ru_green_s21_b", - "ru_green_s31_b": "airdef:block/ru_green_s31_b", - "ru_green_s23_b": "airdef:block/ru_green_s23_b", - "ru_green_b": "airdef:block/ru_green_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "dark_metal_b": "airdef:block/dark_metal_b", - "bare_nozzle_b": "airdef:block/bare_nozzle_b", "track_b": "airdef:block/track_b", - "track_side_s31_b": "airdef:block/track_side_s31_b", - "ru_green_s13_b": "airdef:block/ru_green_s13_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "mk_ru2_b": "airdef:block/mk_ru2_b", - "particle": "airdef:block/ru_green_s21_b" + "hub_b": "airdef:block/hub_b", + "metal_b": "airdef:block/metal_b", + "bare_nozzle_b": "airdef:block/bare_nozzle_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -10.0, + -9.0, 4.0, - -14.0 + -13.0 ], "to": [ - 26.0, - 15.0, - 32.0 + 25.0, + 13.0, + 30.0 ], "faces": { "north": { @@ -32,36 +27,36 @@ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -70,7 +65,7 @@ 16, 16 ], - "texture": "#ru_green_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -79,88 +74,88 @@ 16, 16 ], - "texture": "#ru_green_s23_b" - } - } - }, - { - "from": [ - -10.0, - 4.0, - -16.0 - ], - "to": [ - 26.0, - 10.0, - -14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_green_s21_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_green_s21_b" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_green_b" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_green_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_green_s21_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" } } }, { "from": [ -9.0, - 10.0, - -15.0 + 4.0, + -16.0 ], "to": [ 25.0, - 14.0, - -12.0 + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 ], "faces": { "north": { @@ -170,7 +165,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -179,43 +174,43 @@ 16, 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" } }, "rotation": { @@ -223,21 +218,89 @@ "axis": "x", "origin": [ 8.0, - 12.0, - -13.5 + 11.0, + -11.5 ] } }, { "from": [ -8.0, - 15.0, - 22.0 + 13.0, + -7.0 ], "to": [ 24.0, - 16.0, - 32.0 + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 ], "faces": { "north": { @@ -262,7 +325,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal_b" @@ -271,7 +334,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal_b" @@ -281,7 +344,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal_b" }, @@ -290,7 +353,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal_b" } @@ -298,13 +361,13 @@ }, { "from": [ - -5.0, - 15.2, - 24.0 + -13.0, + 0.0, + -15.0 ], "to": [ - 21.0, - 15.8, + -9.0, + 8.5, 31.0 ], "faces": { @@ -312,737 +375,2369 @@ "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "south": { "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "east": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "west": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "up": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "down": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" } } }, { "from": [ - -1.0, + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -12.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -13.5 + ], + "to": [ + 29.08, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -12.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -0.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 11.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 10.5 + ], + "to": [ + 29.08, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 11.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 17.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 16.5 + ], + "to": [ + 29.08, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 17.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -7.0, 15.0, -10.0 ], "to": [ - 9.0, - 16.0, - -4.0 + 2.0, + 15.7, + -5.0 ], "faces": { "north": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "south": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "east": { "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "west": { "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "up": { "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "down": { "uv": [ 0, 0, - 10.0, - 6.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -14.0, - 0.0, - -15.0 - ], - "to": [ - -10.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - } - } - }, - { - "from": [ - 26.0, - 0.0, - -15.0 - ], - "to": [ - 30.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - } - } - }, - { - "from": [ - -14.5, - 5.0, - -15.0 - ], - "to": [ - -10.0, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, + 9.0, 5.0 ], - "texture": "#ru_green_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_green_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13_b" - } - } - }, - { - "from": [ - 26.0, - 5.0, - -15.0 - ], - "to": [ - 30.5, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_green_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_green_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_green_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_green_s13_b" - } - } - }, - { - "from": [ - -4.0, - 11.0, - -16.0 - ], - "to": [ - -1.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - 17.0, - 11.0, - -16.0 - ], - "to": [ - 20.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - -10.120000000000001, - 11.0, - 0.0 - ], - "to": [ - -10.0, - 14.5, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2_b" + "texture": "#metal_b" } } }, { "from": [ 3.0, - 16.0, - 2.0 + 14.0, + 23.0 ], "to": [ - 7.0, - 17.4, - 8.0 + 20.0, + 14.5, + 28.0 ], "faces": { "north": { "uv": [ 0, 0, - 4.0, - 1.3999999999999986 + 16, + 0.5 ], - "texture": "#dark_metal_b" + "texture": "#bare_nozzle_b" }, "south": { "uv": [ 0, 0, - 4.0, - 1.3999999999999986 + 16, + 0.5 ], - "texture": "#dark_metal_b" + "texture": "#bare_nozzle_b" }, "east": { "uv": [ 0, 0, - 6.0, - 1.3999999999999986 + 5.0, + 0.5 ], - "texture": "#dark_metal_b" + "texture": "#bare_nozzle_b" }, "west": { "uv": [ 0, 0, - 6.0, - 1.3999999999999986 + 5.0, + 0.5 ], - "texture": "#dark_metal_b" + "texture": "#bare_nozzle_b" }, "up": { "uv": [ 0, 0, - 4.0, - 6.0 + 16, + 5.0 ], - "texture": "#dark_metal_b" + "texture": "#bare_nozzle_b" }, "down": { "uv": [ 0, 0, - 4.0, - 6.0 + 16, + 5.0 ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - 9.0, - 16.0, - 2.0 - ], - "to": [ - 13.0, - 17.4, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal_b" + "texture": "#bare_nozzle_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/buk_rails.json b/airdef-mod/assets/airdef/models/block/buk_rails.json index a5c717b..4e95bdb 100644 --- a/airdef-mod/assets/airdef/models/block/buk_rails.json +++ b/airdef-mod/assets/airdef/models/block/buk_rails.json @@ -4,8 +4,7 @@ "missile_ru": "airdef:block/missile_ru", "booster": "airdef:block/booster", "dark_metal": "airdef:block/dark_metal", - "ru_green_s31": "airdef:block/ru_green_s31", - "ru_green": "airdef:block/ru_green", + "pilot_olive": "airdef:block/pilot_olive", "particle": "airdef:block/missile_ru" }, "elements": [ @@ -844,7 +843,7 @@ 16, 2.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -853,7 +852,7 @@ 16, 2.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -862,7 +861,7 @@ 16, 2.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -871,7 +870,7 @@ 16, 2.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -880,7 +879,7 @@ 16, 16 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -889,9 +888,9 @@ 16, 16 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/buk_rails_burnt.json b/airdef-mod/assets/airdef/models/block/buk_rails_burnt.json index a5311a8..cdaf609 100644 --- a/airdef-mod/assets/airdef/models/block/buk_rails_burnt.json +++ b/airdef-mod/assets/airdef/models/block/buk_rails_burnt.json @@ -4,8 +4,7 @@ "missile_ru_b": "airdef:block/missile_ru_b", "booster_b": "airdef:block/booster_b", "dark_metal_b": "airdef:block/dark_metal_b", - "ru_green_s31_b": "airdef:block/ru_green_s31_b", - "ru_green_b": "airdef:block/ru_green_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "particle": "airdef:block/missile_ru_b" }, "elements": [ @@ -844,7 +843,7 @@ 16, 2.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -853,7 +852,7 @@ 16, 2.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -862,7 +861,7 @@ 16, 2.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -871,7 +870,7 @@ 16, 2.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -880,7 +879,7 @@ 16, 16 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -889,9 +888,9 @@ 16, 16 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/buk_turret.json b/airdef-mod/assets/airdef/models/block/buk_turret.json index 2431d96..3c2067e 100644 --- a/airdef-mod/assets/airdef/models/block/buk_turret.json +++ b/airdef-mod/assets/airdef/models/block/buk_turret.json @@ -1,11 +1,11 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21": "airdef:block/ru_green_s21", - "ru_green_s22": "airdef:block/ru_green_s22", + "pilot_olive": "airdef:block/pilot_olive", "radar_face": "airdef:block/radar_face", - "ru_green": "airdef:block/ru_green", - "particle": "airdef:block/ru_green_s21" + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "particle": "airdef:block/pilot_olive" }, "elements": [ { @@ -16,7 +16,7 @@ ], "to": [ 20.0, - 9.0, + 7.0, 20.0 ], "faces": { @@ -25,36 +25,36 @@ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -63,7 +63,7 @@ 16, 16 ], - "texture": "#ru_green_s22" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -72,7 +72,75 @@ 16, 16 ], - "texture": "#ru_green_s22" + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 7.0, + 2.0 + ], + "to": [ + 18.0, + 10.0, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" } } }, @@ -83,7 +151,7 @@ -10.0 ], "to": [ - 15.0, + 11.0, 14.0, -4.0 ], @@ -92,7 +160,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 12.0 ], "texture": "#radar_face" @@ -101,7 +169,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 12.0 ], "texture": "#radar_face" @@ -128,7 +196,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 6.0 ], "texture": "#radar_face" @@ -137,7 +205,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 6.0 ], "texture": "#radar_face" @@ -146,14 +214,14 @@ }, { "from": [ - -2.0, - 9.0, - 6.0 + 0.0, + 1.0, + -5.0 ], "to": [ - 18.0, - 11.0, - 18.0 + 16.0, + 3.0, + -3.0 ], "faces": { "north": { @@ -163,7 +231,7 @@ 16, 2.0 ], - "texture": "#ru_green" + "texture": "#dark_metal" }, "south": { "uv": [ @@ -172,7 +240,75 @@ 16, 2.0 ], - "texture": "#ru_green" + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 7.0, + 4.0 + ], + "to": [ + -2.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" }, "east": { "uv": [ @@ -181,7 +317,7 @@ 12.0, 2.0 ], - "texture": "#ru_green" + "texture": "#metal" }, "west": { "uv": [ @@ -190,27 +326,95 @@ 12.0, 2.0 ], - "texture": "#ru_green" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 16, + 2.0, 12.0 ], - "texture": "#ru_green" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 16, + 2.0, 12.0 ], - "texture": "#ru_green" + "texture": "#metal" + } + } + }, + { + "from": [ + 18.0, + 7.0, + 4.0 + ], + "to": [ + 20.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/buk_turret_burnt.json b/airdef-mod/assets/airdef/models/block/buk_turret_burnt.json index 521e5ff..49b5d0a 100644 --- a/airdef-mod/assets/airdef/models/block/buk_turret_burnt.json +++ b/airdef-mod/assets/airdef/models/block/buk_turret_burnt.json @@ -1,11 +1,11 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21_b": "airdef:block/ru_green_s21_b", - "ru_green_s22_b": "airdef:block/ru_green_s22_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "radar_face_b": "airdef:block/radar_face_b", - "ru_green_b": "airdef:block/ru_green_b", - "particle": "airdef:block/ru_green_s21_b" + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { @@ -16,7 +16,7 @@ ], "to": [ 20.0, - 9.0, + 7.0, 20.0 ], "faces": { @@ -25,36 +25,36 @@ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 9.0 + 7.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -63,7 +63,7 @@ 16, 16 ], - "texture": "#ru_green_s22_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -72,7 +72,75 @@ 16, 16 ], - "texture": "#ru_green_s22_b" + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.0, + 7.0, + 2.0 + ], + "to": [ + 18.0, + 10.0, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" } } }, @@ -83,7 +151,7 @@ -10.0 ], "to": [ - 15.0, + 11.0, 14.0, -4.0 ], @@ -92,7 +160,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 12.0 ], "texture": "#radar_face_b" @@ -101,7 +169,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 12.0 ], "texture": "#radar_face_b" @@ -128,7 +196,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 6.0 ], "texture": "#radar_face_b" @@ -137,7 +205,7 @@ "uv": [ 0, 0, - 14.0, + 10.0, 6.0 ], "texture": "#radar_face_b" @@ -146,14 +214,14 @@ }, { "from": [ - -2.0, - 9.0, - 6.0 + 0.0, + 1.0, + -5.0 ], "to": [ - 18.0, - 11.0, - 18.0 + 16.0, + 3.0, + -3.0 ], "faces": { "north": { @@ -163,7 +231,7 @@ 16, 2.0 ], - "texture": "#ru_green_b" + "texture": "#dark_metal_b" }, "south": { "uv": [ @@ -172,7 +240,75 @@ 16, 2.0 ], - "texture": "#ru_green_b" + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.0, + 7.0, + 4.0 + ], + "to": [ + -2.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" }, "east": { "uv": [ @@ -181,7 +317,7 @@ 12.0, 2.0 ], - "texture": "#ru_green_b" + "texture": "#metal_b" }, "west": { "uv": [ @@ -190,27 +326,95 @@ 12.0, 2.0 ], - "texture": "#ru_green_b" + "texture": "#metal_b" }, "up": { "uv": [ 0, 0, - 16, + 2.0, 12.0 ], - "texture": "#ru_green_b" + "texture": "#metal_b" }, "down": { "uv": [ 0, 0, - 16, + 2.0, 12.0 ], - "texture": "#ru_green_b" + "texture": "#metal_b" + } + } + }, + { + "from": [ + 18.0, + 7.0, + 4.0 + ], + "to": [ + 20.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/depot.json b/airdef-mod/assets/airdef/models/block/depot.json index 515d4fb..36dc091 100644 --- a/airdef-mod/assets/airdef/models/block/depot.json +++ b/airdef-mod/assets/airdef/models/block/depot.json @@ -3,6 +3,7 @@ "textures": { "concrete": "airdef:block/concrete", "crate": "airdef:block/crate", + "dark_metal": "airdef:block/dark_metal", "tube_cap": "airdef:block/tube_cap", "tube": "airdef:block/tube", "particle": "airdef:block/concrete" @@ -78,22 +79,22 @@ }, { "from": [ - -12.0, + -13.0, 2.0, - -12.0 + -13.0 ], "to": [ - 0.0, - 10.0, - 0.0 + 1.0, + 12.0, + 1.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate" }, @@ -101,8 +102,8 @@ "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate" }, @@ -110,8 +111,8 @@ "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate" }, @@ -119,8 +120,8 @@ "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate" }, @@ -128,8 +129,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate" }, @@ -137,8 +138,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate" } @@ -146,21 +147,21 @@ }, { "from": [ - -12.0, + -13.0, 2.0, - 4.0 + 3.0 ], "to": [ - 0.0, + 1.0, 10.0, - 16.0 + 17.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -169,7 +170,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -178,7 +179,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -187,7 +188,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -196,8 +197,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate" }, @@ -205,8 +206,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate" } @@ -214,21 +215,21 @@ }, { "from": [ - 4.0, + 3.0, 2.0, - -12.0 + -13.0 ], "to": [ - 16.0, + 17.0, 10.0, - 0.0 + 1.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -237,7 +238,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -246,7 +247,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -255,7 +256,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate" @@ -264,8 +265,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate" }, @@ -273,13 +274,149 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate" } } }, + { + "from": [ + -14.0, + 2.0, + 27.0 + ], + "to": [ + 30.0, + 5.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -14.0, + 2.0, + -14.0 + ], + "to": [ + -12.0, + 5.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, { "from": [ 6.0, @@ -485,4 +622,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/depot_burnt.json b/airdef-mod/assets/airdef/models/block/depot_burnt.json index ba73159..42c6dc6 100644 --- a/airdef-mod/assets/airdef/models/block/depot_burnt.json +++ b/airdef-mod/assets/airdef/models/block/depot_burnt.json @@ -3,6 +3,7 @@ "textures": { "concrete_b": "airdef:block/concrete_b", "crate_b": "airdef:block/crate_b", + "dark_metal_b": "airdef:block/dark_metal_b", "tube_cap_b": "airdef:block/tube_cap_b", "tube_b": "airdef:block/tube_b", "particle": "airdef:block/concrete_b" @@ -78,22 +79,22 @@ }, { "from": [ - -12.0, + -13.0, 2.0, - -12.0 + -13.0 ], "to": [ - 0.0, - 10.0, - 0.0 + 1.0, + 12.0, + 1.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate_b" }, @@ -101,8 +102,8 @@ "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate_b" }, @@ -110,8 +111,8 @@ "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate_b" }, @@ -119,8 +120,8 @@ "uv": [ 0, 0, - 12.0, - 8.0 + 14.0, + 10.0 ], "texture": "#crate_b" }, @@ -128,8 +129,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate_b" }, @@ -137,8 +138,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate_b" } @@ -146,21 +147,21 @@ }, { "from": [ - -12.0, + -13.0, 2.0, - 4.0 + 3.0 ], "to": [ - 0.0, + 1.0, 10.0, - 16.0 + 17.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -169,7 +170,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -178,7 +179,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -187,7 +188,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -196,8 +197,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate_b" }, @@ -205,8 +206,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate_b" } @@ -214,21 +215,21 @@ }, { "from": [ - 4.0, + 3.0, 2.0, - -12.0 + -13.0 ], "to": [ - 16.0, + 17.0, 10.0, - 0.0 + 1.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -237,7 +238,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -246,7 +247,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -255,7 +256,7 @@ "uv": [ 0, 0, - 12.0, + 14.0, 8.0 ], "texture": "#crate_b" @@ -264,8 +265,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate_b" }, @@ -273,13 +274,149 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#crate_b" } } }, + { + "from": [ + -14.0, + 2.0, + 27.0 + ], + "to": [ + 30.0, + 5.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -14.0, + 2.0, + -14.0 + ], + "to": [ + -12.0, + 5.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, { "from": [ 6.0, @@ -485,4 +622,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/ew_truck.json b/airdef-mod/assets/airdef/models/block/ew_truck.json index 5d5ca34..02971fc 100644 --- a/airdef-mod/assets/airdef/models/block/ew_truck.json +++ b/airdef-mod/assets/airdef/models/block/ew_truck.json @@ -1,29 +1,24 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21": "airdef:block/ru_khaki_s21", - "ru_khaki_s31": "airdef:block/ru_khaki_s31", - "ru_khaki_s23": "airdef:block/ru_khaki_s23", + "pilot_olive": "airdef:block/pilot_olive", "glass_cab": "airdef:block/glass_cab", - "ru_khaki": "airdef:block/ru_khaki", - "ru_khaki_s22": "airdef:block/ru_khaki_s22", - "bare_antenna": "airdef:block/bare_antenna", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "mk_ru": "airdef:block/mk_ru", - "bare_lamp": "airdef:block/bare_lamp", - "dark_metal": "airdef:block/dark_metal", - "particle": "airdef:block/ru_khaki_s21" + "bare_antenna": "airdef:block/bare_antenna", + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -35,7 +30,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -44,7 +39,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -53,7 +48,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -62,7 +57,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -71,7 +66,7 @@ 16, 16 ], - "texture": "#ru_khaki_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -80,19 +75,19 @@ 16, 16 ], - "texture": "#ru_khaki_s23" + "texture": "#pilot_olive" } } }, { "from": [ - -6.0, + -5.0, 9.0, -16.0 ], "to": [ - 22.0, - 19.0, + 21.0, + 16.0, -6.0 ], "faces": { @@ -101,36 +96,36 @@ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#glass_cab" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 10.0, - 10.0 + 7.0 ], - "texture": "#ru_khaki" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 10.0, - 10.0 + 7.0 ], - "texture": "#ru_khaki" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -139,7 +134,7 @@ 16, 10.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -148,7 +143,3815 @@ 16, 10.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.0 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" } } }, @@ -171,7 +3974,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -180,7 +3983,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -189,7 +3992,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -198,7 +4001,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -207,7 +4010,7 @@ 16, 16 ], - "texture": "#ru_khaki_s22" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -216,7 +4019,75 @@ 16, 16 ], - "texture": "#ru_khaki_s22" + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 12.0, + 30.0 + ], + "to": [ + 20.0, + 20.0, + 30.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 8.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 8.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.3000000000000007 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.3000000000000007 + ], + "texture": "#dark_metal" } } }, @@ -294,77 +4165,9 @@ 24.0, 9.0 ], - "to": [ - 7.0, - 27.0, - 13.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 11.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "south": { - "uv": [ - 0, - 0, - 11.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "up": { - "uv": [ - 0, - 0, - 11.0, - 4.0 - ], - "texture": "#bare_antenna" - }, - "down": { - "uv": [ - 0, - 0, - 11.0, - 4.0 - ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - 9.0, - 24.0, - 9.0 - ], "to": [ 20.0, - 27.0, + 25.0, 13.0 ], "faces": { @@ -372,8 +4175,8 @@ "uv": [ 0, 0, - 11.0, - 3.0 + 16, + 1.0 ], "texture": "#bare_antenna" }, @@ -381,8 +4184,8 @@ "uv": [ 0, 0, - 11.0, - 3.0 + 16, + 1.0 ], "texture": "#bare_antenna" }, @@ -391,7 +4194,7 @@ 0, 0, 4.0, - 3.0 + 1.0 ], "texture": "#bare_antenna" }, @@ -400,7 +4203,7 @@ 0, 0, 4.0, - 3.0 + 1.0 ], "texture": "#bare_antenna" }, @@ -408,7 +4211,7 @@ "uv": [ 0, 0, - 11.0, + 16, 4.0 ], "texture": "#bare_antenna" @@ -417,7 +4220,7 @@ "uv": [ 0, 0, - 11.0, + 16, 4.0 ], "texture": "#bare_antenna" @@ -430,77 +4233,9 @@ 28.0, 9.0 ], - "to": [ - 7.0, - 31.0, - 13.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 11.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "south": { - "uv": [ - 0, - 0, - 11.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 3.0 - ], - "texture": "#bare_antenna" - }, - "up": { - "uv": [ - 0, - 0, - 11.0, - 4.0 - ], - "texture": "#bare_antenna" - }, - "down": { - "uv": [ - 0, - 0, - 11.0, - 4.0 - ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - 9.0, - 28.0, - 9.0 - ], "to": [ 20.0, - 31.0, + 29.0, 13.0 ], "faces": { @@ -508,8 +4243,8 @@ "uv": [ 0, 0, - 11.0, - 3.0 + 16, + 1.0 ], "texture": "#bare_antenna" }, @@ -517,8 +4252,8 @@ "uv": [ 0, 0, - 11.0, - 3.0 + 16, + 1.0 ], "texture": "#bare_antenna" }, @@ -527,7 +4262,7 @@ 0, 0, 4.0, - 3.0 + 1.0 ], "texture": "#bare_antenna" }, @@ -536,7 +4271,7 @@ 0, 0, 4.0, - 3.0 + 1.0 ], "texture": "#bare_antenna" }, @@ -544,7 +4279,7 @@ "uv": [ 0, 0, - 11.0, + 16, 4.0 ], "texture": "#bare_antenna" @@ -553,1512 +4288,12 @@ "uv": [ 0, 0, - 11.0, + 16, 4.0 ], "texture": "#bare_antenna" } } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.5, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 25.0, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.53, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 24.97, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.5, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 25.0, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.53, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 24.97, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.5, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 25.0, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.53, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 24.97, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 22.0, - 11.0, - -14.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - } - } - }, - { - "from": [ - -6.119999999999997, - 11.0, - -14.0 - ], - "to": [ - -6.0, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - } - } - }, - { - "from": [ - -4.5, - 10.0, - -16.0 - ], - "to": [ - -2.5, - 11.6, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 10.0, - -16.0 - ], - "to": [ - 20.5, - 11.6, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 6.0, - 22.0, - -4.0 - ], - "to": [ - 10.0, - 23.5, - 0.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 1.5 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 1.5 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 1.5 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 1.5 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 4.0 - ], - "texture": "#dark_metal" - } - } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/ew_truck_burnt.json b/airdef-mod/assets/airdef/models/block/ew_truck_burnt.json index 0c56bb0..bc3ca8d 100644 --- a/airdef-mod/assets/airdef/models/block/ew_truck_burnt.json +++ b/airdef-mod/assets/airdef/models/block/ew_truck_burnt.json @@ -1,27 +1,24 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21_b": "airdef:block/ru_khaki_s21_b", - "ru_khaki_s31_b": "airdef:block/ru_khaki_s31_b", - "ru_khaki_s23_b": "airdef:block/ru_khaki_s23_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "glass_cab_b": "airdef:block/glass_cab_b", - "ru_khaki_b": "airdef:block/ru_khaki_b", - "ru_khaki_s22_b": "airdef:block/ru_khaki_s22_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "mk_ru_b": "airdef:block/mk_ru_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "particle": "airdef:block/ru_khaki_s21_b" + "dark_metal_b": "airdef:block/dark_metal_b", + "bare_antenna_b": "airdef:block/bare_antenna_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -33,7 +30,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -42,7 +39,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -51,7 +48,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -60,7 +57,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -69,7 +66,7 @@ 16, 16 ], - "texture": "#ru_khaki_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -78,19 +75,19 @@ 16, 16 ], - "texture": "#ru_khaki_s23_b" + "texture": "#pilot_olive_b" } } }, { "from": [ - -6.0, + -5.0, 9.0, -16.0 ], "to": [ - 22.0, - 19.0, + 21.0, + 16.0, -6.0 ], "faces": { @@ -99,36 +96,36 @@ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#glass_cab_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 10.0, - 10.0 + 7.0 ], - "texture": "#ru_khaki_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 10.0, - 10.0 + 7.0 ], - "texture": "#ru_khaki_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -137,7 +134,7 @@ 16, 10.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -146,7 +143,2795 @@ 16, 10.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" } } }, @@ -169,7 +2954,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -178,7 +2963,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -187,7 +2972,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -196,7 +2981,7 @@ 16, 13.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -205,7 +2990,7 @@ 16, 16 ], - "texture": "#ru_khaki_s22_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -214,1373 +2999,281 @@ 16, 16 ], - "texture": "#ru_khaki_s22_b" + "texture": "#pilot_olive_b" } } }, { "from": [ - -9.0, - 0.3424499999999995, - -12.65755 + -4.0, + 12.0, + 30.0 ], "to": [ - -5.5, - 8.65755, - -4.3424499999999995 + 20.0, + 20.0, + 30.3 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 16, + 8.0 ], - "texture": "#tire_b" + "texture": "#dark_metal_b" }, "south": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 16, + 8.0 ], - "texture": "#tire_b" + "texture": "#dark_metal_b" }, "east": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 0.3000000000000007, + 8.0 ], - "texture": "#hub_b" + "texture": "#dark_metal_b" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 0.3000000000000007, + 8.0 ], - "texture": "#hub_b" + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 16, + 0.3000000000000007 ], - "texture": "#tire_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 16, + 0.3000000000000007 ], - "texture": "#tire_b" + "texture": "#dark_metal_b" } } }, { "from": [ - 21.5, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 25.0, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.53, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 24.97, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.5, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 25.0, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.53, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 24.97, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.5, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 25.0, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.53, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 24.97, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ + 7.0, 22.0, - 11.0, - -14.0 + 10.0 ], "to": [ - 22.119999999999997, - 15.0, - -7.0 + 9.0, + 32.0, + 12.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.25, - 4.0 + 2.0, + 10.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "south": { "uv": [ 0, 0, - 0.25, - 4.0 + 2.0, + 10.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "east": { "uv": [ 0, 0, - 7.0, - 4.0 + 2.0, + 10.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "west": { "uv": [ 0, 0, - 7.0, - 4.0 + 2.0, + 10.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "up": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.0, + 2.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "down": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.0, + 2.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" } } }, { "from": [ - -6.119999999999997, - 11.0, - -14.0 + -4.0, + 24.0, + 9.0 ], "to": [ - -6.0, - 15.0, - -7.0 + 20.0, + 25.0, + 13.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.25, - 4.0 + 16, + 1.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "south": { "uv": [ 0, 0, - 0.25, - 4.0 + 16, + 1.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "east": { "uv": [ 0, 0, - 7.0, - 4.0 + 4.0, + 1.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "west": { "uv": [ 0, 0, - 7.0, - 4.0 + 4.0, + 1.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "up": { "uv": [ 0, 0, - 0.25, - 7.0 + 16, + 4.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" }, "down": { "uv": [ 0, 0, - 0.25, - 7.0 + 16, + 4.0 ], - "texture": "#mk_ru_b" + "texture": "#bare_antenna_b" } } }, { "from": [ - -4.5, - 10.0, - -16.0 + -4.0, + 28.0, + 9.0 ], "to": [ - -2.5, - 11.6, - -15.4 + 20.0, + 29.0, + 13.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 16, + 1.0 ], - "texture": "#bare_lamp_b" + "texture": "#bare_antenna_b" }, "south": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 16, + 1.0 ], - "texture": "#bare_lamp_b" + "texture": "#bare_antenna_b" }, "east": { "uv": [ 0, 0, - 0.5999999999999996, - 1.5999999999999996 + 4.0, + 1.0 ], - "texture": "#bare_lamp_b" + "texture": "#bare_antenna_b" }, "west": { "uv": [ 0, 0, - 0.5999999999999996, - 1.5999999999999996 + 4.0, + 1.0 ], - "texture": "#bare_lamp_b" + "texture": "#bare_antenna_b" }, "up": { "uv": [ 0, 0, - 2.0, - 0.5999999999999996 + 16, + 4.0 ], - "texture": "#bare_lamp_b" + "texture": "#bare_antenna_b" }, "down": { "uv": [ 0, 0, - 2.0, - 0.5999999999999996 + 16, + 4.0 ], - "texture": "#bare_lamp_b" + "texture": "#bare_antenna_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/gepard_guns.json b/airdef-mod/assets/airdef/models/block/gepard_guns.json index a62e408..9ec9c4d 100644 --- a/airdef-mod/assets/airdef/models/block/gepard_guns.json +++ b/airdef-mod/assets/airdef/models/block/gepard_guns.json @@ -2,7 +2,7 @@ "ambientocclusion": false, "textures": { "bare_barrel": "airdef:block/bare_barrel", - "flecktarn": "airdef:block/flecktarn", + "pilot_nato": "airdef:block/pilot_nato", "dark_metal": "airdef:block/dark_metal", "particle": "airdef:block/bare_barrel" }, @@ -162,7 +162,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "south": { "uv": [ @@ -171,7 +171,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "east": { "uv": [ @@ -180,7 +180,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "west": { "uv": [ @@ -189,7 +189,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -198,7 +198,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -207,7 +207,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" } } }, @@ -230,7 +230,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "south": { "uv": [ @@ -239,7 +239,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "east": { "uv": [ @@ -248,7 +248,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "west": { "uv": [ @@ -257,7 +257,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -266,7 +266,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -275,7 +275,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" } } }, @@ -416,4 +416,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/gepard_guns_burnt.json b/airdef-mod/assets/airdef/models/block/gepard_guns_burnt.json index e2d3531..899e053 100644 --- a/airdef-mod/assets/airdef/models/block/gepard_guns_burnt.json +++ b/airdef-mod/assets/airdef/models/block/gepard_guns_burnt.json @@ -2,7 +2,7 @@ "ambientocclusion": false, "textures": { "bare_barrel_b": "airdef:block/bare_barrel_b", - "flecktarn_b": "airdef:block/flecktarn_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", "dark_metal_b": "airdef:block/dark_metal_b", "particle": "airdef:block/bare_barrel_b" }, @@ -162,7 +162,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ @@ -171,7 +171,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ @@ -180,7 +180,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ @@ -189,7 +189,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -198,7 +198,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -207,7 +207,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" } } }, @@ -230,7 +230,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ @@ -239,7 +239,7 @@ 5.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ @@ -248,7 +248,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ @@ -257,7 +257,7 @@ 12.0, 5.5 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -266,7 +266,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -275,7 +275,7 @@ 5.0, 12.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" } } }, @@ -348,4 +348,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/gepard_hull.json b/airdef-mod/assets/airdef/models/block/gepard_hull.json index 8df7024..a3c25ff 100644 --- a/airdef-mod/assets/airdef/models/block/gepard_hull.json +++ b/airdef-mod/assets/airdef/models/block/gepard_hull.json @@ -1,26 +1,24 @@ { "ambientocclusion": false, "textures": { - "flecktarn_s21": "airdef:block/flecktarn_s21", - "flecktarn_s31": "airdef:block/flecktarn_s31", - "flecktarn_s23": "airdef:block/flecktarn_s23", - "flecktarn": "airdef:block/flecktarn", + "pilot_nato": "airdef:block/pilot_nato", "dark_metal": "airdef:block/dark_metal", "metal": "airdef:block/metal", "track": "airdef:block/track", - "flecktarn_s13": "airdef:block/flecktarn_s13", - "particle": "airdef:block/flecktarn_s21" + "hub": "airdef:block/hub", + "bare_nozzle": "airdef:block/bare_nozzle", + "particle": "airdef:block/pilot_nato" }, "elements": [ { "from": [ - -10.0, + -9.0, 4.0, - -12.0 + -8.0 ], "to": [ - 26.0, - 17.0, + 25.0, + 13.5, 30.0 ], "faces": { @@ -29,36 +27,36 @@ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s31" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s31" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -67,7 +65,7 @@ 16, 16 ], - "texture": "#flecktarn_s23" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -76,20 +74,20 @@ 16, 16 ], - "texture": "#flecktarn_s23" + "texture": "#pilot_nato" } } }, { "from": [ - -10.0, + -9.0, 4.0, - -16.0 + -15.0 ], "to": [ - 26.0, - 12.0, - -12.0 + 25.0, + 9.0, + -8.0 ], "faces": { "north": { @@ -97,66 +95,211 @@ 0, 0, 16, - 8.0 + 5.0 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 8.0 + 5.0 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, - 4.0, - 8.0 + 7.0, + 5.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, - 4.0, - 8.0 + 7.0, + 5.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "up": { "uv": [ 0, 0, 16, - 4.0 + 7.0 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "down": { "uv": [ 0, 0, 16, - 4.0 + 7.0 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" } } }, + { + "from": [ + -8.5, + 9.0, + -14.0 + ], + "to": [ + 24.5, + 13.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -10.5 + ] + } + }, { "from": [ -8.0, - 17.0, - 18.0 + 13.5, + -4.0 ], "to": [ 24.0, - 18.0, + 15.2, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -7.0, + 13.5, + 18.0 + ], + "to": [ + 23.0, + 14.5, 30.0 ], "faces": { @@ -219,21 +362,21 @@ { "from": [ -6.0, - 17.0, - -10.0 + 15.3, + -6.0 ], "to": [ - 4.0, - 18.0, - -4.0 + 3.0, + 15.9, + -1.0 ], "faces": { "north": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.5999999999999996 ], "texture": "#metal" }, @@ -241,8 +384,8 @@ "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.5999999999999996 ], "texture": "#metal" }, @@ -250,8 +393,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.5999999999999996 ], "texture": "#metal" }, @@ -259,8 +402,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.5999999999999996 ], "texture": "#metal" }, @@ -268,8 +411,8 @@ "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], "texture": "#metal" }, @@ -277,8 +420,8 @@ "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], "texture": "#metal" } @@ -292,7 +435,7 @@ ], "to": [ -10.0, - 10.0, + 8.8, 31.0 ], "faces": { @@ -301,7 +444,7 @@ 0, 0, 4.0, - 10.0 + 8.8 ], "texture": "#track" }, @@ -310,7 +453,7 @@ 0, 0, 4.0, - 10.0 + 8.8 ], "texture": "#track" }, @@ -319,7 +462,7 @@ 0, 0, 16, - 10.0 + 8.8 ], "texture": "#track" }, @@ -328,7 +471,7 @@ 0, 0, 16, - 10.0 + 8.8 ], "texture": "#track" }, @@ -360,113 +503,2969 @@ ], "to": [ 30.0, + 8.8, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -11.82 + ], + "to": [ + -14.02, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + -11.82 + ], + "to": [ + 30.08, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -12.8 + ], + "to": [ + -14.02, + 6.05, + -7.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -12.8 + ], + "to": [ + 30.08, + 6.05, + -7.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + -11.82 + ], + "to": [ + -14.02, + 7.8, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -11.82 + ], + "to": [ + 30.08, + 7.8, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -5.82 + ], + "to": [ + -14.02, + 2.55, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + -5.82 + ], + "to": [ + 30.08, + 2.55, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -6.8 + ], + "to": [ + -14.02, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -6.8 + ], + "to": [ + 30.08, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + -5.82 + ], + "to": [ + -14.02, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -5.82 + ], + "to": [ + 30.08, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 0.18000000000000016 + ], + "to": [ + -14.02, + 2.55, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 0.18000000000000016 + ], + "to": [ + 30.08, + 2.55, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -0.7999999999999998 + ], + "to": [ + -14.02, + 6.05, + 4.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -0.7999999999999998 + ], + "to": [ + 30.08, + 6.05, + 4.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 0.18000000000000016 + ], + "to": [ + -14.02, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 0.18000000000000016 + ], + "to": [ + 30.08, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 6.18 + ], + "to": [ + -14.02, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 6.18 + ], + "to": [ + 30.08, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 5.2 + ], + "to": [ + -14.02, + 6.05, + 10.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 5.2 + ], + "to": [ + 30.08, + 6.05, + 10.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 6.18 + ], + "to": [ + -14.02, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 6.18 + ], + "to": [ + 30.08, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 12.18 + ], + "to": [ + -14.02, + 2.55, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 12.18 + ], + "to": [ + 30.08, + 2.55, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 11.2 + ], + "to": [ + -14.02, + 6.05, + 16.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 11.2 + ], + "to": [ + 30.08, + 6.05, + 16.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 12.18 + ], + "to": [ + -14.02, + 7.8, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 12.18 + ], + "to": [ + 30.08, + 7.8, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 18.18 + ], + "to": [ + -14.02, + 2.55, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 18.18 + ], + "to": [ + 30.08, + 2.55, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 17.2 + ], + "to": [ + -14.02, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 17.2 + ], + "to": [ + 30.08, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 18.18 + ], + "to": [ + -14.02, + 7.8, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 18.18 + ], + "to": [ + 30.08, + 7.8, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 24.18 + ], + "to": [ + -14.02, + 2.55, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 24.18 + ], + "to": [ + 30.08, + 2.55, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 23.2 + ], + "to": [ + -14.02, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 23.2 + ], + "to": [ + 30.08, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 24.18 + ], + "to": [ + -14.02, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 24.18 + ], + "to": [ + 30.08, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.5, + 8.6, + -14.0 + ], + "to": [ + -10.0, 10.0, 31.0 ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 10.0 - ], - "texture": "#track" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 10.0 - ], - "texture": "#track" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#track" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#track" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - } - } - }, - { - "from": [ - -14.5, - 6.0, - -14.0 - ], - "to": [ - -10.0, - 11.0, - 31.0 - ], "faces": { "north": { "uv": [ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -475,7 +3474,7 @@ 4.5, 16 ], - "texture": "#flecktarn_s13" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -484,19 +3483,19 @@ 4.5, 16 ], - "texture": "#flecktarn_s13" + "texture": "#pilot_nato" } } }, { "from": [ 26.0, - 6.0, + 8.6, -14.0 ], "to": [ 30.5, - 11.0, + 10.0, 31.0 ], "faces": { @@ -505,36 +3504,36 @@ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -543,7 +3542,7 @@ 4.5, 16 ], - "texture": "#flecktarn_s13" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -552,9 +3551,213 @@ 4.5, 16 ], - "texture": "#flecktarn_s13" + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 20.0 + ], + "to": [ + 22.0, + 14.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 24.0 + ], + "to": [ + 22.0, + 14.8, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 28.0 + ], + "to": [ + 22.0, + 14.8, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/gepard_hull_burnt.json b/airdef-mod/assets/airdef/models/block/gepard_hull_burnt.json index b871dda..04683f0 100644 --- a/airdef-mod/assets/airdef/models/block/gepard_hull_burnt.json +++ b/airdef-mod/assets/airdef/models/block/gepard_hull_burnt.json @@ -1,26 +1,24 @@ { "ambientocclusion": false, "textures": { - "flecktarn_s21_b": "airdef:block/flecktarn_s21_b", - "flecktarn_s31_b": "airdef:block/flecktarn_s31_b", - "flecktarn_s23_b": "airdef:block/flecktarn_s23_b", - "flecktarn_b": "airdef:block/flecktarn_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", "dark_metal_b": "airdef:block/dark_metal_b", "metal_b": "airdef:block/metal_b", "track_b": "airdef:block/track_b", - "flecktarn_s13_b": "airdef:block/flecktarn_s13_b", - "particle": "airdef:block/flecktarn_s21_b" + "hub_b": "airdef:block/hub_b", + "bare_nozzle_b": "airdef:block/bare_nozzle_b", + "particle": "airdef:block/pilot_nato_b" }, "elements": [ { "from": [ - -10.0, + -9.0, 4.0, - -12.0 + -8.0 ], "to": [ - 26.0, - 17.0, + 25.0, + 13.5, 30.0 ], "faces": { @@ -29,36 +27,36 @@ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s31_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 13.0 + 9.5 ], - "texture": "#flecktarn_s31_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -67,7 +65,7 @@ 16, 16 ], - "texture": "#flecktarn_s23_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -76,20 +74,20 @@ 16, 16 ], - "texture": "#flecktarn_s23_b" + "texture": "#pilot_nato_b" } } }, { "from": [ - -10.0, + -9.0, 4.0, - -16.0 + -15.0 ], "to": [ - 26.0, - 12.0, - -12.0 + 25.0, + 9.0, + -8.0 ], "faces": { "north": { @@ -97,66 +95,211 @@ 0, 0, 16, - 8.0 + 5.0 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 8.0 + 5.0 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, - 4.0, - 8.0 + 7.0, + 5.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, - 4.0, - 8.0 + 7.0, + 5.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ 0, 0, 16, - 4.0 + 7.0 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ 0, 0, 16, - 4.0 + 7.0 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" } } }, + { + "from": [ + -8.5, + 9.0, + -14.0 + ], + "to": [ + 24.5, + 13.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -10.5 + ] + } + }, { "from": [ -8.0, - 17.0, - 18.0 + 13.5, + -4.0 ], "to": [ 24.0, - 18.0, + 15.2, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -7.0, + 13.5, + 18.0 + ], + "to": [ + 23.0, + 14.5, 30.0 ], "faces": { @@ -219,21 +362,21 @@ { "from": [ -6.0, - 17.0, - -10.0 + 15.3, + -6.0 ], "to": [ - 4.0, - 18.0, - -4.0 + 3.0, + 15.9, + -1.0 ], "faces": { "north": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.5999999999999996 ], "texture": "#metal_b" }, @@ -241,8 +384,8 @@ "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.5999999999999996 ], "texture": "#metal_b" }, @@ -250,8 +393,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.5999999999999996 ], "texture": "#metal_b" }, @@ -259,8 +402,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.5999999999999996 ], "texture": "#metal_b" }, @@ -268,8 +411,8 @@ "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], "texture": "#metal_b" }, @@ -277,8 +420,8 @@ "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], "texture": "#metal_b" } @@ -292,7 +435,7 @@ ], "to": [ -10.0, - 10.0, + 8.8, 31.0 ], "faces": { @@ -301,7 +444,7 @@ 0, 0, 4.0, - 10.0 + 8.8 ], "texture": "#track_b" }, @@ -310,7 +453,7 @@ 0, 0, 4.0, - 10.0 + 8.8 ], "texture": "#track_b" }, @@ -319,7 +462,7 @@ 0, 0, 16, - 10.0 + 8.8 ], "texture": "#track_b" }, @@ -328,7 +471,7 @@ 0, 0, 16, - 10.0 + 8.8 ], "texture": "#track_b" }, @@ -360,113 +503,2153 @@ ], "to": [ 30.0, + 8.8, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -11.82 + ], + "to": [ + -14.02, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + -11.82 + ], + "to": [ + 30.08, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -12.8 + ], + "to": [ + -14.02, + 6.05, + -7.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -11.82 + ], + "to": [ + 30.08, + 7.8, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -5.82 + ], + "to": [ + -14.02, + 2.55, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -6.8 + ], + "to": [ + -14.02, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -6.8 + ], + "to": [ + 30.08, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + -5.82 + ], + "to": [ + -14.02, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -5.82 + ], + "to": [ + 30.08, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 0.18000000000000016 + ], + "to": [ + 30.08, + 2.55, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -0.7999999999999998 + ], + "to": [ + -14.02, + 6.05, + 4.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 0.18000000000000016 + ], + "to": [ + -14.02, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 0.18000000000000016 + ], + "to": [ + 30.08, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 6.18 + ], + "to": [ + -14.02, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 6.18 + ], + "to": [ + 30.08, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 5.2 + ], + "to": [ + -14.02, + 6.05, + 10.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 6.18 + ], + "to": [ + -14.02, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 6.18 + ], + "to": [ + 30.08, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 12.18 + ], + "to": [ + 30.08, + 2.55, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 11.2 + ], + "to": [ + 30.08, + 6.05, + 16.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 12.18 + ], + "to": [ + -14.02, + 7.8, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 18.18 + ], + "to": [ + -14.02, + 2.55, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 17.2 + ], + "to": [ + -14.02, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 17.2 + ], + "to": [ + 30.08, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 18.18 + ], + "to": [ + -14.02, + 7.8, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 24.18 + ], + "to": [ + 30.08, + 2.55, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 23.2 + ], + "to": [ + -14.02, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 23.2 + ], + "to": [ + 30.08, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 24.18 + ], + "to": [ + -14.02, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 24.18 + ], + "to": [ + 30.08, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.5, + 8.6, + -14.0 + ], + "to": [ + -10.0, 10.0, 31.0 ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 10.0 - ], - "texture": "#track_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 10.0 - ], - "texture": "#track_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#track_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#track_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - } - } - }, - { - "from": [ - -14.5, - 6.0, - -14.0 - ], - "to": [ - -10.0, - 11.0, - 31.0 - ], "faces": { "north": { "uv": [ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -475,7 +2658,7 @@ 4.5, 16 ], - "texture": "#flecktarn_s13_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -484,19 +2667,19 @@ 4.5, 16 ], - "texture": "#flecktarn_s13_b" + "texture": "#pilot_nato_b" } } }, { "from": [ 26.0, - 6.0, + 8.6, -14.0 ], "to": [ 30.5, - 11.0, + 10.0, 31.0 ], "faces": { @@ -505,36 +2688,36 @@ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 4.5, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 1.4000000000000004 ], - "texture": "#flecktarn_s31_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -543,7 +2726,7 @@ 4.5, 16 ], - "texture": "#flecktarn_s13_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -552,9 +2735,77 @@ 4.5, 16 ], - "texture": "#flecktarn_s13_b" + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 24.0 + ], + "to": [ + 22.0, + 14.8, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/gepard_turret.json b/airdef-mod/assets/airdef/models/block/gepard_turret.json index 811d793..8a11834 100644 --- a/airdef-mod/assets/airdef/models/block/gepard_turret.json +++ b/airdef-mod/assets/airdef/models/block/gepard_turret.json @@ -1,25 +1,23 @@ { "ambientocclusion": false, "textures": { - "flecktarn_s21": "airdef:block/flecktarn_s21", - "flecktarn": "airdef:block/flecktarn", + "pilot_nato": "airdef:block/pilot_nato", "radar_face": "airdef:block/radar_face", "metal": "airdef:block/metal", - "mk_de": "airdef:block/mk_de", - "bare_antenna": "airdef:block/bare_antenna", "dark_metal": "airdef:block/dark_metal", - "particle": "airdef:block/flecktarn_s21" + "mk_de": "airdef:block/mk_de", + "particle": "airdef:block/pilot_nato" }, "elements": [ { "from": [ -4.0, 0.0, - -6.0 + -5.0 ], "to": [ 20.0, - 12.0, + 5.0, 16.0 ], "faces": { @@ -28,36 +26,36 @@ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -66,7 +64,7 @@ 16, 16 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -75,7 +73,75 @@ 16, 16 ], - "texture": "#flecktarn_s21" + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -2.5, + 5.0, + -5.0 + ], + "to": [ + 18.5, + 12.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" } } }, @@ -87,7 +153,7 @@ ], "to": [ 18.0, - 14.0, + 13.0, 14.0 ], "faces": { @@ -96,36 +162,36 @@ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -134,7 +200,7 @@ 16, 16 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -143,7 +209,7 @@ 16, 16 ], - "texture": "#flecktarn" + "texture": "#pilot_nato" } } }, @@ -218,7 +284,7 @@ { "from": [ 7.0, - 14.0, + 13.0, 10.0 ], "to": [ @@ -232,7 +298,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal" }, @@ -241,7 +307,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal" }, @@ -250,7 +316,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal" }, @@ -259,7 +325,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal" }, @@ -285,150 +351,168 @@ }, { "from": [ - 20.0, - 3.0, - -5.5 + -4.7, + 4.8, + -4.0 ], "to": [ - 20.119999999999997, - 6.5, - 1.5 + -2.5, + 10.7, + 14.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.2, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.2, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "up": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.2, + 16 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "down": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.2, + 16 ], - "texture": "#mk_de" + "texture": "#pilot_nato" } + }, + "rotation": { + "angle": 22.5, + "axis": "z", + "origin": [ + -3.6, + 7.75, + 5.0 + ] } }, { "from": [ - -4.119999999999997, - 3.0, - -5.5 + 18.5, + 4.8, + -4.0 ], "to": [ - -4.0, - 6.5, - 1.5 + 20.7, + 10.7, + 14.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.1999999999999993, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.1999999999999993, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "up": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.1999999999999993, + 16 ], - "texture": "#mk_de" + "texture": "#pilot_nato" }, "down": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.1999999999999993, + 16 ], - "texture": "#mk_de" + "texture": "#pilot_nato" } + }, + "rotation": { + "angle": -22.5, + "axis": "z", + "origin": [ + 19.6, + 7.75, + 5.0 + ] } }, { "from": [ - 5.5, - 14.0, + 0.0, + 13.05, 2.0 ], "to": [ - 10.5, - 14.8, - 7.0 + 5.0, + 13.7, + 8.0 ], "faces": { "north": { @@ -436,143 +520,75 @@ 0, 0, 5.0, - 0.8000000000000007 + 0.6499999999999986 ], - "texture": "#metal" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, 5.0, - 0.8000000000000007 + 0.6499999999999986 ], - "texture": "#metal" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 5.0, - 0.8000000000000007 + 6.0, + 0.6499999999999986 ], - "texture": "#metal" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 5.0, - 0.8000000000000007 + 6.0, + 0.6499999999999986 ], - "texture": "#metal" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, 5.0, - 5.0 + 6.0 ], - "texture": "#metal" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, 5.0, - 5.0 + 6.0 ], - "texture": "#metal" + "texture": "#dark_metal" } } }, { "from": [ - 2.0, - 14.0, - 5.0 - ], - "to": [ - 3.0, - 16.0, - 6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_antenna" - }, - "south": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_antenna" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_antenna" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_antenna" - }, - "up": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna" - }, - "down": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - -4.800000000000001, - 7.0, - -5.0 - ], - "to": [ - -4.0, 10.0, - -2.0 + 13.05, + 2.0 + ], + "to": [ + 15.0, + 13.7, + 8.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 0.6499999999999986 ], "texture": "#dark_metal" }, @@ -580,8 +596,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 0.6499999999999986 ], "texture": "#dark_metal" }, @@ -589,8 +605,8 @@ "uv": [ 0, 0, - 3.0, - 3.0 + 6.0, + 0.6499999999999986 ], "texture": "#dark_metal" }, @@ -598,8 +614,8 @@ "uv": [ 0, 0, - 3.0, - 3.0 + 6.0, + 0.6499999999999986 ], "texture": "#dark_metal" }, @@ -607,8 +623,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 6.0 ], "texture": "#dark_metal" }, @@ -616,8 +632,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 6.0 ], "texture": "#dark_metal" } @@ -625,207 +641,139 @@ }, { "from": [ - 20.0, - 7.0, - -5.0 + 18.5, + 6.0, + -4.0 ], "to": [ - 20.8, - 10.0, - -2.0 + 18.619999999999997, + 9.0, + 2.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.8000000000000007, + 0.25, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "south": { "uv": [ 0, 0, - 0.8000000000000007, + 0.25, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "east": { "uv": [ 0, 0, - 3.0, + 6.0, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "west": { "uv": [ 0, 0, - 3.0, + 6.0, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "up": { "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 0.25, + 6.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "down": { "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 0.25, + 6.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" } } }, { "from": [ - -4.800000000000001, - 7.0, - -1.5 + -2.6199999999999974, + 6.0, + -4.0 ], "to": [ - -4.0, - 10.0, - 0.5 + -2.5, + 9.0, + 2.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.8000000000000007, + 0.25, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "south": { "uv": [ 0, 0, - 0.8000000000000007, + 0.25, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "east": { "uv": [ 0, 0, - 2.0, + 6.0, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "west": { "uv": [ 0, 0, - 2.0, + 6.0, 3.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "up": { "uv": [ 0, 0, - 0.8000000000000007, - 2.0 + 0.25, + 6.0 ], - "texture": "#dark_metal" + "texture": "#mk_de" }, "down": { "uv": [ 0, 0, - 0.8000000000000007, - 2.0 + 0.25, + 6.0 ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 20.0, - 7.0, - -1.5 - ], - "to": [ - 20.8, - 10.0, - 0.5 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 3.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 3.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 2.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 2.0 - ], - "texture": "#dark_metal" + "texture": "#mk_de" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/gepard_turret_burnt.json b/airdef-mod/assets/airdef/models/block/gepard_turret_burnt.json index d677618..cb3613b 100644 --- a/airdef-mod/assets/airdef/models/block/gepard_turret_burnt.json +++ b/airdef-mod/assets/airdef/models/block/gepard_turret_burnt.json @@ -1,25 +1,23 @@ { "ambientocclusion": false, "textures": { - "flecktarn_s21_b": "airdef:block/flecktarn_s21_b", - "flecktarn_b": "airdef:block/flecktarn_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", "radar_face_b": "airdef:block/radar_face_b", "metal_b": "airdef:block/metal_b", - "mk_de_b": "airdef:block/mk_de_b", - "bare_antenna_b": "airdef:block/bare_antenna_b", "dark_metal_b": "airdef:block/dark_metal_b", - "particle": "airdef:block/flecktarn_s21_b" + "mk_de_b": "airdef:block/mk_de_b", + "particle": "airdef:block/pilot_nato_b" }, "elements": [ { "from": [ -4.0, 0.0, - -6.0 + -5.0 ], "to": [ 20.0, - 12.0, + 5.0, 16.0 ], "faces": { @@ -28,36 +26,36 @@ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 12.0 + 5.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -66,7 +64,7 @@ 16, 16 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -75,7 +73,75 @@ 16, 16 ], - "texture": "#flecktarn_s21_b" + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -2.5, + 5.0, + -5.0 + ], + "to": [ + 18.5, + 12.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" } } }, @@ -87,7 +153,7 @@ ], "to": [ 18.0, - 14.0, + 13.0, 14.0 ], "faces": { @@ -96,36 +162,36 @@ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 2.0 + 1.0 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -134,7 +200,7 @@ 16, 16 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -143,7 +209,7 @@ 16, 16 ], - "texture": "#flecktarn_b" + "texture": "#pilot_nato_b" } } }, @@ -218,7 +284,7 @@ { "from": [ 7.0, - 14.0, + 13.0, 10.0 ], "to": [ @@ -232,7 +298,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal_b" }, @@ -241,7 +307,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal_b" }, @@ -250,7 +316,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal_b" }, @@ -259,7 +325,7 @@ 0, 0, 2.0, - 4.0 + 5.0 ], "texture": "#metal_b" }, @@ -285,226 +351,244 @@ }, { "from": [ - 20.0, - 3.0, - -5.5 + -4.7, + 4.8, + -4.0 ], "to": [ - 20.119999999999997, - 6.5, - 1.5 + -2.5, + 10.7, + 14.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.2, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.2, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.2, + 16 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.2, + 16 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" } + }, + "rotation": { + "angle": 22.5, + "axis": "z", + "origin": [ + -3.6, + 7.75, + 5.0 + ] } }, { "from": [ - -4.119999999999997, - 3.0, - -5.5 + 18.5, + 4.8, + -4.0 ], "to": [ - -4.0, - 6.5, - 1.5 + 20.7, + 10.7, + 14.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.1999999999999993, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, - 0.25, - 3.5 + 2.1999999999999993, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, - 7.0, - 3.5 + 16, + 5.8999999999999995 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.1999999999999993, + 16 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ 0, 0, - 0.25, - 7.0 + 2.1999999999999993, + 16 ], - "texture": "#mk_de_b" + "texture": "#pilot_nato_b" } + }, + "rotation": { + "angle": -22.5, + "axis": "z", + "origin": [ + 19.6, + 7.75, + 5.0 + ] } }, { "from": [ - 2.0, - 14.0, - 5.0 + 0.0, + 13.05, + 2.0 ], "to": [ - 3.0, - 16.0, - 6.0 + 5.0, + 13.7, + 8.0 ], "faces": { "north": { "uv": [ 0, 0, - 1.0, - 2.0 + 5.0, + 0.6499999999999986 ], - "texture": "#bare_antenna_b" + "texture": "#dark_metal_b" }, "south": { "uv": [ 0, 0, - 1.0, - 2.0 + 5.0, + 0.6499999999999986 ], - "texture": "#bare_antenna_b" + "texture": "#dark_metal_b" }, "east": { "uv": [ 0, 0, - 1.0, - 2.0 + 6.0, + 0.6499999999999986 ], - "texture": "#bare_antenna_b" + "texture": "#dark_metal_b" }, "west": { "uv": [ 0, 0, - 1.0, - 2.0 + 6.0, + 0.6499999999999986 ], - "texture": "#bare_antenna_b" + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, - 1.0, - 1.0 + 5.0, + 6.0 ], - "texture": "#bare_antenna_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, - 1.0, - 1.0 + 5.0, + 6.0 ], - "texture": "#bare_antenna_b" + "texture": "#dark_metal_b" } } }, { "from": [ - -4.800000000000001, - 7.0, - -5.0 - ], - "to": [ - -4.0, 10.0, - -2.0 + 13.05, + 2.0 + ], + "to": [ + 15.0, + 13.7, + 8.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 0.6499999999999986 ], "texture": "#dark_metal_b" }, @@ -512,8 +596,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 0.6499999999999986 ], "texture": "#dark_metal_b" }, @@ -521,8 +605,8 @@ "uv": [ 0, 0, - 3.0, - 3.0 + 6.0, + 0.6499999999999986 ], "texture": "#dark_metal_b" }, @@ -530,8 +614,8 @@ "uv": [ 0, 0, - 3.0, - 3.0 + 6.0, + 0.6499999999999986 ], "texture": "#dark_metal_b" }, @@ -539,8 +623,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 6.0 ], "texture": "#dark_metal_b" }, @@ -548,8 +632,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 5.0, + 6.0 ], "texture": "#dark_metal_b" } @@ -557,139 +641,71 @@ }, { "from": [ - 20.0, - 7.0, - -5.0 + -2.6199999999999974, + 6.0, + -4.0 ], "to": [ - 20.8, - 10.0, - -2.0 + -2.5, + 9.0, + 2.0 ], "faces": { "north": { "uv": [ 0, 0, - 0.8000000000000007, + 0.25, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#mk_de_b" }, "south": { "uv": [ 0, 0, - 0.8000000000000007, + 0.25, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#mk_de_b" }, "east": { "uv": [ 0, 0, - 3.0, + 6.0, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#mk_de_b" }, "west": { "uv": [ 0, 0, - 3.0, + 6.0, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#mk_de_b" }, "up": { "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 0.25, + 6.0 ], - "texture": "#dark_metal_b" + "texture": "#mk_de_b" }, "down": { "uv": [ 0, 0, - 0.8000000000000007, - 3.0 + 0.25, + 6.0 ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -4.800000000000001, - 7.0, - -1.5 - ], - "to": [ - -4.0, - 10.0, - 0.5 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.8000000000000007, - 2.0 - ], - "texture": "#dark_metal_b" + "texture": "#mk_de_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/irist_chassis.json b/airdef-mod/assets/airdef/models/block/irist_chassis.json index ae5f6a8..327ac05 100644 --- a/airdef-mod/assets/airdef/models/block/irist_chassis.json +++ b/airdef-mod/assets/airdef/models/block/irist_chassis.json @@ -1,30 +1,25 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21": "airdef:block/nato_green_s21", - "nato_green_s31": "airdef:block/nato_green_s31", - "nato_green_s23": "airdef:block/nato_green_s23", + "pilot_nato": "airdef:block/pilot_nato", "glass_cab": "airdef:block/glass_cab", - "nato_green": "airdef:block/nato_green", "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", "metal": "airdef:block/metal", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "bare_lamp": "airdef:block/bare_lamp", - "bare_antenna": "airdef:block/bare_antenna", - "mk_ru": "airdef:block/mk_ru", - "particle": "airdef:block/nato_green_s21" + "particle": "airdef:block/pilot_nato" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, - 10.0, + 21.0, + 9.0, 32.0 ], "faces": { @@ -33,36 +28,36 @@ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s31" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s31" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -71,7 +66,7 @@ 16, 16 ], - "texture": "#nato_green_s23" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -80,224 +75,20 @@ 16, 16 ], - "texture": "#nato_green_s23" - } - } - }, - { - "from": [ - -6.0, - 10.0, - -16.0 - ], - "to": [ - 3.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - } - } - }, - { - "from": [ - 13.0, - 10.0, - -16.0 - ], - "to": [ - 22.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green" - } - } - }, - { - "from": [ - 3.0, - 10.0, - -16.0 - ], - "to": [ - 13.0, - 15.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal" + "texture": "#pilot_nato" } } }, { "from": [ -5.0, - 10.0, - -6.0 + 8.0, + -16.0 ], "to": [ 21.0, - 13.0, - 4.0 + 16.0, + -6.0 ], "faces": { "north": { @@ -305,36 +96,36 @@ 0, 0, 16, - 3.0 + 8.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 3.0 + 8.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 10.0, - 3.0 + 8.0 ], - "texture": "#nato_green" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 10.0, - 3.0 + 8.0 ], - "texture": "#nato_green" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -343,7 +134,7 @@ 16, 10.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -352,1657 +143,633 @@ 16, 10.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.5 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 11.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" } } }, { "from": [ -3.0, - 10.0, - 26.0 + 11.0, + -4.0 ], "to": [ 19.0, - 14.0, - 32.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -8.0, - 0.0, + 13.0, 28.0 ], - "to": [ - -6.0, - 6.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - 22.0, - 0.0, - 28.0 - ], - "to": [ - 24.0, - 6.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.5, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 25.0, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.53, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 24.97, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.5, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 25.0, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.53, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 24.97, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.5, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 25.0, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.53, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 24.97, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - -4.5, - 11.0, - -16.0 - ], - "to": [ - -2.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 11.0, - -16.0 - ], - "to": [ - 20.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - -4.5, - 14.5, - -16.0 - ], - "to": [ - -2.5, - 15.5, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 14.5, - -16.0 - ], - "to": [ - 20.5, - 15.5, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - -1.0, - 5.5, - -16.0 - ], - "to": [ - 17.0, - 7.5, - -15.0 - ], "faces": { "north": { "uv": [ @@ -2026,7 +793,7 @@ "uv": [ 0, 0, - 1.0, + 16, 2.0 ], "texture": "#dark_metal" @@ -2035,7 +802,7 @@ "uv": [ 0, 0, - 1.0, + 16, 2.0 ], "texture": "#dark_metal" @@ -2045,7 +812,7 @@ 0, 0, 16, - 1.0 + 16 ], "texture": "#dark_metal" }, @@ -2054,7 +821,7 @@ 0, 0, 16, - 1.0 + 16 ], "texture": "#dark_metal" } @@ -2062,81 +829,13 @@ }, { "from": [ - -10.5, - 7.0, - 4.0 - ], - "to": [ -7.0, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 23.0, - 7.0, + 6.0, 4.0 ], "to": [ - 26.5, - 10.0, + -5.0, + 9.0, 14.0 ], "faces": { @@ -2144,7 +843,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal" @@ -2153,7 +852,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal" @@ -2180,7 +879,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 10.0 ], "texture": "#dark_metal" @@ -2189,7 +888,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 10.0 ], "texture": "#dark_metal" @@ -2198,149 +897,489 @@ }, { "from": [ - -6.5, - 15.0, - -9.0 + 21.0, + 6.0, + 4.0 ], "to": [ - -5.5, - 22.0, - -8.0 + 23.0, + 9.0, + 14.0 ], "faces": { "north": { "uv": [ 0, 0, - 1.0, - 7.0 + 2.0, + 3.0 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, - 1.0, - 7.0 + 2.0, + 3.0 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 1.0, - 7.0 + 10.0, + 3.0 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 1.0, - 7.0 + 10.0, + 3.0 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, - 1.0, - 1.0 + 2.0, + 10.0 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, - 1.0, - 1.0 + 2.0, + 10.0 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 25.0 + ], + "to": [ + -5.0, + 6.0, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 25.0 + ], + "to": [ + 23.0, + 6.0, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" } } }, { "from": [ 21.5, - 15.0, - -9.0 + 0.0, + -13.4 ], "to": [ - 22.5, - 22.0, - -8.0 + 24.5, + 1.6, + -8.6 ], "faces": { "north": { "uv": [ 0, 0, - 1.0, - 7.0 + 3.0, + 1.6 ], - "texture": "#bare_antenna" + "texture": "#tire" }, "south": { "uv": [ 0, 0, - 1.0, - 7.0 + 3.0, + 1.6 ], - "texture": "#bare_antenna" + "texture": "#tire" }, "east": { "uv": [ 0, 0, - 1.0, - 7.0 + 4.800000000000001, + 1.6 ], - "texture": "#bare_antenna" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 1.0, - 7.0 + 4.800000000000001, + 1.6 ], - "texture": "#bare_antenna" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 1.0, - 1.0 + 3.0, + 4.800000000000001 ], - "texture": "#bare_antenna" + "texture": "#tire" }, "down": { "uv": [ 0, 0, - 1.0, - 1.0 + 3.0, + 4.800000000000001 ], - "texture": "#bare_antenna" + "texture": "#tire" } } }, { "from": [ - 22.0, - 11.0, + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, -15.0 ], "to": [ - 22.119999999999997, - 15.0, + -5.5, + 4.8, -7.0 ], "faces": { @@ -2348,67 +1387,67 @@ "uv": [ 0, 0, - 0.25, - 4.0 + 3.0, + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "south": { "uv": [ 0, 0, - 0.25, - 4.0 + 3.0, + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "east": { "uv": [ 0, 0, 8.0, - 4.0 + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "west": { "uv": [ 0, 0, 8.0, - 4.0 + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 0.25, + 3.0, 8.0 ], - "texture": "#mk_ru" + "texture": "#tire" }, "down": { "uv": [ 0, 0, - 0.25, + 3.0, 8.0 ], - "texture": "#mk_ru" + "texture": "#tire" } } }, { "from": [ - -6.119999999999997, - 11.0, + 21.5, + 3.2, -15.0 ], "to": [ - -6.0, - 15.0, + 24.5, + 4.8, -7.0 ], "faces": { @@ -2416,57 +1455,2913 @@ "uv": [ 0, 0, - 0.25, - 4.0 + 3.0, + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "south": { "uv": [ 0, 0, - 0.25, - 4.0 + 3.0, + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "east": { "uv": [ 0, 0, 8.0, - 4.0 + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" }, "west": { "uv": [ 0, 0, 8.0, - 4.0 + 1.5999999999999996 ], - "texture": "#mk_ru" + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" }, "up": { "uv": [ 0, 0, 0.25, - 8.0 + 3.6000000000000014 ], - "texture": "#mk_ru" + "texture": "#hub" }, "down": { "uv": [ 0, 0, 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, 8.0 ], - "texture": "#mk_ru" + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/irist_chassis_burnt.json b/airdef-mod/assets/airdef/models/block/irist_chassis_burnt.json index f9c7f7c..7afe773 100644 --- a/airdef-mod/assets/airdef/models/block/irist_chassis_burnt.json +++ b/airdef-mod/assets/airdef/models/block/irist_chassis_burnt.json @@ -1,30 +1,25 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21_b": "airdef:block/nato_green_s21_b", - "nato_green_s31_b": "airdef:block/nato_green_s31_b", - "nato_green_s23_b": "airdef:block/nato_green_s23_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", "glass_cab_b": "airdef:block/glass_cab_b", - "nato_green_b": "airdef:block/nato_green_b", "dark_metal_b": "airdef:block/dark_metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", "metal_b": "airdef:block/metal_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "bare_antenna_b": "airdef:block/bare_antenna_b", - "mk_ru_b": "airdef:block/mk_ru_b", - "particle": "airdef:block/nato_green_s21_b" + "particle": "airdef:block/pilot_nato_b" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, - 10.0, + 21.0, + 9.0, 32.0 ], "faces": { @@ -33,36 +28,36 @@ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s31_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#nato_green_s31_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -71,7 +66,7 @@ 16, 16 ], - "texture": "#nato_green_s23_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -80,224 +75,20 @@ 16, 16 ], - "texture": "#nato_green_s23_b" - } - } - }, - { - "from": [ - -6.0, - 10.0, - -16.0 - ], - "to": [ - 3.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab_b" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - } - } - }, - { - "from": [ - 13.0, - 10.0, - -16.0 - ], - "to": [ - 22.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab_b" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#nato_green_b" - } - } - }, - { - "from": [ - 3.0, - 10.0, - -16.0 - ], - "to": [ - 13.0, - 15.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" } } }, { "from": [ -5.0, - 10.0, - -6.0 + 8.0, + -16.0 ], "to": [ 21.0, - 13.0, - 4.0 + 16.0, + -6.0 ], "faces": { "north": { @@ -305,36 +96,36 @@ 0, 0, 16, - 3.0 + 8.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 3.0 + 8.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 10.0, - 3.0 + 8.0 ], - "texture": "#nato_green_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 10.0, - 3.0 + 8.0 ], - "texture": "#nato_green_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -343,7 +134,7 @@ 16, 10.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -352,1791 +143,155 @@ 16, 10.0 ], - "texture": "#nato_green_s21_b" - } - } - }, - { - "from": [ - -3.0, - 10.0, - 26.0 - ], - "to": [ - 19.0, - 14.0, - 32.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -8.0, - 0.0, - 28.0 - ], - "to": [ - -6.0, - 6.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal_b" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.5, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 25.0, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.53, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 24.97, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.5, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 25.0, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.53, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 24.97, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.5, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 25.0, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.53, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 24.97, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - -4.5, - 11.0, - -16.0 - ], - "to": [ - -2.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - 18.5, - 11.0, - -16.0 - ], - "to": [ - 20.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" } } }, { "from": [ -4.5, - 14.5, - -16.0 - ], - "to": [ - -2.5, - 15.5, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - 18.5, - 14.5, - -16.0 + 16.0, + -15.5 ], "to": [ 20.5, - 15.5, - -15.6 + 20.0, + -6.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.0 + 16, + 4.0 ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, - 2.0, - 1.0 + 16, + 4.0 ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, - 0.40000000000000036, - 1.0 + 9.5, + 4.0 ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, - 0.40000000000000036, - 1.0 + 9.5, + 4.0 ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 16, + 9.5 ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 16, + 9.5 ], - "texture": "#bare_lamp_b" + "texture": "#pilot_nato_b" } } }, { "from": [ - -1.0, - 5.5, - -16.0 + -4.0, + 16.0, + -15.99 ], "to": [ - 17.0, 7.5, - -15.0 + 19.3, + -15.82 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 2.0 + 11.5, + 3.3000000000000007 ], - "texture": "#dark_metal_b" + "texture": "#glass_cab_b" }, "south": { "uv": [ 0, 0, - 16, - 2.0 + 11.5, + 3.3000000000000007 ], - "texture": "#dark_metal_b" + "texture": "#glass_cab_b" }, "east": { "uv": [ 0, 0, - 1.0, - 2.0 + 0.25, + 3.3000000000000007 ], - "texture": "#dark_metal_b" + "texture": "#glass_cab_b" }, "west": { "uv": [ 0, 0, - 1.0, - 2.0 + 0.25, + 3.3000000000000007 ], - "texture": "#dark_metal_b" + "texture": "#glass_cab_b" }, "up": { "uv": [ 0, 0, - 16, - 1.0 + 11.5, + 0.25 ], - "texture": "#dark_metal_b" + "texture": "#glass_cab_b" }, "down": { "uv": [ 0, 0, - 16, - 1.0 + 11.5, + 0.25 ], - "texture": "#dark_metal_b" + "texture": "#glass_cab_b" } } }, { "from": [ - -10.5, - 7.0, - 4.0 + -5.08, + 16.0, + -14.5 ], "to": [ - -7.0, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - 23.0, - 7.0, - 4.0 - ], - "to": [ - 26.5, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -6.5, - 15.0, - -9.0 - ], - "to": [ - -5.5, - 22.0, + -4.99, + 19.0, -8.0 ], "faces": { @@ -2144,68 +299,68 @@ "uv": [ 0, 0, - 1.0, - 7.0 + 0.25, + 3.0 ], - "texture": "#bare_antenna_b" + "texture": "#glass_cab_b" }, "south": { "uv": [ 0, 0, - 1.0, - 7.0 + 0.25, + 3.0 ], - "texture": "#bare_antenna_b" + "texture": "#glass_cab_b" }, "east": { "uv": [ 0, 0, - 1.0, - 7.0 + 6.5, + 3.0 ], - "texture": "#bare_antenna_b" + "texture": "#glass_cab_b" }, "west": { "uv": [ 0, 0, - 1.0, - 7.0 + 6.5, + 3.0 ], - "texture": "#bare_antenna_b" + "texture": "#glass_cab_b" }, "up": { "uv": [ 0, 0, - 1.0, - 1.0 + 0.25, + 6.5 ], - "texture": "#bare_antenna_b" + "texture": "#glass_cab_b" }, "down": { "uv": [ 0, 0, - 1.0, - 1.0 + 0.25, + 6.5 ], - "texture": "#bare_antenna_b" + "texture": "#glass_cab_b" } } }, { "from": [ - 22.0, - 11.0, - -15.0 + 20.990000000000002, + 16.0, + -14.5 ], "to": [ - 22.119999999999997, - 15.0, - -7.0 + 21.08, + 19.0, + -8.0 ], "faces": { "north": { @@ -2213,66 +368,882 @@ 0, 0, 0.25, - 4.0 + 3.0 ], - "texture": "#mk_ru_b" + "texture": "#glass_cab_b" }, "south": { "uv": [ 0, 0, 0.25, - 4.0 + 3.0 ], - "texture": "#mk_ru_b" + "texture": "#glass_cab_b" }, "east": { "uv": [ 0, 0, - 8.0, - 4.0 + 6.5, + 3.0 ], - "texture": "#mk_ru_b" + "texture": "#glass_cab_b" }, "west": { "uv": [ 0, 0, - 8.0, - 4.0 + 6.5, + 3.0 ], - "texture": "#mk_ru_b" + "texture": "#glass_cab_b" }, "up": { "uv": [ 0, 0, 0.25, - 8.0 + 6.5 ], - "texture": "#mk_ru_b" + "texture": "#glass_cab_b" }, "down": { "uv": [ 0, 0, 0.25, - 8.0 + 6.5 ], - "texture": "#mk_ru_b" + "texture": "#glass_cab_b" } } }, { "from": [ - -6.119999999999997, + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, 11.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -3.0, + 11.0, + -4.0 + ], + "to": [ + 19.0, + 13.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -7.0, + 6.0, + 4.0 + ], + "to": [ + -5.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 21.0, + 6.0, + 4.0 + ], + "to": [ + 23.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 25.0 + ], + "to": [ + -5.0, + 6.0, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, -15.0 ], "to": [ - -6.0, - 15.0, + -5.5, + 4.8, -7.0 ], "faces": { @@ -2280,57 +1251,2165 @@ "uv": [ 0, 0, - 0.25, - 4.0 + 3.0, + 1.5999999999999996 ], - "texture": "#mk_ru_b" + "texture": "#tire_b" }, "south": { "uv": [ 0, 0, - 0.25, - 4.0 + 3.0, + 1.5999999999999996 ], - "texture": "#mk_ru_b" + "texture": "#tire_b" }, "east": { "uv": [ 0, 0, 8.0, - 4.0 + 1.5999999999999996 ], - "texture": "#mk_ru_b" + "texture": "#tire_b" }, "west": { "uv": [ 0, 0, 8.0, - 4.0 + 1.5999999999999996 ], - "texture": "#mk_ru_b" + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" }, "up": { "uv": [ 0, 0, 0.25, - 8.0 + 3.6000000000000014 ], - "texture": "#mk_ru_b" + "texture": "#hub_b" }, "down": { "uv": [ 0, 0, 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, 8.0 ], - "texture": "#mk_ru_b" + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/irist_tubes.json b/airdef-mod/assets/airdef/models/block/irist_tubes.json index 77aa982..a54c4e3 100644 --- a/airdef-mod/assets/airdef/models/block/irist_tubes.json +++ b/airdef-mod/assets/airdef/models/block/irist_tubes.json @@ -686,6 +686,142 @@ "texture": "#dark_metal" } } + }, + { + "from": [ + -1.9, + 0.0, + -2.0 + ], + "to": [ + 17.9, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -1.9, + 0.0, + 22.0 + ], + "to": [ + 17.9, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/irist_tubes_burnt.json b/airdef-mod/assets/airdef/models/block/irist_tubes_burnt.json index a08e2d7..4a342e3 100644 --- a/airdef-mod/assets/airdef/models/block/irist_tubes_burnt.json +++ b/airdef-mod/assets/airdef/models/block/irist_tubes_burnt.json @@ -686,6 +686,142 @@ "texture": "#dark_metal_b" } } + }, + { + "from": [ + -1.9, + 0.0, + -2.0 + ], + "to": [ + 17.9, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -1.9, + 0.0, + 22.0 + ], + "to": [ + 17.9, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/kh101.json b/airdef-mod/assets/airdef/models/block/kh101.json index af9476e..dd9f8f6 100644 --- a/airdef-mod/assets/airdef/models/block/kh101.json +++ b/airdef-mod/assets/airdef/models/block/kh101.json @@ -741,8 +741,8 @@ 2.0 ], "to": [ - 22.0, - 7.6, + 13.55, + 7.5, 11.0 ], "faces": { @@ -750,8 +750,8 @@ "uv": [ 0, 0, - 11.5, - 0.6999999999999993 + 3.0500000000000007, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -759,8 +759,8 @@ "uv": [ 0, 0, - 11.5, - 0.6999999999999993 + 3.0500000000000007, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -769,7 +769,7 @@ 0, 0, 9.0, - 0.6999999999999993 + 0.5999999999999996 ], "texture": "#kh101" }, @@ -778,7 +778,7 @@ 0, 0, 9.0, - 0.6999999999999993 + 0.5999999999999996 ], "texture": "#kh101" }, @@ -786,7 +786,7 @@ "uv": [ 0, 0, - 11.5, + 3.0500000000000007, 9.0 ], "texture": "#kh101" @@ -795,7 +795,7 @@ "uv": [ 0, 0, - 11.5, + 3.0500000000000007, 9.0 ], "texture": "#kh101_belly" @@ -804,13 +804,13 @@ }, { "from": [ - -6.0, + 2.4499999999999993, 6.9, 2.0 ], "to": [ 5.5, - 7.6, + 7.5, 11.0 ], "faces": { @@ -818,8 +818,8 @@ "uv": [ 0, 0, - 11.5, - 0.6999999999999993 + 3.0500000000000007, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -827,8 +827,8 @@ "uv": [ 0, 0, - 11.5, - 0.6999999999999993 + 3.0500000000000007, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -837,7 +837,7 @@ 0, 0, 9.0, - 0.6999999999999993 + 0.5999999999999996 ], "texture": "#kh101" }, @@ -846,7 +846,7 @@ 0, 0, 9.0, - 0.6999999999999993 + 0.5999999999999996 ], "texture": "#kh101" }, @@ -854,7 +854,7 @@ "uv": [ 0, 0, - 11.5, + 3.0500000000000007, 9.0 ], "texture": "#kh101" @@ -863,7 +863,7 @@ "uv": [ 0, 0, - 11.5, + 3.0500000000000007, 9.0 ], "texture": "#kh101_belly" @@ -872,22 +872,22 @@ }, { "from": [ - 22.0, + 13.5, 6.9, - 5.0 + 2.65 ], "to": [ - 28.5, - 7.4, - 11.0 + 16.55, + 7.5, + 10.88 ], "faces": { "north": { "uv": [ 0, 0, - 6.5, - 0.5 + 3.0500000000000007, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -895,8 +895,8 @@ "uv": [ 0, 0, - 6.5, - 0.5 + 3.0500000000000007, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -904,8 +904,8 @@ "uv": [ 0, 0, - 6.0, - 0.5 + 8.23, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -913,8 +913,8 @@ "uv": [ 0, 0, - 6.0, - 0.5 + 8.23, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -922,8 +922,8 @@ "uv": [ 0, 0, - 6.5, - 6.0 + 3.0500000000000007, + 8.23 ], "texture": "#kh101" }, @@ -931,8 +931,552 @@ "uv": [ 0, 0, - 6.5, - 6.0 + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -0.5500000000000007, + 6.9, + 2.65 + ], + "to": [ + 2.5, + 7.5, + 10.88 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 8.23, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 8.23, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 16.5, + 6.9, + 3.3 + ], + "to": [ + 19.55, + 7.5, + 10.76 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -3.5500000000000007, + 6.9, + 3.3 + ], + "to": [ + -0.5, + 7.5, + 10.76 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 19.5, + 6.9, + 3.95 + ], + "to": [ + 22.55, + 7.5, + 10.64 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -6.550000000000001, + 6.9, + 3.95 + ], + "to": [ + -3.5, + 7.5, + 10.64 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 22.5, + 6.9, + 4.6 + ], + "to": [ + 25.55, + 7.5, + 10.52 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -9.55, + 6.9, + 4.6 + ], + "to": [ + -6.5, + 7.5, + 10.52 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 25.5, + 6.9, + 5.25 + ], + "to": [ + 28.5, + 7.5, + 10.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.15, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.15, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 5.15 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 5.15 ], "texture": "#kh101_belly" } @@ -942,20 +1486,20 @@ "from": [ -12.5, 6.9, - 5.0 + 5.25 ], "to": [ - -6.0, - 7.4, - 11.0 + -9.5, + 7.5, + 10.4 ], "faces": { "north": { "uv": [ 0, 0, - 6.5, - 0.5 + 3.0, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -963,8 +1507,8 @@ "uv": [ 0, 0, - 6.5, - 0.5 + 3.0, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -972,8 +1516,8 @@ "uv": [ 0, 0, - 6.0, - 0.5 + 5.15, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -981,8 +1525,8 @@ "uv": [ 0, 0, - 6.0, - 0.5 + 5.15, + 0.5999999999999996 ], "texture": "#kh101" }, @@ -990,8 +1534,8 @@ "uv": [ 0, 0, - 6.5, - 6.0 + 3.0, + 5.15 ], "texture": "#kh101" }, @@ -999,8 +1543,8 @@ "uv": [ 0, 0, - 6.5, - 6.0 + 3.0, + 5.15 ], "texture": "#kh101_belly" } @@ -1551,4 +2095,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/lancet.json b/airdef-mod/assets/airdef/models/block/lancet.json index ef4ff16..88351de 100644 --- a/airdef-mod/assets/airdef/models/block/lancet.json +++ b/airdef-mod/assets/airdef/models/block/lancet.json @@ -10,22 +10,22 @@ "elements": [ { "from": [ - 7.170068602879533, - 7.170068602879533, + 7.291522781930396, + 7.291522781930396, -16.0 ], "to": [ - 8.829931397120468, - 8.829931397120468, - -14.0 + 8.708477218069604, + 8.708477218069604, + -14.5 ], "faces": { "north": { "uv": [ 0, 0, - 1.6598627942409356, - 1.6598627942409356 + 1.416954436139208, + 1.416954436139208 ], "texture": "#lancet" }, @@ -33,8 +33,8 @@ "uv": [ 0, 0, - 1.6598627942409356, - 1.6598627942409356 + 1.416954436139208, + 1.416954436139208 ], "texture": "#lancet" }, @@ -42,8 +42,8 @@ "uv": [ 0, 0, - 2.0, - 1.6598627942409356 + 1.5, + 1.416954436139208 ], "texture": "#lancet" }, @@ -51,8 +51,8 @@ "uv": [ 0, 0, - 2.0, - 1.6598627942409356 + 1.5, + 1.416954436139208 ], "texture": "#lancet" }, @@ -60,8 +60,8 @@ "uv": [ 0, 0, - 1.6598627942409356, - 2.0 + 1.416954436139208, + 1.5 ], "texture": "#lancet" }, @@ -69,8 +69,8 @@ "uv": [ 0, 0, - 1.6598627942409356, - 2.0 + 1.416954436139208, + 1.5 ], "texture": "#lancet" } @@ -78,22 +78,22 @@ }, { "from": [ - 7.170068602879533, - 7.170068602879533, + 7.291522781930396, + 7.291522781930396, -15.97 ], "to": [ - 8.829931397120468, - 8.829931397120468, - -14.03 + 8.708477218069604, + 8.708477218069604, + -14.53 ], "faces": { "north": { "uv": [ 0, 0, - 1.6598627942409356, - 1.6598627942409356 + 1.416954436139208, + 1.416954436139208 ], "texture": "#lancet" }, @@ -101,8 +101,8 @@ "uv": [ 0, 0, - 1.6598627942409356, - 1.6598627942409356 + 1.416954436139208, + 1.416954436139208 ], "texture": "#lancet" }, @@ -110,8 +110,8 @@ "uv": [ 0, 0, - 1.9400000000000013, - 1.6598627942409356 + 1.4400000000000013, + 1.416954436139208 ], "texture": "#lancet" }, @@ -119,8 +119,8 @@ "uv": [ 0, 0, - 1.9400000000000013, - 1.6598627942409356 + 1.4400000000000013, + 1.416954436139208 ], "texture": "#lancet" }, @@ -128,8 +128,8 @@ "uv": [ 0, 0, - 1.6598627942409356, - 1.9400000000000013 + 1.416954436139208, + 1.4400000000000013 ], "texture": "#lancet" }, @@ -137,8 +137,8 @@ "uv": [ 0, 0, - 1.6598627942409356, - 1.9400000000000013 + 1.416954436139208, + 1.4400000000000013 ], "texture": "#lancet" } @@ -149,28 +149,28 @@ "origin": [ 8.0, 8.0, - -15.0 + -15.25 ] } }, { "from": [ - 6.647973576320755, - 6.647973576320755, - -14.0 + 6.845832411295283, + 6.845832411295283, + -14.5 ], "to": [ - 9.352026423679245, - 9.352026423679245, - -12.0 + 9.154167588704718, + 9.154167588704718, + -13.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.70405284735849, - 2.70405284735849 + 2.308335177409435, + 2.308335177409435 ], "texture": "#lancet" }, @@ -178,8 +178,8 @@ "uv": [ 0, 0, - 2.70405284735849, - 2.70405284735849 + 2.308335177409435, + 2.308335177409435 ], "texture": "#lancet" }, @@ -187,8 +187,8 @@ "uv": [ 0, 0, - 2.0, - 2.70405284735849 + 1.5, + 2.308335177409435 ], "texture": "#lancet" }, @@ -196,8 +196,8 @@ "uv": [ 0, 0, - 2.0, - 2.70405284735849 + 1.5, + 2.308335177409435 ], "texture": "#lancet" }, @@ -205,8 +205,8 @@ "uv": [ 0, 0, - 2.70405284735849, - 2.0 + 2.308335177409435, + 1.5 ], "texture": "#lancet" }, @@ -214,8 +214,8 @@ "uv": [ 0, 0, - 2.70405284735849, - 2.0 + 2.308335177409435, + 1.5 ], "texture": "#lancet" } @@ -223,22 +223,22 @@ }, { "from": [ - 6.647973576320755, - 6.647973576320755, - -13.97 + 6.845832411295283, + 6.845832411295283, + -14.47 ], "to": [ - 9.352026423679245, - 9.352026423679245, - -12.03 + 9.154167588704718, + 9.154167588704718, + -13.03 ], "faces": { "north": { "uv": [ 0, 0, - 2.70405284735849, - 2.70405284735849 + 2.308335177409435, + 2.308335177409435 ], "texture": "#lancet" }, @@ -246,8 +246,8 @@ "uv": [ 0, 0, - 2.70405284735849, - 2.70405284735849 + 2.308335177409435, + 2.308335177409435 ], "texture": "#lancet" }, @@ -255,8 +255,8 @@ "uv": [ 0, 0, - 1.9400000000000013, - 2.70405284735849 + 1.4400000000000013, + 2.308335177409435 ], "texture": "#lancet" }, @@ -264,8 +264,8 @@ "uv": [ 0, 0, - 1.9400000000000013, - 2.70405284735849 + 1.4400000000000013, + 2.308335177409435 ], "texture": "#lancet" }, @@ -273,8 +273,8 @@ "uv": [ 0, 0, - 2.70405284735849, - 1.9400000000000013 + 2.308335177409435, + 1.4400000000000013 ], "texture": "#lancet" }, @@ -282,8 +282,8 @@ "uv": [ 0, 0, - 2.70405284735849, - 1.9400000000000013 + 2.308335177409435, + 1.4400000000000013 ], "texture": "#lancet" } @@ -294,19 +294,164 @@ "origin": [ 8.0, 8.0, - -13.0 + -13.75 ] } }, { "from": [ - 6.256233448150251, - 6.256233448150251, - -12.0 + 6.511420486194703, + 6.511420486194703, + -13.0 ], "to": [ - 9.743766551849749, - 9.743766551849749, + 9.488579513805297, + 9.488579513805297, + -11.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.5, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.5, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.5 + ], + "texture": "#lancet" + } + } + }, + { + "from": [ + 6.511420486194703, + 6.511420486194703, + -12.97 + ], + "to": [ + 9.488579513805297, + 9.488579513805297, + -11.53 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.4400000000000013 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.4400000000000013 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -12.25 + ] + } + }, + { + "from": [ + 6.229756877591252, + 6.229756877591252, + -11.5 + ], + "to": [ + 9.770243122408747, + 9.770243122408747, -10.0 ], "faces": { @@ -314,8 +459,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 3.487533103699498 + 3.5404862448174947, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -323,8 +468,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 3.487533103699498 + 3.5404862448174947, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -332,8 +477,8 @@ "uv": [ 0, 0, - 2.0, - 3.487533103699498 + 1.5, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -341,8 +486,8 @@ "uv": [ 0, 0, - 2.0, - 3.487533103699498 + 1.5, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -350,8 +495,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 2.0 + 3.5404862448174947, + 1.5 ], "texture": "#lancet" }, @@ -359,8 +504,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 2.0 + 3.5404862448174947, + 1.5 ], "texture": "#lancet" } @@ -368,13 +513,13 @@ }, { "from": [ - 6.256233448150251, - 6.256233448150251, - -11.97 + 6.229756877591252, + 6.229756877591252, + -11.47 ], "to": [ - 9.743766551849749, - 9.743766551849749, + 9.770243122408747, + 9.770243122408747, -10.03 ], "faces": { @@ -382,8 +527,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 3.487533103699498 + 3.5404862448174947, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -391,8 +536,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 3.487533103699498 + 3.5404862448174947, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -400,8 +545,8 @@ "uv": [ 0, 0, - 1.9400000000000013, - 3.487533103699498 + 1.4400000000000013, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -409,8 +554,8 @@ "uv": [ 0, 0, - 1.9400000000000013, - 3.487533103699498 + 1.4400000000000013, + 3.5404862448174947 ], "texture": "#lancet" }, @@ -418,8 +563,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 1.9400000000000013 + 3.5404862448174947, + 1.4400000000000013 ], "texture": "#lancet" }, @@ -427,8 +572,8 @@ "uv": [ 0, 0, - 3.487533103699498, - 1.9400000000000013 + 3.5404862448174947, + 1.4400000000000013 ], "texture": "#lancet" } @@ -439,7 +584,7 @@ "origin": [ 8.0, 8.0, - -11.0 + -10.75 ] } }, @@ -658,13 +803,149 @@ }, { "from": [ - -5.0, - 7.7, + 7.2, + 5.05, + -13.4 + ], + "to": [ + 8.8, + 5.2, + -10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.4000000000000004, + 0.25 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.4000000000000004, + 0.25 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 3.4000000000000004 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 3.4000000000000004 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 7.2, + 9.9, + -6.0 + ], + "to": [ + 8.8, + 10.1, + 5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 11.0, + 0.25 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 11.0, + 0.25 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 11.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 11.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 5.14595148057186, + 6.63095148057186, -5.0 ], "to": [ - 21.0, - 8.3, + 8.55595148057186, + 7.07095148057186, 1.0 ], "faces": { @@ -672,8 +953,8 @@ "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4399999999999995 ], "texture": "#lancet" }, @@ -681,8 +962,8 @@ "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4399999999999995 ], "texture": "#lancet" }, @@ -691,7 +972,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4399999999999995 ], "texture": "#lancet" }, @@ -700,7 +981,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4399999999999995 ], "texture": "#lancet" }, @@ -708,7 +989,7 @@ "uv": [ 0, 0, - 16, + 3.41, 6.0 ], "texture": "#lancet" @@ -717,7 +998,7 @@ "uv": [ 0, 0, - 16, + 3.41, 6.0 ], "texture": "#lancet" @@ -727,30 +1008,30 @@ "angle": 45, "axis": "z", "origin": [ - 8.0, - 8.0, + 6.85095148057186, + 6.85095148057186, -2.0 ] } }, { "from": [ - -5.0, - 7.7, - -5.0 + 2.8478544417155813, + 4.332854441715582, + -4.5 ], "to": [ - 21.0, - 8.3, - 1.0 + 6.2578544417155815, + 4.772854441715581, + 0.75 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4399999999999995 ], "texture": "#lancet" }, @@ -758,8 +1039,239 @@ "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 4.552854441715581, + 4.552854441715581, + -1.875 + ] + } + }, + { + "from": [ + 0.549757402859302, + 2.034757402859302, + -4.0 + ], + "to": [ + 3.959757402859302, + 2.4747574028593022, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 2.254757402859302, + 2.254757402859302, + -1.75 + ] + } + }, + { + "from": [ + -1.7483396359969774, + -0.2633396359969773, + -3.5 + ], + "to": [ + 1.6616603640030227, + 0.17666036400302268, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + -0.04333963599697732, + -0.04333963599697731, + -1.625 + ] + } + }, + { + "from": [ + 5.14595148057186, + 8.92904851942814, + -5.0 + ], + "to": [ + 8.55595148057186, + 9.36904851942814, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 ], "texture": "#lancet" }, @@ -768,7 +1280,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4400000000000013 ], "texture": "#lancet" }, @@ -777,7 +1289,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4400000000000013 ], "texture": "#lancet" }, @@ -785,7 +1297,7 @@ "uv": [ 0, 0, - 16, + 3.41, 6.0 ], "texture": "#lancet" @@ -794,7 +1306,7 @@ "uv": [ 0, 0, - 16, + 3.41, 6.0 ], "texture": "#lancet" @@ -804,30 +1316,30 @@ "angle": -45, "axis": "z", "origin": [ - 8.0, - 8.0, + 6.85095148057186, + 9.14904851942814, -2.0 ] } }, { "from": [ - -3.0, - 7.7, - 12.0 + 2.8478544417155813, + 11.227145558284418, + -4.5 ], "to": [ - 19.0, - 8.3, - 18.0 + 6.2578544417155815, + 11.66714555828442, + 0.75 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4400000000000013 ], "texture": "#lancet" }, @@ -835,8 +1347,239 @@ "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 4.552854441715581, + 11.447145558284419, + -1.875 + ] + } + }, + { + "from": [ + 0.549757402859302, + 13.525242597140698, + -4.0 + ], + "to": [ + 3.959757402859302, + 13.9652425971407, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 2.254757402859302, + 13.745242597140699, + -1.75 + ] + } + }, + { + "from": [ + -1.7483396359969774, + 15.823339635996978, + -3.5 + ], + "to": [ + 1.6616603640030227, + 16.263339635996978, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + -0.04333963599697732, + 16.04333963599698, + -1.625 + ] + } + }, + { + "from": [ + 7.44404851942814, + 6.63095148057186, + -5.0 + ], + "to": [ + 10.85404851942814, + 7.07095148057186, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 ], "texture": "#lancet" }, @@ -845,7 +1588,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4399999999999995 ], "texture": "#lancet" }, @@ -854,7 +1597,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4399999999999995 ], "texture": "#lancet" }, @@ -862,7 +1605,7 @@ "uv": [ 0, 0, - 16, + 3.41, 6.0 ], "texture": "#lancet" @@ -871,7 +1614,315 @@ "uv": [ 0, 0, - 16, + 3.41, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 9.14904851942814, + 6.85095148057186, + -2.0 + ] + } + }, + { + "from": [ + 9.742145558284419, + 4.332854441715582, + -4.5 + ], + "to": [ + 13.152145558284419, + 4.772854441715581, + 0.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 11.447145558284419, + 4.552854441715581, + -1.875 + ] + } + }, + { + "from": [ + 12.040242597140697, + 2.034757402859302, + -4.0 + ], + "to": [ + 15.450242597140697, + 2.4747574028593022, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 13.745242597140697, + 2.254757402859302, + -1.75 + ] + } + }, + { + "from": [ + 14.338339635996977, + -0.2633396359969773, + -3.5 + ], + "to": [ + 17.748339635996977, + 0.17666036400302268, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 16.04333963599698, + -0.04333963599697731, + -1.625 + ] + } + }, + { + "from": [ + 7.44404851942814, + 8.92904851942814, + -5.0 + ], + "to": [ + 10.85404851942814, + 9.36904851942814, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, 6.0 ], "texture": "#lancet" @@ -881,30 +1932,30 @@ "angle": 45, "axis": "z", "origin": [ - 8.0, - 8.0, - 15.0 + 9.14904851942814, + 9.14904851942814, + -2.0 ] } }, { "from": [ - -3.0, - 7.7, - 12.0 + 9.742145558284419, + 11.227145558284418, + -4.5 ], "to": [ - 19.0, - 8.3, - 18.0 + 13.152145558284419, + 11.66714555828442, + 0.75 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4400000000000013 ], "texture": "#lancet" }, @@ -912,8 +1963,239 @@ "uv": [ 0, 0, - 16, - 0.6000000000000005 + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 11.447145558284419, + 11.447145558284419, + -1.875 + ] + } + }, + { + "from": [ + 12.040242597140697, + 13.525242597140698, + -4.0 + ], + "to": [ + 15.450242597140697, + 13.9652425971407, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 13.745242597140697, + 13.745242597140699, + -1.75 + ] + } + }, + { + "from": [ + 14.338339635996977, + 15.823339635996978, + -3.5 + ], + "to": [ + 17.748339635996977, + 16.263339635996978, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 16.04333963599698, + 16.04333963599698, + -1.625 + ] + } + }, + { + "from": [ + 5.572728175868497, + 6.8077281758684975, + 12.0 + ], + "to": [ + 8.482728175868496, + 7.247728175868497, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 ], "texture": "#lancet" }, @@ -922,7 +2204,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4399999999999995 ], "texture": "#lancet" }, @@ -931,7 +2213,7 @@ 0, 0, 6.0, - 0.6000000000000005 + 0.4399999999999995 ], "texture": "#lancet" }, @@ -939,7 +2221,7 @@ "uv": [ 0, 0, - 16, + 2.9099999999999993, 6.0 ], "texture": "#lancet" @@ -948,7 +2230,315 @@ "uv": [ 0, 0, - 16, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 7.027728175868496, + 7.027728175868497, + 15.0 + ] + } + }, + { + "from": [ + 3.628184527605491, + 4.863184527605491, + 12.5 + ], + "to": [ + 6.538184527605491, + 5.303184527605491, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 5.083184527605491, + 5.083184527605491, + 15.125 + ] + } + }, + { + "from": [ + 1.6836408793424864, + 2.9186408793424863, + 13.0 + ], + "to": [ + 4.5936408793424865, + 3.3586408793424867, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 3.1386408793424865, + 3.1386408793424865, + 15.25 + ] + } + }, + { + "from": [ + -0.2609027689205199, + 0.9740972310794802, + 13.5 + ], + "to": [ + 2.6490972310794803, + 1.4140972310794802, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 1.1940972310794802, + 1.1940972310794802, + 15.375 + ] + } + }, + { + "from": [ + 5.572728175868497, + 8.752271824131503, + 12.0 + ], + "to": [ + 8.482728175868496, + 9.192271824131504, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, 6.0 ], "texture": "#lancet" @@ -958,12 +2548,859 @@ "angle": -45, "axis": "z", "origin": [ - 8.0, - 8.0, + 7.027728175868496, + 8.972271824131504, 15.0 ] } }, + { + "from": [ + 3.628184527605491, + 10.696815472394508, + 12.5 + ], + "to": [ + 6.538184527605491, + 11.13681547239451, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 5.083184527605491, + 10.916815472394509, + 15.125 + ] + } + }, + { + "from": [ + 1.6836408793424864, + 12.641359120657514, + 13.0 + ], + "to": [ + 4.5936408793424865, + 13.081359120657515, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 3.1386408793424865, + 12.861359120657514, + 15.25 + ] + } + }, + { + "from": [ + -0.2609027689205199, + 14.58590276892052, + 13.5 + ], + "to": [ + 2.6490972310794803, + 15.02590276892052, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 1.1940972310794802, + 14.80590276892052, + 15.375 + ] + } + }, + { + "from": [ + 7.517271824131503, + 6.8077281758684975, + 12.0 + ], + "to": [ + 10.427271824131502, + 7.247728175868497, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 8.972271824131502, + 7.027728175868497, + 15.0 + ] + } + }, + { + "from": [ + 9.461815472394509, + 4.863184527605491, + 12.5 + ], + "to": [ + 12.371815472394509, + 5.303184527605491, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 10.916815472394509, + 5.083184527605491, + 15.125 + ] + } + }, + { + "from": [ + 11.406359120657513, + 2.9186408793424863, + 13.0 + ], + "to": [ + 14.316359120657513, + 3.3586408793424867, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 12.861359120657513, + 3.1386408793424865, + 15.25 + ] + } + }, + { + "from": [ + 13.35090276892052, + 0.9740972310794802, + 13.5 + ], + "to": [ + 16.26090276892052, + 1.4140972310794802, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 14.80590276892052, + 1.1940972310794802, + 15.375 + ] + } + }, + { + "from": [ + 7.517271824131503, + 8.752271824131503, + 12.0 + ], + "to": [ + 10.427271824131502, + 9.192271824131504, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.972271824131502, + 8.972271824131504, + 15.0 + ] + } + }, + { + "from": [ + 9.461815472394509, + 10.696815472394508, + 12.5 + ], + "to": [ + 12.371815472394509, + 11.13681547239451, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 10.916815472394509, + 10.916815472394509, + 15.125 + ] + } + }, + { + "from": [ + 11.406359120657513, + 12.641359120657514, + 13.0 + ], + "to": [ + 14.316359120657513, + 13.081359120657515, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 12.861359120657513, + 12.861359120657514, + 15.25 + ] + } + }, + { + "from": [ + 13.35090276892052, + 14.58590276892052, + 13.5 + ], + "to": [ + 16.26090276892052, + 15.02590276892052, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 14.80590276892052, + 14.80590276892052, + 15.375 + ] + } + }, { "from": [ 6.61415, @@ -1246,4 +3683,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/nasams_launcher.json b/airdef-mod/assets/airdef/models/block/nasams_launcher.json index 362338c..596cdd1 100644 --- a/airdef-mod/assets/airdef/models/block/nasams_launcher.json +++ b/airdef-mod/assets/airdef/models/block/nasams_launcher.json @@ -550,6 +550,142 @@ "texture": "#dark_metal" } } + }, + { + "from": [ + 0.6, + 0.0, + -2.0 + ], + "to": [ + 15.4, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.6, + 0.0, + 22.0 + ], + "to": [ + 15.4, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/nasams_launcher_burnt.json b/airdef-mod/assets/airdef/models/block/nasams_launcher_burnt.json index 795504a..2c8ab6a 100644 --- a/airdef-mod/assets/airdef/models/block/nasams_launcher_burnt.json +++ b/airdef-mod/assets/airdef/models/block/nasams_launcher_burnt.json @@ -550,6 +550,142 @@ "texture": "#dark_metal_b" } } + }, + { + "from": [ + 0.6, + 0.0, + -2.0 + ], + "to": [ + 15.4, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 0.6, + 0.0, + 22.0 + ], + "to": [ + 15.4, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/nasams_trailer.json b/airdef-mod/assets/airdef/models/block/nasams_trailer.json index 9709636..e08e8bf 100644 --- a/airdef-mod/assets/airdef/models/block/nasams_trailer.json +++ b/airdef-mod/assets/airdef/models/block/nasams_trailer.json @@ -1,24 +1,23 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21": "airdef:block/nato_green_s21", - "nato_green_s22": "airdef:block/nato_green_s22", + "pilot_nato": "airdef:block/pilot_nato", "dark_metal": "airdef:block/dark_metal", "metal": "airdef:block/metal", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "particle": "airdef:block/nato_green_s21" + "particle": "airdef:block/pilot_nato" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, -10.0 ], "to": [ - 22.0, - 9.0, + 21.0, + 8.0, 26.0 ], "faces": { @@ -27,36 +26,36 @@ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -65,7 +64,7 @@ 16, 16 ], - "texture": "#nato_green_s22" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -74,7 +73,75 @@ 16, 16 ], - "texture": "#nato_green_s22" + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 8.0, + -8.0 + ], + "to": [ + 20.0, + 9.0, + 23.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" } } }, @@ -97,7 +164,7 @@ 16, 4.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" }, "south": { "uv": [ @@ -106,7 +173,7 @@ 16, 4.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" }, "east": { "uv": [ @@ -115,7 +182,7 @@ 14.0, 4.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" }, "west": { "uv": [ @@ -124,7 +191,7 @@ 14.0, 4.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -133,7 +200,7 @@ 16, 14.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -142,7 +209,7 @@ 16, 14.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" } } }, @@ -216,22 +283,294 @@ }, { "from": [ - -9.0, - 0.3424499999999995, - 3.3424499999999995 + -7.0, + 0.0, + -6.0 ], "to": [ - -5.5, - 8.65755, - 11.65755 + -5.0, + 5.0, + -4.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + -6.0 + ], + "to": [ + 23.0, + 5.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 23.0 + ], + "to": [ + -5.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ + 23.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 5.779999999999999 + ], + "to": [ + -5.5, + 1.48, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 ], "texture": "#tire" }, @@ -239,8 +578,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -248,26 +587,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.440000000000001, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.440000000000001, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.440000000000001 ], "texture": "#tire" }, @@ -275,8 +614,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.440000000000001 ], "texture": "#tire" } @@ -285,21 +624,21 @@ { "from": [ 21.5, - 0.3424499999999995, - 3.3424499999999995 + 0.0, + 5.779999999999999 ], "to": [ - 25.0, - 8.65755, - 11.65755 + 24.5, + 1.48, + 10.22 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -307,8 +646,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -316,26 +655,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.440000000000001, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.440000000000001, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.440000000000001 ], "texture": "#tire" }, @@ -343,8 +682,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.440000000000001 ], "texture": "#tire" } @@ -352,176 +691,22 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - 3.3424499999999995 - ], - "to": [ - -5.53, - 8.65755, - 11.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 7.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 3.3424499999999995 - ], - "to": [ - 24.97, - 8.65755, - 11.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 7.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 13.34245 + -8.5, + 1.48, + 4.596 ], "to": [ -5.5, - 8.65755, - 21.65755 + 2.96, + 11.404 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -529,8 +714,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -538,26 +723,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire" }, @@ -565,8 +750,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire" } @@ -575,21 +760,21 @@ { "from": [ 21.5, - 0.3424499999999995, - 13.34245 + 1.48, + 4.596 ], "to": [ - 25.0, - 8.65755, - 21.65755 + 24.5, + 2.96, + 11.404 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -597,8 +782,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire" }, @@ -606,26 +791,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.48 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire" }, @@ -633,8 +818,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire" } @@ -642,22 +827,22 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - 13.34245 + -8.5, + 2.96, + 4.3 ], "to": [ - -5.53, - 8.65755, - 21.65755 + -5.5, + 4.44, + 11.7 ], "faces": { "north": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.4800000000000004 ], "texture": "#tire" }, @@ -665,8 +850,8 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.4800000000000004 ], "texture": "#tire" }, @@ -674,26 +859,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.3999999999999995, + 1.4800000000000004 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.3999999999999995, + 1.4800000000000004 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 7.3999999999999995 ], "texture": "#tire" }, @@ -701,40 +886,31 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 7.3999999999999995 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 17.5 - ] } }, { "from": [ - 21.53, - 0.3424499999999995, - 13.34245 + 21.5, + 2.96, + 4.3 ], "to": [ - 24.97, - 8.65755, - 21.65755 + 24.5, + 4.44, + 11.7 ], "faces": { "north": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.4800000000000004 ], "texture": "#tire" }, @@ -742,8 +918,8 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.4800000000000004 ], "texture": "#tire" }, @@ -751,26 +927,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.3999999999999995, + 1.4800000000000004 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.3999999999999995, + 1.4800000000000004 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 7.3999999999999995 ], "texture": "#tire" }, @@ -778,21 +954,1236 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 7.3999999999999995 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 17.5 - ] + } + }, + { + "from": [ + -8.5, + 4.44, + 4.596 + ], + "to": [ + -5.5, + 5.92, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.44, + 4.596 + ], + "to": [ + 24.5, + 5.92, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 5.92, + 5.779999999999999 + ], + "to": [ + -5.5, + 7.4, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 5.92, + 5.779999999999999 + ], + "to": [ + 24.5, + 7.4, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 1.9000000000000001, + 6.2 + ], + "to": [ + -8.5, + 5.5, + 9.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 6.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 9.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 15.78 + ], + "to": [ + -5.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 15.78 + ], + "to": [ + 24.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 14.596 + ], + "to": [ + -5.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.48, + 14.596 + ], + "to": [ + 24.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 14.3 + ], + "to": [ + -5.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 2.96, + 14.3 + ], + "to": [ + 24.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.44, + 14.596 + ], + "to": [ + -5.5, + 5.92, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.44, + 14.596 + ], + "to": [ + 24.5, + 5.92, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 5.92, + 15.78 + ], + "to": [ + -5.5, + 7.4, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 5.92, + 15.78 + ], + "to": [ + 24.5, + 7.4, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 1.9000000000000001, + 16.2 + ], + "to": [ + -8.5, + 5.5, + 19.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 16.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 19.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/nasams_trailer_burnt.json b/airdef-mod/assets/airdef/models/block/nasams_trailer_burnt.json index 0ede9e4..aaa44bb 100644 --- a/airdef-mod/assets/airdef/models/block/nasams_trailer_burnt.json +++ b/airdef-mod/assets/airdef/models/block/nasams_trailer_burnt.json @@ -1,24 +1,23 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21_b": "airdef:block/nato_green_s21_b", - "nato_green_s22_b": "airdef:block/nato_green_s22_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", "dark_metal_b": "airdef:block/dark_metal_b", "metal_b": "airdef:block/metal_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "particle": "airdef:block/nato_green_s21_b" + "particle": "airdef:block/pilot_nato_b" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, -10.0 ], "to": [ - 22.0, - 9.0, + 21.0, + 8.0, 26.0 ], "faces": { @@ -27,36 +26,36 @@ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 4.0 + 3.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -65,7 +64,7 @@ 16, 16 ], - "texture": "#nato_green_s22_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -74,7 +73,75 @@ 16, 16 ], - "texture": "#nato_green_s22_b" + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -4.0, + 8.0, + -8.0 + ], + "to": [ + 20.0, + 9.0, + 23.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" } } }, @@ -97,7 +164,7 @@ 16, 4.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ @@ -106,7 +173,7 @@ 16, 4.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ @@ -115,7 +182,7 @@ 14.0, 4.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ @@ -124,7 +191,7 @@ 14.0, 4.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -133,7 +200,7 @@ 16, 14.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -142,7 +209,7 @@ 16, 14.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_nato_b" } } }, @@ -216,22 +283,294 @@ }, { "from": [ - -9.0, - 0.3424499999999995, - 3.3424499999999995 + -7.0, + 0.0, + -6.0 ], "to": [ - -5.5, - 8.65755, - 11.65755 + -5.0, + 5.0, + -4.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 21.0, + 0.0, + -6.0 + ], + "to": [ + 23.0, + 5.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 23.0 + ], + "to": [ + -5.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ + 23.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 5.779999999999999 + ], + "to": [ + -5.5, + 1.48, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 ], "texture": "#tire_b" }, @@ -239,8 +578,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.48 ], "texture": "#tire_b" }, @@ -248,26 +587,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.440000000000001, + 1.48 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.440000000000001, + 1.48 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.440000000000001 ], "texture": "#tire_b" }, @@ -275,8 +614,144 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.440000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 4.596 + ], + "to": [ + -5.5, + 2.96, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 4.3 + ], + "to": [ + -5.5, + 4.44, + 11.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 ], "texture": "#tire_b" } @@ -285,21 +760,21 @@ { "from": [ 21.5, - 0.3424499999999995, - 3.3424499999999995 + 2.96, + 4.3 ], "to": [ - 25.0, - 8.65755, - 11.65755 + 24.5, + 4.44, + 11.7 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.4800000000000004 ], "texture": "#tire_b" }, @@ -307,8 +782,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.4800000000000004 ], "texture": "#tire_b" }, @@ -316,26 +791,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.3999999999999995, + 1.4800000000000004 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.3999999999999995, + 1.4800000000000004 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 7.3999999999999995 ], "texture": "#tire_b" }, @@ -343,8 +818,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 7.3999999999999995 ], "texture": "#tire_b" } @@ -352,176 +827,22 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - 3.3424499999999995 - ], - "to": [ - -5.53, - 8.65755, - 11.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 7.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 3.3424499999999995 - ], - "to": [ - 24.97, - 8.65755, - 11.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 7.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 13.34245 + -8.5, + 4.44, + 4.596 ], "to": [ -5.5, - 8.65755, - 21.65755 + 5.92, + 11.404 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.4799999999999995 ], "texture": "#tire_b" }, @@ -529,8 +850,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.4799999999999995 ], "texture": "#tire_b" }, @@ -538,26 +859,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.4799999999999995 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.4799999999999995 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire_b" }, @@ -565,8 +886,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire_b" } @@ -575,21 +896,21 @@ { "from": [ 21.5, - 0.3424499999999995, - 13.34245 + 4.44, + 4.596 ], "to": [ - 25.0, - 8.65755, - 21.65755 + 24.5, + 5.92, + 11.404 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.4799999999999995 ], "texture": "#tire_b" }, @@ -597,8 +918,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.4799999999999995 ], "texture": "#tire_b" }, @@ -606,26 +927,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.4799999999999995 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 6.808, + 1.4799999999999995 ], - "texture": "#hub_b" + "texture": "#tire_b" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire_b" }, @@ -633,8 +954,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 6.808 ], "texture": "#tire_b" } @@ -642,40 +963,40 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - 13.34245 + -8.56, + 1.9000000000000001, + 6.2 ], "to": [ - -5.53, - 8.65755, - 21.65755 + -8.5, + 5.5, + 9.8 ], "faces": { "north": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 0.25, + 3.5999999999999996 ], - "texture": "#tire_b" + "texture": "#hub_b" }, "south": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 0.25, + 3.5999999999999996 ], - "texture": "#tire_b" + "texture": "#hub_b" }, "east": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 3.6000000000000005, + 3.5999999999999996 ], "texture": "#hub_b" }, @@ -683,8 +1004,8 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 3.6000000000000005, + 3.5999999999999996 ], "texture": "#hub_b" }, @@ -692,67 +1013,58 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 0.25, + 3.6000000000000005 ], - "texture": "#tire_b" + "texture": "#hub_b" }, "down": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 0.25, + 3.6000000000000005 ], - "texture": "#tire_b" + "texture": "#hub_b" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 17.5 - ] } }, { "from": [ - 21.53, - 0.3424499999999995, - 13.34245 + 24.5, + 1.9000000000000001, + 6.2 ], "to": [ - 24.97, - 8.65755, - 21.65755 + 24.560000000000002, + 5.5, + 9.8 ], "faces": { "north": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 0.25, + 3.5999999999999996 ], - "texture": "#tire_b" + "texture": "#hub_b" }, "south": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 0.25, + 3.5999999999999996 ], - "texture": "#tire_b" + "texture": "#hub_b" }, "east": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 3.6000000000000005, + 3.5999999999999996 ], "texture": "#hub_b" }, @@ -760,8 +1072,8 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 3.6000000000000005, + 3.5999999999999996 ], "texture": "#hub_b" }, @@ -769,8 +1081,76 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 0.25, + 3.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 15.78 + ], + "to": [ + -5.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 ], "texture": "#tire_b" }, @@ -778,21 +1158,556 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 4.4399999999999995 ], "texture": "#tire_b" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 17.5 - ] + } + }, + { + "from": [ + 21.5, + 0.0, + 15.78 + ], + "to": [ + 24.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 14.596 + ], + "to": [ + -5.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.48, + 14.596 + ], + "to": [ + 24.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 14.3 + ], + "to": [ + -5.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 2.96, + 14.3 + ], + "to": [ + 24.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.44, + 14.596 + ], + "to": [ + -5.5, + 5.92, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 5.92, + 15.78 + ], + "to": [ + 24.5, + 7.4, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 16.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 19.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/pantsir_pack.json b/airdef-mod/assets/airdef/models/block/pantsir_pack.json index 512ee26..f88b325 100644 --- a/airdef-mod/assets/airdef/models/block/pantsir_pack.json +++ b/airdef-mod/assets/airdef/models/block/pantsir_pack.json @@ -2,9 +2,7 @@ "ambientocclusion": false, "textures": { "tubes6": "airdef:block/tubes6", - "ru_green": "airdef:block/ru_green", - "ru_green_s21": "airdef:block/ru_green_s21", - "ru_green_s12": "airdef:block/ru_green_s12", + "pilot_olive": "airdef:block/pilot_olive", "bare_barrel": "airdef:block/bare_barrel", "dark_metal": "airdef:block/dark_metal", "particle": "airdef:block/tubes6" @@ -38,7 +36,7 @@ 8.0, 10.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -47,7 +45,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -56,7 +54,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -65,7 +63,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -74,7 +72,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12" + "texture": "#pilot_olive" } } }, @@ -106,7 +104,7 @@ 8.0, 10.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -115,7 +113,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -124,7 +122,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -133,7 +131,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -142,7 +140,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12" + "texture": "#pilot_olive" } } }, @@ -555,4 +553,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/pantsir_pack_burnt.json b/airdef-mod/assets/airdef/models/block/pantsir_pack_burnt.json index df62e78..8eae2ba 100644 --- a/airdef-mod/assets/airdef/models/block/pantsir_pack_burnt.json +++ b/airdef-mod/assets/airdef/models/block/pantsir_pack_burnt.json @@ -2,9 +2,7 @@ "ambientocclusion": false, "textures": { "tubes6_b": "airdef:block/tubes6_b", - "ru_green_b": "airdef:block/ru_green_b", - "ru_green_s21_b": "airdef:block/ru_green_s21_b", - "ru_green_s12_b": "airdef:block/ru_green_s12_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "bare_barrel_b": "airdef:block/bare_barrel_b", "dark_metal_b": "airdef:block/dark_metal_b", "particle": "airdef:block/tubes6_b" @@ -38,7 +36,7 @@ 8.0, 10.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -47,7 +45,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -56,7 +54,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -65,7 +63,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -74,7 +72,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12_b" + "texture": "#pilot_olive_b" } } }, @@ -106,7 +104,7 @@ 8.0, 10.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -115,7 +113,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -124,7 +122,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -133,7 +131,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -142,7 +140,7 @@ 8.0, 16 ], - "texture": "#ru_green_s12_b" + "texture": "#pilot_olive_b" } } }, @@ -555,4 +553,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/pantsir_truck.json b/airdef-mod/assets/airdef/models/block/pantsir_truck.json index 18b2db6..e65d73b 100644 --- a/airdef-mod/assets/airdef/models/block/pantsir_truck.json +++ b/airdef-mod/assets/airdef/models/block/pantsir_truck.json @@ -1,27 +1,23 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21": "airdef:block/ru_green_s21", - "ru_green_s31": "airdef:block/ru_green_s31", - "ru_green_s23": "airdef:block/ru_green_s23", + "pilot_olive": "airdef:block/pilot_olive", "glass_cab": "airdef:block/glass_cab", - "ru_green": "airdef:block/ru_green", "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "mk_star": "airdef:block/mk_star", - "bare_lamp": "airdef:block/bare_lamp", - "particle": "airdef:block/ru_green_s21" + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -33,7 +29,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -42,7 +38,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -51,7 +47,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -60,7 +56,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -69,7 +65,7 @@ 16, 16 ], - "texture": "#ru_green_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -78,19 +74,19 @@ 16, 16 ], - "texture": "#ru_green_s23" + "texture": "#pilot_olive" } } }, { "from": [ - -6.0, - 9.0, + -5.0, + 8.0, -16.0 ], "to": [ - 22.0, 21.0, + 16.0, -6.0 ], "faces": { @@ -99,36 +95,36 @@ 0, 0, 16, - 12.0 + 8.0 ], - "texture": "#glass_cab" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 12.0 + 8.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 10.0, - 12.0 + 8.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 10.0, - 12.0 + 8.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -137,7 +133,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -146,20 +142,360 @@ 16, 10.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" } } }, { "from": [ - -5.0, - 13.0, + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.5 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, -16.0 ], "to": [ - 21.0, - 20.0, - -15.9 + 21.5, + 8.0, + -15.3 ], "faces": { "north": { @@ -167,122 +503,190 @@ 0, 0, 16, - 7.0 + 1.0 ], - "texture": "#glass_cab" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, 16, - 7.0 + 1.0 ], - "texture": "#glass_cab" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 0.25, - 7.0 + 0.6999999999999993, + 1.0 ], - "texture": "#glass_cab" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 0.25, - 7.0 + 0.6999999999999993, + 1.0 ], - "texture": "#glass_cab" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, 16, - 0.25 + 0.6999999999999993 ], - "texture": "#glass_cab" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, 16, - 0.25 + 0.6999999999999993 ], - "texture": "#glass_cab" + "texture": "#dark_metal" } } }, { "from": [ - -5.0, + -4.5, 9.0, - -5.0 + -16.0 ], "to": [ - 21.0, - 18.0, - 6.0 + -1.5, + 10.2, + -15.8 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 9.0 + 3.0, + 1.1999999999999993 ], - "texture": "#ru_green_s21" + "texture": "#bare_lamp" }, "south": { "uv": [ 0, 0, - 16, - 9.0 + 3.0, + 1.1999999999999993 ], - "texture": "#ru_green_s21" + "texture": "#bare_lamp" }, "east": { "uv": [ 0, 0, - 11.0, - 9.0 + 0.25, + 1.1999999999999993 ], - "texture": "#ru_green" + "texture": "#bare_lamp" }, "west": { "uv": [ 0, 0, - 11.0, - 9.0 + 0.25, + 1.1999999999999993 ], - "texture": "#ru_green" + "texture": "#bare_lamp" }, "up": { "uv": [ 0, 0, - 16, - 11.0 + 3.0, + 0.25 ], - "texture": "#ru_green_s21" + "texture": "#bare_lamp" }, "down": { "uv": [ 0, 0, - 16, - 11.0 + 3.0, + 0.25 ], - "texture": "#ru_green_s21" + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" } } }, @@ -290,12 +694,80 @@ "from": [ -4.0, 9.0, - 8.0 + -5.0 ], "to": [ 20.0, + 16.5, + 5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 5.0 + ], + "to": [ + 19.0, 12.0, - 30.0 + 28.0 ], "faces": { "north": { @@ -356,13 +828,13 @@ }, { "from": [ - -6.5, - 3.0, - 26.0 + -5.0, + 8.0, + 28.0 ], "to": [ - 22.5, - 6.0, + 21.0, + 11.0, 32.0 ], "faces": { @@ -373,7 +845,7 @@ 16, 3.0 ], - "texture": "#dark_metal" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -382,1487 +854,55 @@ 16, 3.0 ], - "texture": "#dark_metal" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, - 6.0, + 4.0, 3.0 ], - "texture": "#dark_metal" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, - 6.0, + 4.0, 3.0 ], - "texture": "#dark_metal" + "texture": "#pilot_olive" }, "up": { "uv": [ 0, 0, 16, - 6.0 + 4.0 ], - "texture": "#dark_metal" + "texture": "#pilot_olive" }, "down": { "uv": [ 0, 0, 16, - 6.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -13.65755 - ], - "to": [ - -5.5, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -13.65755 - ], - "to": [ - 25.0, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -13.65755 - ], - "to": [ - -5.53, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -9.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -13.65755 - ], - "to": [ - 24.97, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -9.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -0.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -0.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.5, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 25.0, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.53, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 24.97, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.5, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 25.0, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.53, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 24.97, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 22.0, - 12.0, - -14.0 - ], - "to": [ - 22.119999999999997, - 16.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, 4.0 ], - "texture": "#mk_star" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_star" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star" + "texture": "#pilot_olive" } } }, { "from": [ - -6.119999999999997, - 12.0, - -14.0 - ], - "to": [ - -6.0, - 16.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_star" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_star" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star" - } - } - }, - { - "from": [ - -4.5, - 10.5, - -16.0 - ], - "to": [ - -2.5, - 12.2, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 10.5, - -16.0 - ], - "to": [ - 20.5, - 12.2, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.6999999999999993 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - -10.5, - 7.0, - 6.0 - ], - "to": [ -7.0, - 10.0, + 8.0, + 6.0 + ], + "to": [ + -5.0, + 11.0, 14.0 ], "faces": { @@ -1870,7 +910,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal" @@ -1879,7 +919,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal" @@ -1906,7 +946,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal" @@ -1915,7 +955,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal" @@ -1924,13 +964,13 @@ }, { "from": [ - 23.0, - 7.0, + 21.0, + 8.0, 6.0 ], "to": [ - 26.5, - 10.0, + 23.0, + 11.0, 14.0 ], "faces": { @@ -1938,7 +978,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal" @@ -1947,7 +987,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal" @@ -1974,7 +1014,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal" @@ -1983,12 +1023,3276 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal" } } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -3.9 + ], + "to": [ + -5.5, + 1.6, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -3.9 + ], + "to": [ + 24.5, + 1.6, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.18 + ], + "to": [ + -5.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.18 + ], + "to": [ + 24.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -5.5 + ], + "to": [ + -5.5, + 4.8, + 2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -5.5 + ], + "to": [ + 24.5, + 4.8, + 2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.18 + ], + "to": [ + -5.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.18 + ], + "to": [ + 24.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -3.9 + ], + "to": [ + -5.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -3.9 + ], + "to": [ + 24.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.3 + ], + "to": [ + -8.5, + 5.8, + 0.30000000000000004 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.3 + ], + "to": [ + 24.560000000000002, + 5.8, + 0.30000000000000004 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/pantsir_truck_burnt.json b/airdef-mod/assets/airdef/models/block/pantsir_truck_burnt.json index 57a6c41..f20bc86 100644 --- a/airdef-mod/assets/airdef/models/block/pantsir_truck_burnt.json +++ b/airdef-mod/assets/airdef/models/block/pantsir_truck_burnt.json @@ -1,27 +1,23 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21_b": "airdef:block/ru_green_s21_b", - "ru_green_s31_b": "airdef:block/ru_green_s31_b", - "ru_green_s23_b": "airdef:block/ru_green_s23_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "glass_cab_b": "airdef:block/glass_cab_b", - "ru_green_b": "airdef:block/ru_green_b", "dark_metal_b": "airdef:block/dark_metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "mk_star_b": "airdef:block/mk_star_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "particle": "airdef:block/ru_green_s21_b" + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -33,7 +29,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -42,7 +38,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -51,7 +47,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -60,7 +56,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -69,7 +65,7 @@ 16, 16 ], - "texture": "#ru_green_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -78,19 +74,19 @@ 16, 16 ], - "texture": "#ru_green_s23_b" + "texture": "#pilot_olive_b" } } }, { "from": [ - -6.0, - 9.0, + -5.0, + 8.0, -16.0 ], "to": [ - 22.0, 21.0, + 16.0, -6.0 ], "faces": { @@ -99,36 +95,36 @@ 0, 0, 16, - 12.0 + 8.0 ], - "texture": "#glass_cab_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 12.0 + 8.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 10.0, - 12.0 + 8.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 10.0, - 12.0 + 8.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -137,7 +133,7 @@ 16, 10.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -146,20 +142,292 @@ 16, 10.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" } } }, { "from": [ - -5.0, - 13.0, + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.5, + 7.0, -16.0 ], "to": [ - 21.0, - 20.0, - -15.9 + 21.5, + 8.0, + -15.3 ], "faces": { "north": { @@ -167,122 +435,190 @@ 0, 0, 16, - 7.0 + 1.0 ], - "texture": "#glass_cab_b" + "texture": "#dark_metal_b" }, "south": { "uv": [ 0, 0, 16, - 7.0 + 1.0 ], - "texture": "#glass_cab_b" + "texture": "#dark_metal_b" }, "east": { "uv": [ 0, 0, - 0.25, - 7.0 + 0.6999999999999993, + 1.0 ], - "texture": "#glass_cab_b" + "texture": "#dark_metal_b" }, "west": { "uv": [ 0, 0, - 0.25, - 7.0 + 0.6999999999999993, + 1.0 ], - "texture": "#glass_cab_b" + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, 16, - 0.25 + 0.6999999999999993 ], - "texture": "#glass_cab_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, 16, - 0.25 + 0.6999999999999993 ], - "texture": "#glass_cab_b" + "texture": "#dark_metal_b" } } }, { "from": [ - -5.0, + -4.5, 9.0, - -5.0 + -16.0 ], "to": [ - 21.0, - 18.0, - 6.0 + -1.5, + 10.2, + -15.8 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 9.0 + 3.0, + 1.1999999999999993 ], - "texture": "#ru_green_s21_b" + "texture": "#bare_lamp_b" }, "south": { "uv": [ 0, 0, - 16, - 9.0 + 3.0, + 1.1999999999999993 ], - "texture": "#ru_green_s21_b" + "texture": "#bare_lamp_b" }, "east": { "uv": [ 0, 0, - 11.0, - 9.0 + 0.25, + 1.1999999999999993 ], - "texture": "#ru_green_b" + "texture": "#bare_lamp_b" }, "west": { "uv": [ 0, 0, - 11.0, - 9.0 + 0.25, + 1.1999999999999993 ], - "texture": "#ru_green_b" + "texture": "#bare_lamp_b" }, "up": { "uv": [ 0, 0, - 16, - 11.0 + 3.0, + 0.25 ], - "texture": "#ru_green_s21_b" + "texture": "#bare_lamp_b" }, "down": { "uv": [ 0, 0, - 16, - 11.0 + 3.0, + 0.25 ], - "texture": "#ru_green_s21_b" + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" } } }, @@ -290,12 +626,80 @@ "from": [ -4.0, 9.0, - 8.0 + -5.0 ], "to": [ 20.0, + 16.5, + 5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 5.0 + ], + "to": [ + 19.0, 12.0, - 30.0 + 28.0 ], "faces": { "north": { @@ -356,13 +760,13 @@ }, { "from": [ - -6.5, - 3.0, - 26.0 + -5.0, + 8.0, + 28.0 ], "to": [ - 22.5, - 6.0, + 21.0, + 11.0, 32.0 ], "faces": { @@ -373,7 +777,7 @@ 16, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -382,1419 +786,55 @@ 16, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, - 6.0, + 4.0, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, - 6.0, + 4.0, 3.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ 0, 0, 16, - 6.0 + 4.0 ], - "texture": "#dark_metal_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ 0, 0, 16, - 6.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -13.65755 - ], - "to": [ - -5.5, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -13.65755 - ], - "to": [ - 25.0, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -13.65755 - ], - "to": [ - -5.53, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -9.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -13.65755 - ], - "to": [ - 24.97, - 8.65755, - -5.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -9.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -0.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -4.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 3.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -0.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.5, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 25.0, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.53, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 24.97, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.5, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 25.0, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.53, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 24.97, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 22.0, - 12.0, - -14.0 - ], - "to": [ - 22.119999999999997, - 16.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, 4.0 ], - "texture": "#mk_star_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_star_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star_b" + "texture": "#pilot_olive_b" } } }, { "from": [ - -6.119999999999997, - 12.0, - -14.0 - ], - "to": [ - -6.0, - 16.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_star_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_star_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_star_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_star_b" - } - } - }, - { - "from": [ - -4.5, - 10.5, - -16.0 - ], - "to": [ - -2.5, - 12.2, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.6999999999999993 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.6999999999999993 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.6999999999999993 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.6999999999999993 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - -10.5, - 7.0, - 6.0 - ], - "to": [ -7.0, - 10.0, + 8.0, + 6.0 + ], + "to": [ + -5.0, + 11.0, 14.0 ], "faces": { @@ -1802,7 +842,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal_b" @@ -1811,7 +851,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal_b" @@ -1838,7 +878,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal_b" @@ -1847,7 +887,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal_b" @@ -1856,13 +896,13 @@ }, { "from": [ - 23.0, - 7.0, + 21.0, + 8.0, 6.0 ], "to": [ - 26.5, - 10.0, + 23.0, + 11.0, 14.0 ], "faces": { @@ -1870,7 +910,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal_b" @@ -1879,7 +919,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 3.0 ], "texture": "#dark_metal_b" @@ -1906,7 +946,7 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal_b" @@ -1915,12 +955,2052 @@ "uv": [ 0, 0, - 3.5, + 2.0, 8.0 ], "texture": "#dark_metal_b" } } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -3.9 + ], + "to": [ + -5.5, + 1.6, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.18 + ], + "to": [ + -5.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.18 + ], + "to": [ + 24.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -5.5 + ], + "to": [ + -5.5, + 4.8, + 2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.18 + ], + "to": [ + -5.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.18 + ], + "to": [ + 24.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -3.9 + ], + "to": [ + -5.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -3.9 + ], + "to": [ + 24.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.3 + ], + "to": [ + 24.560000000000002, + 5.8, + 0.30000000000000004 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/pantsir_turret.json b/airdef-mod/assets/airdef/models/block/pantsir_turret.json index 0c641a4..24cda16 100644 --- a/airdef-mod/assets/airdef/models/block/pantsir_turret.json +++ b/airdef-mod/assets/airdef/models/block/pantsir_turret.json @@ -1,24 +1,23 @@ { "ambientocclusion": false, "textures": { - "ru_green": "airdef:block/ru_green", + "pilot_olive": "airdef:block/pilot_olive", "radar_face": "airdef:block/radar_face", - "metal": "airdef:block/metal", "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", "bare_lamp": "airdef:block/bare_lamp", - "mk_ru": "airdef:block/mk_ru", - "particle": "airdef:block/ru_green" + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ -2.0, 0.0, - -6.0 + -5.0 ], "to": [ 18.0, - 10.0, + 7.0, 16.0 ], "faces": { @@ -27,36 +26,36 @@ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -65,7 +64,7 @@ 16, 16 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -74,7 +73,75 @@ 16, 16 ], - "texture": "#ru_green" + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 0.0, + 7.0, + -4.0 + ], + "to": [ + 16.0, + 10.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" } } }, @@ -146,6 +213,74 @@ } } }, + { + "from": [ + 2.0, + 1.0, + -6.0 + ], + "to": [ + 14.0, + 11.0, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, { "from": [ 7.0, @@ -216,12 +351,12 @@ }, { "from": [ - 0.0, + 1.0, 10.0, 8.0 ], "to": [ - 16.0, + 15.0, 12.0, 16.0 ], @@ -230,19 +365,19 @@ "uv": [ 0, 0, - 16, + 14.0, 2.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, - 16, + 14.0, 2.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -251,7 +386,7 @@ 8.0, 2.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -260,25 +395,25 @@ 8.0, 2.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "up": { "uv": [ 0, 0, - 16, + 14.0, 8.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" }, "down": { "uv": [ 0, 0, - 16, + 14.0, 8.0 ], - "texture": "#ru_green" + "texture": "#pilot_olive" } } }, @@ -417,210 +552,6 @@ "texture": "#bare_lamp" } } - }, - { - "from": [ - 1.0, - 12.0, - 10.0 - ], - "to": [ - 15.0, - 12.6, - 15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 14.0, - 0.5999999999999996 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 14.0, - 0.5999999999999996 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 5.0, - 0.5999999999999996 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 5.0, - 0.5999999999999996 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 14.0, - 5.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 14.0, - 5.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - 18.0, - 3.0, - -4.0 - ], - "to": [ - 18.119999999999997, - 6.5, - 3.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 3.5 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 3.5 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - } - } - }, - { - "from": [ - -2.1199999999999974, - 3.0, - -4.0 - ], - "to": [ - -2.0, - 6.5, - 3.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 3.5 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 3.5 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru" - } - } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/pantsir_turret_burnt.json b/airdef-mod/assets/airdef/models/block/pantsir_turret_burnt.json index 10e5247..53e4ed8 100644 --- a/airdef-mod/assets/airdef/models/block/pantsir_turret_burnt.json +++ b/airdef-mod/assets/airdef/models/block/pantsir_turret_burnt.json @@ -1,24 +1,22 @@ { "ambientocclusion": false, "textures": { - "ru_green_b": "airdef:block/ru_green_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "radar_face_b": "airdef:block/radar_face_b", - "metal_b": "airdef:block/metal_b", "dark_metal_b": "airdef:block/dark_metal_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "mk_ru_b": "airdef:block/mk_ru_b", - "particle": "airdef:block/ru_green_b" + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ -2.0, 0.0, - -6.0 + -5.0 ], "to": [ 18.0, - 10.0, + 7.0, 16.0 ], "faces": { @@ -27,36 +25,36 @@ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 10.0 + 7.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -65,7 +63,7 @@ 16, 16 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -74,7 +72,75 @@ 16, 16 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 0.0, + 7.0, + -4.0 + ], + "to": [ + 16.0, + 10.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" } } }, @@ -146,6 +212,74 @@ } } }, + { + "from": [ + 2.0, + 1.0, + -6.0 + ], + "to": [ + 14.0, + 11.0, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, { "from": [ 7.0, @@ -216,12 +350,12 @@ }, { "from": [ - 0.0, + 1.0, 10.0, 8.0 ], "to": [ - 16.0, + 15.0, 12.0, 16.0 ], @@ -230,19 +364,19 @@ "uv": [ 0, 0, - 16, + 14.0, 2.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, - 16, + 14.0, 2.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -251,7 +385,7 @@ 8.0, 2.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -260,25 +394,25 @@ 8.0, 2.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ 0, 0, - 16, + 14.0, 8.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ 0, 0, - 16, + 14.0, 8.0 ], - "texture": "#ru_green_b" + "texture": "#pilot_olive_b" } } }, @@ -349,210 +483,6 @@ "texture": "#dark_metal_b" } } - }, - { - "from": [ - 6.0, - 10.5, - -4.4 - ], - "to": [ - 10.0, - 12.5, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - 1.0, - 12.0, - 10.0 - ], - "to": [ - 15.0, - 12.6, - 15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 14.0, - 0.5999999999999996 - ], - "texture": "#metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 14.0, - 0.5999999999999996 - ], - "texture": "#metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 5.0, - 0.5999999999999996 - ], - "texture": "#metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 5.0, - 0.5999999999999996 - ], - "texture": "#metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 14.0, - 5.0 - ], - "texture": "#metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 14.0, - 5.0 - ], - "texture": "#metal_b" - } - } - }, - { - "from": [ - -2.1199999999999974, - 3.0, - -4.0 - ], - "to": [ - -2.0, - 6.5, - 3.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru_b" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 3.5 - ], - "texture": "#mk_ru_b" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 3.5 - ], - "texture": "#mk_ru_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru_b" - } - } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/patriot_trailer.json b/airdef-mod/assets/airdef/models/block/patriot_trailer.json index 7260bce..d2d615b 100644 --- a/airdef-mod/assets/airdef/models/block/patriot_trailer.json +++ b/airdef-mod/assets/airdef/models/block/patriot_trailer.json @@ -1,25 +1,23 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21": "airdef:block/nato_green_s21", - "nato_green_s31": "airdef:block/nato_green_s31", - "nato_green_s23": "airdef:block/nato_green_s23", - "metal": "airdef:block/metal", + "pilot_nato": "airdef:block/pilot_nato", "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "particle": "airdef:block/nato_green_s21" + "particle": "airdef:block/pilot_nato" }, "elements": [ { "from": [ - -7.0, + -6.0, 6.0, -16.0 ], "to": [ - 23.0, - 11.0, + 22.0, + 9.0, 32.0 ], "faces": { @@ -28,36 +26,36 @@ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s31" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s31" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -66,7 +64,7 @@ 16, 16 ], - "texture": "#nato_green_s23" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -75,7 +73,75 @@ 16, 16 ], - "texture": "#nato_green_s23" + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -12.0 + ], + "to": [ + 20.0, + 11.0, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" } } }, @@ -149,12 +215,12 @@ }, { "from": [ - -8.0, + -7.0, 3.0, -16.0 ], "to": [ - 24.0, + 23.0, 7.0, -12.0 ], @@ -217,719 +283,1771 @@ }, { "from": [ - -10.0, - 0.35006000000000004, - 13.750060000000001 + -8.0, + 0.0, + -6.0 ], "to": [ - -6.5, - 8.84994, - 22.24994 + -6.0, + 6.0, + -4.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#tire" + "texture": "#metal" }, "south": { "uv": [ 0, 0, - 3.5, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#tire" + "texture": "#metal" }, "east": { "uv": [ 0, 0, - 8.499879999999997, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#hub" + "texture": "#metal" }, "west": { "uv": [ 0, 0, - 8.499879999999997, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#hub" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 3.5, - 8.499879999999997 + 2.0, + 2.0 ], - "texture": "#tire" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 3.5, - 8.499879999999997 + 2.0, + 2.0 ], - "texture": "#tire" + "texture": "#metal" } } }, { "from": [ - 22.5, - 0.35006000000000004, - 13.750060000000001 + 22.0, + 0.0, + -6.0 ], "to": [ - 26.0, - 8.84994, - 22.24994 + 24.0, + 6.0, + -4.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#tire" + "texture": "#metal" }, "south": { "uv": [ 0, 0, - 3.5, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#tire" + "texture": "#metal" }, "east": { "uv": [ 0, 0, - 8.499879999999997, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#hub" + "texture": "#metal" }, "west": { "uv": [ 0, 0, - 8.499879999999997, - 8.499880000000001 + 2.0, + 6.0 ], - "texture": "#hub" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 3.5, - 8.499879999999997 + 2.0, + 2.0 ], - "texture": "#tire" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 3.5, - 8.499879999999997 + 2.0, + 2.0 ], - "texture": "#tire" + "texture": "#metal" } } }, - { - "from": [ - -9.97, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - -6.53, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -8.25, - 4.6, - 18.0 - ] - } - }, - { - "from": [ - 22.53, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - 25.97, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 24.25, - 4.6, - 18.0 - ] - } - }, - { - "from": [ - -10.0, - 0.35006000000000004, - 22.75006 - ], - "to": [ - -6.5, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 22.5, - 0.35006000000000004, - 22.75006 - ], - "to": [ - 26.0, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -9.97, - 0.35006000000000004, - 22.75006 - ], - "to": [ - -6.53, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -8.25, - 4.6, - 27.0 - ] - } - }, - { - "from": [ - 22.53, - 0.35006000000000004, - 22.75006 - ], - "to": [ - 25.97, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 24.25, - 4.6, - 27.0 - ] - } - }, { "from": [ -9.0, 0.0, - -6.0 + 16.78 ], "to": [ - -7.0, - 6.0, - -4.0 + -6.0, + 1.48, + 21.22 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 6.0 + 3.0, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "south": { "uv": [ 0, 0, - 2.0, - 6.0 + 3.0, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "east": { "uv": [ 0, 0, - 2.0, - 6.0 + 4.439999999999998, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 2.0, - 6.0 + 4.439999999999998, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 2.0, - 2.0 + 3.0, + 4.439999999999998 ], - "texture": "#metal" + "texture": "#tire" }, "down": { "uv": [ 0, 0, - 2.0, - 2.0 + 3.0, + 4.439999999999998 ], - "texture": "#metal" + "texture": "#tire" } } }, { "from": [ - 23.0, + 22.0, 0.0, - -6.0 + 16.78 ], "to": [ 25.0, - 6.0, - -4.0 + 1.48, + 21.22 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 6.0 + 3.0, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "south": { "uv": [ 0, 0, - 2.0, - 6.0 + 3.0, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "east": { "uv": [ 0, 0, - 2.0, - 6.0 + 4.439999999999998, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 2.0, - 6.0 + 4.439999999999998, + 1.48 ], - "texture": "#metal" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 2.0, - 2.0 + 3.0, + 4.439999999999998 ], - "texture": "#metal" + "texture": "#tire" }, "down": { "uv": [ 0, 0, - 2.0, - 2.0 + 3.0, + 4.439999999999998 ], - "texture": "#metal" + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 1.48, + 15.596 + ], + "to": [ + -6.0, + 2.96, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 15.596 + ], + "to": [ + 25.0, + 2.96, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 15.3 + ], + "to": [ + -6.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 2.96, + 15.3 + ], + "to": [ + 25.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 15.596 + ], + "to": [ + -6.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 4.44, + 15.596 + ], + "to": [ + 25.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 16.78 + ], + "to": [ + -6.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 16.78 + ], + "to": [ + 25.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 17.2 + ], + "to": [ + -9.0, + 5.5, + 20.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 25.0, + 1.9000000000000001, + 17.2 + ], + "to": [ + 25.060000000000002, + 5.5, + 20.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -9.0, + 0.0, + 24.78 + ], + "to": [ + -6.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 0.0, + 24.78 + ], + "to": [ + 25.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 1.48, + 23.596 + ], + "to": [ + -6.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 23.596 + ], + "to": [ + 25.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 23.3 + ], + "to": [ + -6.0, + 4.44, + 30.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 2.96, + 23.3 + ], + "to": [ + 25.0, + 4.44, + 30.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 23.596 + ], + "to": [ + -6.0, + 5.92, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 4.44, + 23.596 + ], + "to": [ + 25.0, + 5.92, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 24.78 + ], + "to": [ + -6.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 24.78 + ], + "to": [ + 25.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 25.2 + ], + "to": [ + -9.0, + 5.5, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 25.0, + 1.9000000000000001, + 25.2 + ], + "to": [ + 25.060000000000002, + 5.5, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/patriot_trailer_burnt.json b/airdef-mod/assets/airdef/models/block/patriot_trailer_burnt.json index 2b9d852..907e11f 100644 --- a/airdef-mod/assets/airdef/models/block/patriot_trailer_burnt.json +++ b/airdef-mod/assets/airdef/models/block/patriot_trailer_burnt.json @@ -1,25 +1,23 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21_b": "airdef:block/nato_green_s21_b", - "nato_green_s31_b": "airdef:block/nato_green_s31_b", - "nato_green_s23_b": "airdef:block/nato_green_s23_b", - "metal_b": "airdef:block/metal_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "particle": "airdef:block/nato_green_s21_b" + "particle": "airdef:block/pilot_nato_b" }, "elements": [ { "from": [ - -7.0, + -6.0, 6.0, -16.0 ], "to": [ - 23.0, - 11.0, + 22.0, + 9.0, 32.0 ], "faces": { @@ -28,36 +26,36 @@ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s21_b" + "texture": "#pilot_nato_b" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s31_b" + "texture": "#pilot_nato_b" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 3.0 ], - "texture": "#nato_green_s31_b" + "texture": "#pilot_nato_b" }, "up": { "uv": [ @@ -66,7 +64,7 @@ 16, 16 ], - "texture": "#nato_green_s23_b" + "texture": "#pilot_nato_b" }, "down": { "uv": [ @@ -75,7 +73,75 @@ 16, 16 ], - "texture": "#nato_green_s23_b" + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -12.0 + ], + "to": [ + 20.0, + 11.0, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" } } }, @@ -149,12 +215,12 @@ }, { "from": [ - -8.0, + -7.0, 3.0, -16.0 ], "to": [ - 24.0, + 23.0, 7.0, -12.0 ], @@ -217,592 +283,12 @@ }, { "from": [ - -10.0, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - -6.5, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 22.5, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - 26.0, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -9.97, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - -6.53, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -8.25, - 4.6, - 18.0 - ] - } - }, - { - "from": [ - 22.53, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - 25.97, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 24.25, - 4.6, - 18.0 - ] - } - }, - { - "from": [ - -10.0, - 0.35006000000000004, - 22.75006 - ], - "to": [ - -6.5, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 22.5, - 0.35006000000000004, - 22.75006 - ], - "to": [ - 26.0, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -9.97, - 0.35006000000000004, - 22.75006 - ], - "to": [ - -6.53, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -8.25, - 4.6, - 27.0 - ] - } - }, - { - "from": [ - 22.53, - 0.35006000000000004, - 22.75006 - ], - "to": [ - 25.97, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 24.25, - 4.6, - 27.0 - ] - } - }, - { - "from": [ - -9.0, + -8.0, 0.0, -6.0 ], "to": [ - -7.0, + -6.0, 6.0, -4.0 ], @@ -862,6 +348,1230 @@ "texture": "#metal_b" } } + }, + { + "from": [ + -9.0, + 0.0, + 16.78 + ], + "to": [ + -6.0, + 1.48, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 15.596 + ], + "to": [ + 25.0, + 2.96, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 15.3 + ], + "to": [ + -6.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 2.96, + 15.3 + ], + "to": [ + 25.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 15.596 + ], + "to": [ + -6.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 4.44, + 15.596 + ], + "to": [ + 25.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 16.78 + ], + "to": [ + -6.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 16.78 + ], + "to": [ + 25.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 17.2 + ], + "to": [ + -9.0, + 5.5, + 20.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -9.0, + 0.0, + 24.78 + ], + "to": [ + -6.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 0.0, + 24.78 + ], + "to": [ + 25.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 1.48, + 23.596 + ], + "to": [ + -6.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 23.596 + ], + "to": [ + 25.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 23.3 + ], + "to": [ + -6.0, + 4.44, + 30.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 23.596 + ], + "to": [ + -6.0, + 5.92, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 24.78 + ], + "to": [ + -6.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 24.78 + ], + "to": [ + 25.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 25.2 + ], + "to": [ + -9.0, + 5.5, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/patriot_truck.json b/airdef-mod/assets/airdef/models/block/patriot_truck.json index a9a77e5..47c3894 100644 --- a/airdef-mod/assets/airdef/models/block/patriot_truck.json +++ b/airdef-mod/assets/airdef/models/block/patriot_truck.json @@ -1,28 +1,24 @@ { "ambientocclusion": false, "textures": { - "nato_green_s21": "airdef:block/nato_green_s21", - "nato_green_s31": "airdef:block/nato_green_s31", - "nato_green_s23": "airdef:block/nato_green_s23", - "nato_green": "airdef:block/nato_green", + "pilot_nato": "airdef:block/pilot_nato", "glass_cab": "airdef:block/glass_cab", "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", "metal": "airdef:block/metal", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "mk_us": "airdef:block/mk_us", - "bare_lamp": "airdef:block/bare_lamp", - "particle": "airdef:block/nato_green_s21" + "particle": "airdef:block/pilot_nato" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -34,7 +30,7 @@ 16, 4.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "south": { "uv": [ @@ -43,7 +39,7 @@ 16, 4.0 ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" }, "east": { "uv": [ @@ -52,7 +48,7 @@ 16, 4.0 ], - "texture": "#nato_green_s31" + "texture": "#pilot_nato" }, "west": { "uv": [ @@ -61,7 +57,7 @@ 16, 4.0 ], - "texture": "#nato_green_s31" + "texture": "#pilot_nato" }, "up": { "uv": [ @@ -70,7 +66,7 @@ 16, 16 ], - "texture": "#nato_green_s23" + "texture": "#pilot_nato" }, "down": { "uv": [ @@ -79,360 +75,20 @@ 16, 16 ], - "texture": "#nato_green_s23" - } - } - }, - { - "from": [ - -6.0, - 9.0, - -15.0 - ], - "to": [ - 22.0, - 24.0, - -1.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 15.0 - ], - "texture": "#nato_green_s21" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 15.0 - ], - "texture": "#nato_green_s21" - }, - "east": { - "uv": [ - 0, - 0, - 14.0, - 15.0 - ], - "texture": "#nato_green" - }, - "west": { - "uv": [ - 0, - 0, - 14.0, - 15.0 - ], - "texture": "#nato_green" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 14.0 - ], - "texture": "#nato_green_s21" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 14.0 - ], - "texture": "#nato_green_s21" + "texture": "#pilot_nato" } } }, { "from": [ -5.0, - 16.0, + 8.0, -16.0 ], "to": [ 21.0, - 23.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 7.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 7.0 - ], - "texture": "#glass_cab" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#glass_cab" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#glass_cab" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#glass_cab" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#glass_cab" - } - } - }, - { - "from": [ - -6.2, - 16.0, - -13.0 - ], - "to": [ - -6.0, - 23.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#glass_cab" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 7.0 - ], - "texture": "#glass_cab" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 7.0 - ], - "texture": "#glass_cab" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 9.0 - ], - "texture": "#glass_cab" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 9.0 - ], - "texture": "#glass_cab" - } - } - }, - { - "from": [ - 22.0, - 16.0, - -13.0 - ], - "to": [ - 22.2, - 23.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#glass_cab" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 7.0 - ], - "texture": "#glass_cab" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 7.0 - ], - "texture": "#glass_cab" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 9.0 - ], - "texture": "#glass_cab" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 9.0 - ], - "texture": "#glass_cab" - } - } - }, - { - "from": [ - -6.0, - 9.0, - 2.0 - ], - "to": [ - 22.0, - 15.0, - 16.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#nato_green_s21" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#nato_green_s21" - }, - "east": { - "uv": [ - 0, - 0, - 14.0, - 6.0 - ], - "texture": "#nato_green" - }, - "west": { - "uv": [ - 0, - 0, - 14.0, - 6.0 - ], - "texture": "#nato_green" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 14.0 - ], - "texture": "#nato_green_s21" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 14.0 - ], - "texture": "#nato_green_s21" - } - } - }, - { - "from": [ - -2.0, - 24.0, - -12.0 - ], - "to": [ 18.0, - 25.0, - -4.0 + -6.0 ], "faces": { "north": { @@ -440,1073 +96,271 @@ 0, 0, 16, - 1.0 + 10.0 ], - "texture": "#dark_metal" + "texture": "#pilot_nato" }, "south": { "uv": [ 0, 0, 16, - 1.0 + 10.0 ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 1.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 1.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 8.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 8.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 2.0, - 9.0, - 20.0 - ], - "to": [ - 14.0, - 13.0, - 30.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 12.0, - 4.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 12.0, - 4.0 - ], - "texture": "#metal" + "texture": "#pilot_nato" }, "east": { "uv": [ 0, 0, 10.0, - 4.0 + 10.0 ], - "texture": "#metal" + "texture": "#pilot_nato" }, "west": { "uv": [ 0, 0, 10.0, - 4.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 12.0, 10.0 ], - "texture": "#metal" + "texture": "#pilot_nato" }, - "down": { + "up": { "uv": [ 0, 0, - 12.0, + 16, 10.0 ], - "texture": "#metal" - } - } - }, - { - "from": [ - -9.0, - 0.35006000000000004, - -12.249939999999999 - ], - "to": [ - -5.5, - 8.84994, - -3.7500600000000004 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" + "texture": "#pilot_nato" }, "down": { "uv": [ 0, 0, - 3.5, - 8.499879999999997 + 16, + 10.0 ], - "texture": "#tire" + "texture": "#pilot_nato" } } }, { "from": [ - 21.5, - 0.35006000000000004, - -12.249939999999999 + -4.5, + 18.0, + -15.5 ], "to": [ - 25.0, - 8.84994, - -3.7500600000000004 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.35006000000000004, - -12.249939999999999 - ], - "to": [ - -5.53, - 8.84994, - -3.7500600000000004 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.6, - -8.0 - ] - } - }, - { - "from": [ - 21.53, - 0.35006000000000004, - -12.249939999999999 - ], - "to": [ - 24.97, - 8.84994, - -3.7500600000000004 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.6, - -8.0 - ] - } - }, - { - "from": [ - -9.0, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - -5.5, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - 25.0, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - -5.53, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.6, - 18.0 - ] - } - }, - { - "from": [ - 21.53, - 0.35006000000000004, - 13.750060000000001 - ], - "to": [ - 24.97, - 8.84994, - 22.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.6, - 18.0 - ] - } - }, - { - "from": [ - -9.0, - 0.35006000000000004, - 22.75006 - ], - "to": [ - -5.5, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.35006000000000004, - 22.75006 - ], - "to": [ - 25.0, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.499879999999997 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.35006000000000004, - 22.75006 - ], - "to": [ - -5.53, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.6, - 27.0 - ] - } - }, - { - "from": [ - 21.53, - 0.35006000000000004, - 22.75006 - ], - "to": [ - 24.97, - 8.84994, - 31.24994 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499880000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.499879999999997, - 8.499880000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.499879999999997 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.6, - 27.0 - ] - } - }, - { - "from": [ - -6.199999999999999, - 10.0, - -12.0 - ], - "to": [ - -6.0, - 14.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#glass_cab" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#glass_cab" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#glass_cab" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#glass_cab" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#glass_cab" - } - } - }, - { - "from": [ + 20.5, 22.0, - 10.0, - -12.0 + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 18.0, + -15.99 ], "to": [ - 22.2, - 14.0, - -4.0 + 7.5, + 21.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 18.0, + -15.99 + ], + "to": [ + 20.0, + 21.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 18.0, + -14.5 + ], + "to": [ + -4.99, + 21.0, + -8.0 ], "faces": { "north": { @@ -1514,7 +368,7 @@ 0, 0, 0.25, - 4.0 + 3.0 ], "texture": "#glass_cab" }, @@ -1523,7 +377,7 @@ 0, 0, 0.25, - 4.0 + 3.0 ], "texture": "#glass_cab" }, @@ -1531,8 +385,8 @@ "uv": [ 0, 0, - 8.0, - 4.0 + 6.5, + 3.0 ], "texture": "#glass_cab" }, @@ -1540,8 +394,8 @@ "uv": [ 0, 0, - 8.0, - 4.0 + 6.5, + 3.0 ], "texture": "#glass_cab" }, @@ -1550,7 +404,7 @@ 0, 0, 0.25, - 8.0 + 6.5 ], "texture": "#glass_cab" }, @@ -1559,7 +413,7 @@ 0, 0, 0.25, - 8.0 + 6.5 ], "texture": "#glass_cab" } @@ -1567,14 +421,14 @@ }, { "from": [ - 22.0, - 11.0, - -12.0 + 20.990000000000002, + 18.0, + -14.5 ], "to": [ - 22.119999999999997, - 15.0, - -4.0 + 21.08, + 21.0, + -8.0 ], "faces": { "north": { @@ -1582,271 +436,67 @@ 0, 0, 0.25, - 4.0 + 3.0 ], - "texture": "#mk_us" + "texture": "#glass_cab" }, "south": { "uv": [ 0, 0, 0.25, - 4.0 + 3.0 ], - "texture": "#mk_us" + "texture": "#glass_cab" }, "east": { "uv": [ 0, 0, - 8.0, - 4.0 + 6.5, + 3.0 ], - "texture": "#mk_us" + "texture": "#glass_cab" }, "west": { "uv": [ 0, 0, - 8.0, - 4.0 + 6.5, + 3.0 ], - "texture": "#mk_us" + "texture": "#glass_cab" }, "up": { "uv": [ 0, 0, 0.25, - 8.0 + 6.5 ], - "texture": "#mk_us" + "texture": "#glass_cab" }, "down": { "uv": [ 0, 0, 0.25, - 8.0 + 6.5 ], - "texture": "#mk_us" + "texture": "#glass_cab" } } }, { "from": [ - -6.119999999999997, - 11.0, - -12.0 - ], - "to": [ - -6.0, - 15.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_us" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_us" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_us" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_us" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_us" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_us" - } - } - }, - { - "from": [ - -6.0, + -5.5, 7.0, - 6.0 - ], - "to": [ - -9.0, - 11.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 8.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 8.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 25.0, - 7.0, - 6.0 - ], - "to": [ - 22.0, - 11.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 8.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 8.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -1.0, - 5.5, -16.0 ], "to": [ - 17.0, - 7.5, - -15.2 + 21.5, + 8.0, + -15.3 ], "faces": { "north": { @@ -1854,7 +504,7 @@ 0, 0, 16, - 2.0 + 1.0 ], "texture": "#dark_metal" }, @@ -1863,7 +513,7 @@ 0, 0, 16, - 2.0 + 1.0 ], "texture": "#dark_metal" }, @@ -1871,8 +521,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 2.0 + 0.6999999999999993, + 1.0 ], "texture": "#dark_metal" }, @@ -1880,8 +530,8 @@ "uv": [ 0, 0, - 0.8000000000000007, - 2.0 + 0.6999999999999993, + 1.0 ], "texture": "#dark_metal" }, @@ -1890,7 +540,7 @@ 0, 0, 16, - 0.8000000000000007 + 0.6999999999999993 ], "texture": "#dark_metal" }, @@ -1899,7 +549,7 @@ 0, 0, 16, - 0.8000000000000007 + 0.6999999999999993 ], "texture": "#dark_metal" } @@ -1908,21 +558,21 @@ { "from": [ -4.5, - 10.5, + 9.0, -16.0 ], "to": [ - -2.5, - 12.2, - -15.4 + -1.5, + 10.2, + -15.8 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.6999999999999993 + 3.0, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -1930,8 +580,8 @@ "uv": [ 0, 0, - 2.0, - 1.6999999999999993 + 3.0, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -1939,8 +589,8 @@ "uv": [ 0, 0, - 0.5999999999999996, - 1.6999999999999993 + 0.25, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -1948,8 +598,8 @@ "uv": [ 0, 0, - 0.5999999999999996, - 1.6999999999999993 + 0.25, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -1957,8 +607,8 @@ "uv": [ 0, 0, - 2.0, - 0.5999999999999996 + 3.0, + 0.25 ], "texture": "#bare_lamp" }, @@ -1966,8 +616,8 @@ "uv": [ 0, 0, - 2.0, - 0.5999999999999996 + 3.0, + 0.25 ], "texture": "#bare_lamp" } @@ -1975,22 +625,22 @@ }, { "from": [ - 18.5, - 10.5, + 17.5, + 9.0, -16.0 ], "to": [ 20.5, - 12.2, - -15.4 + 10.2, + -15.8 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.6999999999999993 + 3.0, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -1998,8 +648,8 @@ "uv": [ 0, 0, - 2.0, - 1.6999999999999993 + 3.0, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -2007,8 +657,8 @@ "uv": [ 0, 0, - 0.5999999999999996, - 1.6999999999999993 + 0.25, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -2016,8 +666,8 @@ "uv": [ 0, 0, - 0.5999999999999996, - 1.6999999999999993 + 0.25, + 1.1999999999999993 ], "texture": "#bare_lamp" }, @@ -2025,8 +675,8 @@ "uv": [ 0, 0, - 2.0, - 0.5999999999999996 + 3.0, + 0.25 ], "texture": "#bare_lamp" }, @@ -2034,12 +684,3616 @@ "uv": [ 0, 0, - 2.0, - 0.5999999999999996 + 3.0, + 0.25 ], "texture": "#bare_lamp" } } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 15.0, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 6.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 6.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 8.0 + ], + "to": [ + 19.0, + 11.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 2.0, + 11.0, + 18.0 + ], + "to": [ + 14.0, + 12.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 8.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 8.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -7.0, + 9.0, + 1.0 + ], + "to": [ + -5.0, + 12.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 21.0, + 9.0, + 1.0 + ], + "to": [ + 23.0, + 12.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/radar_mast.json b/airdef-mod/assets/airdef/models/block/radar_mast.json new file mode 100644 index 0000000..80e567d --- /dev/null +++ b/airdef-mod/assets/airdef/models/block/radar_mast.json @@ -0,0 +1,418 @@ +{ + "ambientocclusion": false, + "textures": { + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "particle": "airdef:block/dark_metal" + }, + "elements": [ + { + "from": [ + 4.0, + 0.0, + 4.0 + ], + "to": [ + 12.0, + 3.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 5.5, + 3.0, + 5.5 + ], + "to": [ + 10.5, + 18.0, + 10.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 4.5, + 18.0, + 4.5 + ], + "to": [ + 11.5, + 21.0, + 11.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 6.5, + 21.0, + 6.5 + ], + "to": [ + 9.5, + 30.0, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 5.5, + 30.0, + 5.5 + ], + "to": [ + 10.5, + 32.0, + 10.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 7.0, + 31.5, + 7.0 + ], + "to": [ + 9.0, + 32.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal" + } + } + } + ] +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/models/block/radar_mast_burnt.json b/airdef-mod/assets/airdef/models/block/radar_mast_burnt.json new file mode 100644 index 0000000..868fb84 --- /dev/null +++ b/airdef-mod/assets/airdef/models/block/radar_mast_burnt.json @@ -0,0 +1,418 @@ +{ + "ambientocclusion": false, + "textures": { + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/dark_metal_b" + }, + "elements": [ + { + "from": [ + 4.0, + 0.0, + 4.0 + ], + "to": [ + 12.0, + 3.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 5.5, + 3.0, + 5.5 + ], + "to": [ + 10.5, + 18.0, + 10.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 15.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 4.5, + 18.0, + 4.5 + ], + "to": [ + 11.5, + 21.0, + 11.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 6.5, + 21.0, + 6.5 + ], + "to": [ + 9.5, + 30.0, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 5.5, + 30.0, + 5.5 + ], + "to": [ + 10.5, + 32.0, + 10.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 5.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 7.0, + 31.5, + 7.0 + ], + "to": [ + 9.0, + 32.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/models/block/radar_truck.json b/airdef-mod/assets/airdef/models/block/radar_truck.json index 9272429..faccce7 100644 --- a/airdef-mod/assets/airdef/models/block/radar_truck.json +++ b/airdef-mod/assets/airdef/models/block/radar_truck.json @@ -1,30 +1,25 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21": "airdef:block/ru_khaki_s21", - "ru_khaki_s31": "airdef:block/ru_khaki_s31", - "ru_khaki_s23": "airdef:block/ru_khaki_s23", + "pilot_olive": "airdef:block/pilot_olive", "glass_cab": "airdef:block/glass_cab", - "ru_khaki": "airdef:block/ru_khaki", "dark_metal": "airdef:block/dark_metal", - "metal": "airdef:block/metal", + "bare_lamp": "airdef:block/bare_lamp", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "bare_lamp": "airdef:block/bare_lamp", - "bare_antenna": "airdef:block/bare_antenna", - "mk_ru": "airdef:block/mk_ru", - "particle": "airdef:block/ru_khaki_s21" + "metal": "airdef:block/metal", + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, - 10.0, + 21.0, + 9.0, 32.0 ], "faces": { @@ -33,36 +28,36 @@ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -71,7 +66,7 @@ 16, 16 ], - "texture": "#ru_khaki_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -80,224 +75,3896 @@ 16, 16 ], - "texture": "#ru_khaki_s23" - } - } - }, - { - "from": [ - -6.0, - 10.0, - -16.0 - ], - "to": [ - 3.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - } - } - }, - { - "from": [ - 13.0, - 10.0, - -16.0 - ], - "to": [ - 22.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki" - } - } - }, - { - "from": [ - 3.0, - 10.0, - -16.0 - ], - "to": [ - 13.0, - 15.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal" + "texture": "#pilot_olive" } } }, { "from": [ -5.0, - 10.0, - -6.0 + 9.0, + -16.0 ], "to": [ 21.0, - 13.0, - 4.0 + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.0 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 12.0, + 30.0 ], "faces": { "north": { @@ -307,7 +3974,7 @@ 16, 3.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -316,1693 +3983,57 @@ 16, 3.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, - 10.0, + 16, 3.0 ], - "texture": "#ru_khaki" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, - 10.0, + 16, 3.0 ], - "texture": "#ru_khaki" + "texture": "#pilot_olive" }, "up": { "uv": [ 0, 0, 16, - 10.0 + 16 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "down": { "uv": [ 0, 0, 16, - 10.0 + 16 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" } } }, { "from": [ -3.0, - 10.0, - 26.0 + 12.0, + 4.0 ], "to": [ 19.0, 14.0, - 32.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -8.0, - 0.0, 28.0 ], - "to": [ - -6.0, - 6.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - 22.0, - 0.0, - 28.0 - ], - "to": [ - 24.0, - 6.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.5, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 25.0, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.53, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 24.97, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.5, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 25.0, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.53, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 24.97, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.5, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 25.0, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.53, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 24.97, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - -4.5, - 11.0, - -16.0 - ], - "to": [ - -2.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 11.0, - -16.0 - ], - "to": [ - 20.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - -4.5, - 14.5, - -16.0 - ], - "to": [ - -2.5, - 15.5, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 14.5, - -16.0 - ], - "to": [ - 20.5, - 15.5, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - -1.0, - 5.5, - -16.0 - ], - "to": [ - 17.0, - 7.5, - -15.0 - ], "faces": { "north": { "uv": [ @@ -2026,7 +4057,7 @@ "uv": [ 0, 0, - 1.0, + 16, 2.0 ], "texture": "#dark_metal" @@ -2035,7 +4066,7 @@ "uv": [ 0, 0, - 1.0, + 16, 2.0 ], "texture": "#dark_metal" @@ -2045,7 +4076,7 @@ 0, 0, 16, - 1.0 + 16 ], "texture": "#dark_metal" }, @@ -2054,7 +4085,7 @@ 0, 0, 16, - 1.0 + 16 ], "texture": "#dark_metal" } @@ -2062,411 +4093,139 @@ }, { "from": [ - -10.5, - 7.0, - 4.0 - ], - "to": [ -7.0, - 10.0, - 14.0 + 0.0, + 23.0 + ], + "to": [ + -5.0, + 6.0, + 25.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "south": { "uv": [ 0, 0, - 3.5, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "east": { "uv": [ 0, 0, - 10.0, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "west": { "uv": [ 0, 0, - 10.0, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 3.5, - 10.0 + 2.0, + 2.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 3.5, - 10.0 + 2.0, + 2.0 ], - "texture": "#dark_metal" + "texture": "#metal" } } }, { "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ 23.0, - 7.0, - 4.0 - ], - "to": [ - 26.5, - 10.0, - 14.0 + 6.0, + 25.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "south": { "uv": [ 0, 0, - 3.5, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "east": { "uv": [ 0, 0, - 10.0, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "west": { "uv": [ 0, 0, - 10.0, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 3.5, - 10.0 + 2.0, + 2.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 3.5, - 10.0 + 2.0, + 2.0 ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -6.5, - 15.0, - -9.0 - ], - "to": [ - -5.5, - 22.0, - -8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "south": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "up": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna" - }, - "down": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - 21.5, - 15.0, - -9.0 - ], - "to": [ - 22.5, - 22.0, - -8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "south": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna" - }, - "up": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna" - }, - "down": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - 22.0, - 11.0, - -15.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" - } - } - }, - { - "from": [ - -6.119999999999997, - 11.0, - -15.0 - ], - "to": [ - -6.0, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" + "texture": "#metal" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/radar_truck_burnt.json b/airdef-mod/assets/airdef/models/block/radar_truck_burnt.json index 74f9cba..23c17aa 100644 --- a/airdef-mod/assets/airdef/models/block/radar_truck_burnt.json +++ b/airdef-mod/assets/airdef/models/block/radar_truck_burnt.json @@ -1,30 +1,25 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21_b": "airdef:block/ru_khaki_s21_b", - "ru_khaki_s31_b": "airdef:block/ru_khaki_s31_b", - "ru_khaki_s23_b": "airdef:block/ru_khaki_s23_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "glass_cab_b": "airdef:block/glass_cab_b", - "ru_khaki_b": "airdef:block/ru_khaki_b", - "dark_metal_b": "airdef:block/dark_metal_b", - "metal_b": "airdef:block/metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "bare_antenna_b": "airdef:block/bare_antenna_b", - "mk_ru_b": "airdef:block/mk_ru_b", - "particle": "airdef:block/ru_khaki_s21_b" + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, - 10.0, + 21.0, + 9.0, 32.0 ], "faces": { @@ -33,36 +28,36 @@ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_khaki_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -71,7 +66,7 @@ 16, 16 ], - "texture": "#ru_khaki_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -80,224 +75,2876 @@ 16, 16 ], - "texture": "#ru_khaki_s23_b" - } - } - }, - { - "from": [ - -6.0, - 10.0, - -16.0 - ], - "to": [ - 3.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab_b" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - } - } - }, - { - "from": [ - 13.0, - 10.0, - -16.0 - ], - "to": [ - 22.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab_b" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_khaki_b" - } - } - }, - { - "from": [ - 3.0, - 10.0, - -16.0 - ], - "to": [ - 13.0, - 15.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal_b" + "texture": "#pilot_olive_b" } } }, { "from": [ -5.0, - 10.0, - -6.0 + 9.0, + -16.0 ], "to": [ 21.0, - 13.0, - 4.0 + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 12.0, + 30.0 ], "faces": { "north": { @@ -307,7 +2954,7 @@ 16, 3.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -316,2021 +2963,181 @@ 16, 3.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, - 10.0, + 16, 3.0 ], - "texture": "#ru_khaki_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, - 10.0, + 16, 3.0 ], - "texture": "#ru_khaki_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ 0, 0, 16, - 10.0 + 16 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ 0, 0, 16, - 10.0 + 16 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" } } }, { "from": [ -3.0, - 10.0, - 26.0 + 12.0, + 4.0 ], "to": [ 19.0, 14.0, - 32.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -8.0, - 0.0, 28.0 ], - "to": [ - -6.0, - 6.0, - 31.0 - ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 6.0 + 16, + 2.0 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" }, "south": { "uv": [ 0, 0, - 2.0, - 6.0 + 16, + 2.0 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" }, "east": { "uv": [ 0, 0, - 3.0, - 6.0 + 16, + 2.0 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" }, "west": { "uv": [ 0, 0, - 3.0, - 6.0 + 16, + 2.0 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, - 2.0, - 3.0 + 16, + 16 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, - 2.0, - 3.0 + 16, + 16 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" } } }, { "from": [ - 22.0, + 21.0, 0.0, - 28.0 + 23.0 ], "to": [ - 24.0, - 6.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 6.0 - ], - "texture": "#metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal_b" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.5, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 25.0, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.53, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 24.97, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.5, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 25.0, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.53, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 24.97, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.5, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 25.0, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.53, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 24.97, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - -4.5, - 11.0, - -16.0 - ], - "to": [ - -2.5, - 12.6, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - 18.5, - 14.5, - -16.0 - ], - "to": [ - 20.5, - 15.5, - -15.6 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - -1.0, - 5.5, - -16.0 - ], - "to": [ - 17.0, - 7.5, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -10.5, - 7.0, - 4.0 - ], - "to": [ - -7.0, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ 23.0, - 7.0, - 4.0 - ], - "to": [ - 26.5, - 10.0, - 14.0 + 6.0, + 25.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "south": { "uv": [ 0, 0, - 3.5, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "east": { "uv": [ 0, 0, - 10.0, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "west": { "uv": [ 0, 0, - 10.0, - 3.0 + 2.0, + 6.0 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "up": { "uv": [ 0, 0, - 3.5, - 10.0 + 2.0, + 2.0 ], - "texture": "#dark_metal_b" + "texture": "#metal_b" }, "down": { "uv": [ 0, 0, - 3.5, - 10.0 + 2.0, + 2.0 ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -6.5, - 15.0, - -9.0 - ], - "to": [ - -5.5, - 22.0, - -8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "south": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "up": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna_b" - }, - "down": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna_b" - } - } - }, - { - "from": [ - 21.5, - 15.0, - -9.0 - ], - "to": [ - 22.5, - 22.0, - -8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "south": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 7.0 - ], - "texture": "#bare_antenna_b" - }, - "up": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna_b" - }, - "down": { - "uv": [ - 0, - 0, - 1.0, - 1.0 - ], - "texture": "#bare_antenna_b" - } - } - }, - { - "from": [ - 22.0, - 11.0, - -15.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - } - } - }, - { - "from": [ - -6.119999999999997, - 11.0, - -15.0 - ], - "to": [ - -6.0, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" + "texture": "#metal_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/rm48u.json b/airdef-mod/assets/airdef/models/block/rm48u.json new file mode 100644 index 0000000..7bb132a --- /dev/null +++ b/airdef-mod/assets/airdef/models/block/rm48u.json @@ -0,0 +1,1886 @@ +{ + "ambientocclusion": false, + "textures": { + "missile_ru": "airdef:block/missile_ru", + "rm_ru": "airdef:block/rm_ru", + "rm_ru_t": "airdef:block/rm_ru_t", + "bare_nozzle": "airdef:block/bare_nozzle", + "glow_ember": "airdef:block/glow_ember", + "missile_grey": "airdef:block/missile_grey", + "particle": "airdef:block/missile_ru" + }, + "elements": [ + { + "from": [ + 7.211565172735556, + 7.211565172735556, + -16.0 + ], + "to": [ + 8.788434827264444, + 8.788434827264444, + -13.666666666666666 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.333333333333334, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.333333333333334, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 2.333333333333334 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 2.333333333333334 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.211565172735556, + 7.211565172735556, + -15.97 + ], + "to": [ + 8.788434827264444, + 8.788434827264444, + -13.696666666666665 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.273333333333335, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.273333333333335, + 1.5768696545288883 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 2.273333333333335 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 1.5768696545288883, + 2.273333333333335 + ], + "texture": "#missile_ru" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -14.833333333333332 + ] + } + }, + { + "from": [ + 6.715574897504718, + 6.715574897504718, + -13.666666666666666 + ], + "to": [ + 9.284425102495282, + 9.284425102495282, + -11.333333333333332 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.333333333333334, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.333333333333334, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.333333333333334 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.333333333333334 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 6.715574897504718, + 6.715574897504718, + -13.636666666666667 + ], + "to": [ + 9.284425102495282, + 9.284425102495282, + -11.363333333333332 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.273333333333335, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.273333333333335, + 2.5688502049905644 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.273333333333335 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 2.5688502049905644, + 2.273333333333335 + ], + "texture": "#missile_ru" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -12.5 + ] + } + }, + { + "from": [ + 6.343421775742739, + 6.343421775742739, + -11.333333333333332 + ], + "to": [ + 9.656578224257261, + 9.656578224257261, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.333333333333332, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.333333333333332, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 2.333333333333332 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 2.333333333333332 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 6.343421775742739, + 6.343421775742739, + -11.303333333333333 + ], + "to": [ + 9.656578224257261, + 9.656578224257261, + -9.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.2733333333333334, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.2733333333333334, + 3.3131564485145226 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 2.2733333333333334 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.3131564485145226, + 2.2733333333333334 + ], + "texture": "#missile_ru" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -10.166666666666666 + ] + } + }, + { + "from": [ + 6.2445900000000005, + 6.2445900000000005, + -9.0 + ], + "to": [ + 9.75541, + 9.75541, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.510819999999999, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.510819999999999, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.510819999999999, + 16 + ], + "texture": "#rm_ru_t" + }, + "down": { + "uv": [ + 0, + 0, + 3.510819999999999, + 16 + ], + "texture": "#rm_ru_t" + } + } + }, + { + "from": [ + 6.2445900000000005, + 6.2445900000000005, + -8.97 + ], + "to": [ + 9.75541, + 9.75541, + 25.97 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.510819999999999, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.510819999999999, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.510819999999999 + ], + "texture": "#rm_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.510819999999999, + 16 + ], + "texture": "#rm_ru_t" + }, + "down": { + "uv": [ + 0, + 0, + 3.510819999999999, + 16 + ], + "texture": "#rm_ru_t" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + 8.5 + ] + } + }, + { + "from": [ + 9.71, + 7.65, + 18.0 + ], + "to": [ + 13.120000000000001, + 8.35, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 8.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 8.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 2.879999999999999, + 7.65, + 18.0 + ], + "to": [ + 6.289999999999999, + 8.35, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 8.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 8.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 13.120000000000001, + 7.65, + 22.0 + ], + "to": [ + 15.91, + 8.35, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.789999999999999, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 2.789999999999999, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 2.789999999999999, + 4.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 2.789999999999999, + 4.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 0.08999999999999986, + 7.65, + 22.0 + ], + "to": [ + 2.879999999999999, + 8.35, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.789999999999999, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 2.789999999999999, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 2.789999999999999, + 4.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 2.789999999999999, + 4.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 9.71, + 18.0 + ], + "to": [ + 8.35, + 13.120000000000001, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.41 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.41 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.41 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.41 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 13.120000000000001, + 22.0 + ], + "to": [ + 8.35, + 15.91, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.789999999999999 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.789999999999999 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 2.789999999999999 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 2.789999999999999 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 2.8799999999999994, + 18.0 + ], + "to": [ + 8.35, + 6.29, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.4100000000000006 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.4100000000000006 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.4100000000000006 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.4100000000000006 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 0.08999999999999986, + 22.0 + ], + "to": [ + 8.35, + 2.8799999999999994, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.7899999999999996 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.7899999999999996 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 2.7899999999999996 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 2.7899999999999996 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 9.71, + 7.65, + -6.0 + ], + "to": [ + 11.085, + 8.35, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.375, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 1.375, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 1.375, + 3.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 1.375, + 3.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 4.914999999999999, + 7.65, + -6.0 + ], + "to": [ + 6.289999999999999, + 8.35, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.375, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 1.375, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 1.375, + 3.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 1.375, + 3.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 11.085, + 7.65, + -5.0 + ], + "to": [ + 12.21, + 8.35, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.125, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 1.125, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 1.125, + 2.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 1.125, + 2.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 3.789999999999999, + 7.65, + -5.0 + ], + "to": [ + 4.914999999999999, + 8.35, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.125, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 1.125, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.6999999999999993 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 1.125, + 2.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 1.125, + 2.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 9.71, + -6.0 + ], + "to": [ + 8.35, + 11.085, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.375 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.375 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 1.375 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 1.375 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 11.085, + -5.0 + ], + "to": [ + 8.35, + 12.21, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.125 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.125 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 1.125 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 1.125 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 4.915, + -6.0 + ], + "to": [ + 8.35, + 6.29, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.375 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.375 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 1.375 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 1.375 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 3.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 7.65, + 3.79, + -5.0 + ], + "to": [ + 8.35, + 4.915, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.125 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.125 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 1.125 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 1.125 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.0 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 2.0 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 6.48, + 6.48, + 26.0 + ], + "to": [ + 9.52, + 9.52, + 27.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.039999999999999, + 3.039999999999999 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 3.039999999999999, + 3.039999999999999 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.6000000000000014, + 3.039999999999999 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.6000000000000014, + 3.039999999999999 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 3.039999999999999, + 1.6000000000000014 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 3.039999999999999, + 1.6000000000000014 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + 6.955, + 6.955, + 27.6 + ], + "to": [ + 9.045, + 9.045, + 27.9 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.09, + 2.09 + ], + "texture": "#glow_ember" + }, + "south": { + "uv": [ + 0, + 0, + 2.09, + 2.09 + ], + "texture": "#glow_ember" + }, + "east": { + "uv": [ + 0, + 0, + 0.29999999999999716, + 2.09 + ], + "texture": "#glow_ember" + }, + "west": { + "uv": [ + 0, + 0, + 0.29999999999999716, + 2.09 + ], + "texture": "#glow_ember" + }, + "up": { + "uv": [ + 0, + 0, + 2.09, + 0.29999999999999716 + ], + "texture": "#glow_ember" + }, + "down": { + "uv": [ + 0, + 0, + 2.09, + 0.29999999999999716 + ], + "texture": "#glow_ember" + } + } + }, + { + "from": [ + 5.9, + 5.9, + 2.0 + ], + "to": [ + 10.1, + 10.1, + 3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 4.199999999999999 + ], + "texture": "#missile_grey" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 4.199999999999999 + ], + "texture": "#missile_grey" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 4.199999999999999 + ], + "texture": "#missile_grey" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 4.199999999999999 + ], + "texture": "#missile_grey" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.0 + ], + "texture": "#missile_grey" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.0 + ], + "texture": "#missile_grey" + } + } + } + ] +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/models/block/s400_chassis.json b/airdef-mod/assets/airdef/models/block/s400_chassis.json index c7910f3..8ac910b 100644 --- a/airdef-mod/assets/airdef/models/block/s400_chassis.json +++ b/airdef-mod/assets/airdef/models/block/s400_chassis.json @@ -1,30 +1,24 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21": "airdef:block/ru_green_s21", - "ru_green_s31": "airdef:block/ru_green_s31", - "ru_green_s23": "airdef:block/ru_green_s23", + "pilot_olive": "airdef:block/pilot_olive", "glass_cab": "airdef:block/glass_cab", - "ru_green": "airdef:block/ru_green", "dark_metal": "airdef:block/dark_metal", - "metal": "airdef:block/metal", "tire": "airdef:block/tire", "hub": "airdef:block/hub", "bare_lamp": "airdef:block/bare_lamp", - "bare_antenna": "airdef:block/bare_antenna", - "mk_ru": "airdef:block/mk_ru", - "particle": "airdef:block/ru_green_s21" + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -6.0, + -2.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, - 10.0, + 18.0, + 9.0, 32.0 ], "faces": { @@ -33,36 +27,36 @@ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -71,7 +65,7 @@ 16, 16 ], - "texture": "#ru_green_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -80,301 +74,709 @@ 16, 16 ], - "texture": "#ru_green_s23" - } - } - }, - { - "from": [ - -6.0, - 10.0, - -16.0 - ], - "to": [ - 3.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - } - } - }, - { - "from": [ - 13.0, - 10.0, - -16.0 - ], - "to": [ - 22.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green" - } - } - }, - { - "from": [ - 3.0, - 10.0, - -16.0 - ], - "to": [ - 13.0, - 15.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -5.0, - 10.0, - -6.0 - ], - "to": [ - 21.0, - 13.0, - 4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 3.0 - ], - "texture": "#ru_green_s21" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 3.0 - ], - "texture": "#ru_green_s21" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#ru_green" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#ru_green" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#ru_green_s21" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" } } }, { "from": [ -3.0, - 10.0, - 26.0 + 9.0, + -16.0 ], "to": [ 19.0, + 15.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.5, + 15.0, + -15.0 + ], + "to": [ + 18.5, + 19.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 15.2, + -15.95 + ], + "to": [ + 7.5, + 18.0, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 15.2, + -15.95 + ], + "to": [ + 18.0, + 18.0, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -3.06, 14.0, + -14.0 + ], + "to": [ + -2.95, + 17.5, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 18.95, + 14.0, + -14.0 + ], + "to": [ + 19.06, + 17.5, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -2.5, + 19.0, + -15.0 + ], + "to": [ + 18.5, + 19.4, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 9.0, + -6.5 + ], + "to": [ + 18.0, + 11.0, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 0.0, + 9.1, + 7.0 + ], + "to": [ + 16.0, + 10.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 28.0 + ], + "to": [ + 19.0, + 12.0, 32.0 ], "faces": { "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { "uv": [ 0, 0, 16, 4.0 ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.5, + 7.0, + -16.0 + ], + "to": [ + 19.5, + 8.0, + -15.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], "texture": "#dark_metal" }, "south": { @@ -382,7 +784,7 @@ 0, 0, 16, - 4.0 + 1.0 ], "texture": "#dark_metal" }, @@ -390,8 +792,8 @@ "uv": [ 0, 0, - 6.0, - 4.0 + 0.5999999999999996, + 1.0 ], "texture": "#dark_metal" }, @@ -399,8 +801,8 @@ "uv": [ 0, 0, - 6.0, - 4.0 + 0.5999999999999996, + 1.0 ], "texture": "#dark_metal" }, @@ -409,7 +811,7 @@ 0, 0, 16, - 6.0 + 0.5999999999999996 ], "texture": "#dark_metal" }, @@ -418,7 +820,7 @@ 0, 0, 16, - 6.0 + 0.5999999999999996 ], "texture": "#dark_metal" } @@ -426,167 +828,847 @@ }, { "from": [ - -8.0, + -6.0, 0.0, - 28.0 + -12.959999999999999 ], "to": [ - -6.0, - 6.0, - 31.0 + -3.0, + 1.6400000000000001, + -8.040000000000001 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 6.0 + 3.0, + 1.6400000000000001 ], - "texture": "#metal" + "texture": "#tire" }, "south": { "uv": [ 0, 0, - 2.0, - 6.0 + 3.0, + 1.6400000000000001 ], - "texture": "#metal" + "texture": "#tire" }, "east": { "uv": [ 0, 0, - 3.0, - 6.0 + 4.919999999999998, + 1.6400000000000001 ], - "texture": "#metal" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 3.0, - 6.0 + 4.919999999999998, + 1.6400000000000001 ], - "texture": "#metal" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 2.0, - 3.0 + 3.0, + 4.919999999999998 ], - "texture": "#metal" + "texture": "#tire" }, "down": { "uv": [ 0, 0, - 2.0, - 3.0 + 3.0, + 4.919999999999998 ], - "texture": "#metal" + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 0.0, + -12.959999999999999 + ], + "to": [ + 22.0, + 1.6400000000000001, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + -14.272 + ], + "to": [ + -3.0, + 3.28, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + -14.272 + ], + "to": [ + 22.0, + 3.28, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -14.6 + ], + "to": [ + -3.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -14.6 + ], + "to": [ + 22.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -14.272 + ], + "to": [ + -3.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -14.272 + ], + "to": [ + 22.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + -3.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + 22.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + -12.7 + ], + "to": [ + -6.0, + 6.3, + -8.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" } } }, { "from": [ 22.0, + 1.8999999999999995, + -12.7 + ], + "to": [ + 22.05, + 6.3, + -8.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -6.0, 0.0, - 28.0 + -3.9599999999999995 ], "to": [ - 24.0, - 6.0, - 31.0 + -3.0, + 1.6400000000000001, + 0.9599999999999995 ], "faces": { "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal" - }, - "east": { "uv": [ 0, 0, 3.0, - 6.0 + 1.6400000000000001 ], - "texture": "#metal" + "texture": "#tire" }, - "west": { + "south": { "uv": [ 0, 0, 3.0, - 6.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.5, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 + 1.6400000000000001 ], "texture": "#tire" }, @@ -594,26 +1676,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" }, @@ -621,8 +1703,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" } @@ -630,22 +1712,22 @@ }, { "from": [ - 21.5, - 0.3424499999999995, - -14.65755 + 19.0, + 0.0, + -3.9599999999999995 ], "to": [ - 25.0, - 8.65755, - -6.3424499999999995 + 22.0, + 1.6400000000000001, + 0.9599999999999995 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -653,8 +1735,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -662,26 +1744,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" }, @@ -689,8 +1771,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" } @@ -698,22 +1780,22 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - -14.65755 + -6.0, + 1.6400000000000001, + -5.272 ], "to": [ - -5.53, - 8.65755, - -6.3424499999999995 + -3.0, + 3.28, + 2.272 ], "faces": { "north": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -721,8 +1803,8 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -730,26 +1812,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" }, @@ -757,40 +1839,31 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -10.5 - ] } }, { "from": [ - 21.53, - 0.3424499999999995, - -14.65755 + 19.0, + 1.6400000000000001, + -5.272 ], "to": [ - 24.97, - 8.65755, - -6.3424499999999995 + 22.0, + 3.28, + 2.272 ], "faces": { "north": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -798,8 +1871,8 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -807,26 +1880,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" }, @@ -834,40 +1907,31 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -10.5 - ] } }, { "from": [ - -9.0, - 0.3424499999999995, - -5.6575500000000005 + -6.0, + 3.28, + -5.6 ], "to": [ - -5.5, - 8.65755, - 2.6575500000000005 + -3.0, + 4.92, + 2.5999999999999996 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -875,8 +1939,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -884,26 +1948,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.2, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.2, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 8.2 ], "texture": "#tire" }, @@ -911,8 +1975,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 8.2 ], "texture": "#tire" } @@ -920,22 +1984,22 @@ }, { "from": [ - 21.5, - 0.3424499999999995, - -5.6575500000000005 + 19.0, + 3.28, + -5.6 ], "to": [ - 25.0, - 8.65755, - 2.6575500000000005 + 22.0, + 4.92, + 2.5999999999999996 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -943,8 +2007,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -952,26 +2016,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.2, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.2, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 8.2 ], "texture": "#tire" }, @@ -979,8 +2043,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 8.2 ], "texture": "#tire" } @@ -988,22 +2052,22 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - -5.6575500000000005 + -6.0, + 4.92, + -5.272 ], "to": [ - -5.53, - 8.65755, - 2.6575500000000005 + -3.0, + 6.559999999999999, + 2.272 ], "faces": { "north": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6399999999999988 ], "texture": "#tire" }, @@ -1011,8 +2075,8 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6399999999999988 ], "texture": "#tire" }, @@ -1020,26 +2084,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999988 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999988 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" }, @@ -1047,40 +2111,31 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -1.5 - ] } }, { "from": [ - 21.53, - 0.3424499999999995, - -5.6575500000000005 + 19.0, + 4.92, + -5.272 ], "to": [ - 24.97, - 8.65755, - 2.6575500000000005 + 22.0, + 6.559999999999999, + 2.272 ], "faces": { "north": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6399999999999988 ], "texture": "#tire" }, @@ -1088,8 +2143,8 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6399999999999988 ], "texture": "#tire" }, @@ -1097,26 +2152,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999988 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.5440000000000005, + 1.6399999999999988 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" }, @@ -1124,40 +2179,31 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 7.5440000000000005 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -1.5 - ] } }, { "from": [ - -9.0, - 0.3424499999999995, - 12.34245 + -6.0, + 6.559999999999999, + -3.9599999999999995 ], "to": [ - -5.5, - 8.65755, - 20.65755 + -3.0, + 8.2, + 0.9599999999999995 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000006 ], "texture": "#tire" }, @@ -1165,8 +2211,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000006 ], "texture": "#tire" }, @@ -1174,26 +2220,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000006 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000006 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" }, @@ -1201,8 +2247,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" } @@ -1210,22 +2256,22 @@ }, { "from": [ - 21.5, - 0.3424499999999995, - 12.34245 + 19.0, + 6.559999999999999, + -3.9599999999999995 ], "to": [ - 25.0, - 8.65755, - 20.65755 + 22.0, + 8.2, + 0.9599999999999995 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000006 ], "texture": "#tire" }, @@ -1233,8 +2279,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6400000000000006 ], "texture": "#tire" }, @@ -1242,26 +2288,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000006 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.919999999999999, + 1.6400000000000006 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" }, @@ -1269,8 +2315,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 4.919999999999999 ], "texture": "#tire" } @@ -1278,22 +2324,158 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - 12.34245 + -6.05, + 1.8999999999999995, + -3.7 ], "to": [ - -5.53, - 8.65755, - 20.65755 + -6.0, + 6.3, + 0.7000000000000002 ], "faces": { "north": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + -3.7 + ], + "to": [ + 22.05, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -6.0, + 0.0, + 14.040000000000001 + ], + "to": [ + -3.0, + 1.6400000000000001, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1301,8 +2483,8 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1310,26 +2492,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.92, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.92, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 4.92 ], "texture": "#tire" }, @@ -1337,40 +2519,31 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 4.92 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 16.5 - ] } }, { "from": [ - 21.53, - 0.3424499999999995, - 12.34245 + 19.0, + 0.0, + 14.040000000000001 ], "to": [ - 24.97, - 8.65755, - 20.65755 + 22.0, + 1.6400000000000001, + 18.96 ], "faces": { "north": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1378,8 +2551,8 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1387,26 +2560,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.92, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 4.92, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 4.92 ], "texture": "#tire" }, @@ -1414,40 +2587,31 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 4.92 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 16.5 - ] } }, { "from": [ - -9.0, - 0.3424499999999995, - 21.34245 + -6.0, + 1.6400000000000001, + 12.728 ], "to": [ - -5.5, - 8.65755, - 29.65755 + -3.0, + 3.28, + 20.272 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -1455,8 +2619,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -1464,26 +2628,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.543999999999999, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.543999999999999, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 7.543999999999999 ], "texture": "#tire" }, @@ -1491,8 +2655,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 7.543999999999999 ], "texture": "#tire" } @@ -1500,22 +2664,22 @@ }, { "from": [ - 21.5, - 0.3424499999999995, - 21.34245 + 19.0, + 1.6400000000000001, + 12.728 ], "to": [ - 25.0, - 8.65755, - 29.65755 + 22.0, + 3.28, + 20.272 ], "faces": { "north": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -1523,8 +2687,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 1.6399999999999997 ], "texture": "#tire" }, @@ -1532,26 +2696,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.543999999999999, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 7.543999999999999, + 1.6399999999999997 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 7.543999999999999 ], "texture": "#tire" }, @@ -1559,8 +2723,8 @@ "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 3.0, + 7.543999999999999 ], "texture": "#tire" } @@ -1568,22 +2732,22 @@ }, { "from": [ - -8.97, - 0.3424499999999995, - 21.34245 + -6.0, + 3.28, + 12.4 ], "to": [ - -5.53, - 8.65755, - 29.65755 + -3.0, + 4.92, + 20.6 ], "faces": { "north": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1591,8 +2755,8 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1600,26 +2764,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.200000000000001, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.200000000000001, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 8.200000000000001 ], "texture": "#tire" }, @@ -1627,40 +2791,31 @@ "uv": [ 0, 0, - 3.4400000000000004, - 8.315100000000001 + 3.0, + 8.200000000000001 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 25.5 - ] } }, { "from": [ - 21.53, - 0.3424499999999995, - 21.34245 + 19.0, + 3.28, + 12.4 ], "to": [ - 24.97, - 8.65755, - 29.65755 + 22.0, + 4.92, + 20.6 ], "faces": { "north": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1668,8 +2823,8 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 1.6400000000000001 ], "texture": "#tire" }, @@ -1677,26 +2832,26 @@ "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.200000000000001, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.200000000000001, + 1.6400000000000001 ], - "texture": "#hub" + "texture": "#tire" }, "up": { "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 8.200000000000001 ], "texture": "#tire" }, @@ -1704,176 +2859,1391 @@ "uv": [ 0, 0, - 3.4399999999999977, - 8.315100000000001 + 3.0, + 8.200000000000001 ], "texture": "#tire" } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 25.5 - ] } }, { "from": [ - -4.5, - 11.0, - -16.0 + -6.0, + 4.92, + 12.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + 12.728 + ], + "to": [ + 22.0, + 6.559999999999999, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + -3.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + 22.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + 14.3 + ], + "to": [ + -6.0, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 14.3 + ], + "to": [ + 22.05, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -6.0, + 0.0, + 23.04 + ], + "to": [ + -3.0, + 1.6400000000000001, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 0.0, + 23.04 + ], + "to": [ + 22.0, + 1.6400000000000001, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + -3.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + 22.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 21.4 + ], + "to": [ + -3.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 21.4 + ], + "to": [ + 22.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 21.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + 21.728 + ], + "to": [ + 22.0, + 6.559999999999999, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 23.04 + ], + "to": [ + -3.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 23.04 + ], + "to": [ + 22.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + 23.3 + ], + "to": [ + -6.0, + 6.3, + 27.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 23.3 + ], + "to": [ + 22.05, + 6.3, + 27.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -5.0, + 7.0, + 4.0 ], "to": [ -2.5, - 12.6, - -15.6 + 10.0, + 12.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 2.5, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 2.5, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 0.40000000000000036, - 1.5999999999999996 + 8.0, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 0.40000000000000036, - 1.5999999999999996 + 8.0, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 2.5, + 8.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 2.5, + 8.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" } } }, { "from": [ 18.5, - 11.0, - -16.0 + 7.0, + 4.0 ], "to": [ - 20.5, - 12.6, - -15.6 + 21.0, + 10.0, + 12.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 2.5, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 2.5, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 0.40000000000000036, - 1.5999999999999996 + 8.0, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 0.40000000000000036, - 1.5999999999999996 + 8.0, + 3.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 2.5, + 8.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 2.5, + 8.0 ], - "texture": "#bare_lamp" + "texture": "#dark_metal" } } }, { "from": [ -4.5, - 14.5, - -16.0 + 10.0, + -14.0 ], "to": [ - -2.5, - 15.5, - -15.6 + -3.0, + 10.8, + -12.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.0 + 1.5, + 0.8000000000000007 ], "texture": "#bare_lamp" }, @@ -1881,8 +4251,8 @@ "uv": [ 0, 0, - 2.0, - 1.0 + 1.5, + 0.8000000000000007 ], "texture": "#bare_lamp" }, @@ -1890,8 +4260,8 @@ "uv": [ 0, 0, - 0.40000000000000036, - 1.0 + 2.0, + 0.8000000000000007 ], "texture": "#bare_lamp" }, @@ -1899,8 +4269,8 @@ "uv": [ 0, 0, - 0.40000000000000036, - 1.0 + 2.0, + 0.8000000000000007 ], "texture": "#bare_lamp" }, @@ -1908,8 +4278,8 @@ "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 1.5, + 2.0 ], "texture": "#bare_lamp" }, @@ -1917,8 +4287,8 @@ "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 1.5, + 2.0 ], "texture": "#bare_lamp" } @@ -1926,49 +4296,49 @@ }, { "from": [ - 18.5, - 14.5, - -16.0 + 19.0, + 10.0, + -14.0 ], "to": [ 20.5, - 15.5, - -15.6 + 10.8, + -12.0 ], "faces": { "north": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { "uv": [ 0, 0, 2.0, - 1.0 + 0.8000000000000007 ], "texture": "#bare_lamp" }, - "south": { + "west": { "uv": [ 0, 0, 2.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.40000000000000036, - 1.0 + 0.8000000000000007 ], "texture": "#bare_lamp" }, @@ -1976,236 +4346,32 @@ "uv": [ 0, 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.40000000000000036 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - -1.0, - 5.5, - -16.0 - ], - "to": [ - 17.0, - 7.5, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, + 1.5, 2.0 ], - "texture": "#dark_metal" + "texture": "#bare_lamp" }, - "south": { + "down": { "uv": [ 0, 0, - 16, + 1.5, 2.0 ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#dark_metal" + "texture": "#bare_lamp" } } }, { "from": [ - -10.5, - 7.0, - 4.0 + -4.0, + 12.0, + -8.2 ], "to": [ - -7.0, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 23.0, - 7.0, - 4.0 - ], - "to": [ - 26.5, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -6.5, - 15.0, - -9.0 - ], - "to": [ - -5.5, - 22.0, - -8.0 + -3.0, + 12.6, + -7.8 ], "faces": { "north": { @@ -2213,67 +4379,67 @@ 0, 0, 1.0, - 7.0 + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, 1.0, - 7.0 + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 1.0, - 7.0 + 0.39999999999999947, + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 1.0, - 7.0 + 0.39999999999999947, + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, 1.0, - 1.0 + 0.39999999999999947 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, 1.0, - 1.0 + 0.39999999999999947 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" } } }, { "from": [ - 21.5, - 15.0, - -9.0 + 19.0, + 12.0, + -8.2 ], "to": [ - 22.5, - 22.0, - -8.0 + 20.0, + 12.6, + -7.8 ], "faces": { "north": { @@ -2281,192 +4447,56 @@ 0, 0, 1.0, - 7.0 + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "south": { "uv": [ 0, 0, 1.0, - 7.0 + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "east": { "uv": [ 0, 0, - 1.0, - 7.0 + 0.39999999999999947, + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "west": { "uv": [ 0, 0, - 1.0, - 7.0 + 0.39999999999999947, + 0.5999999999999996 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, 1.0, - 1.0 + 0.39999999999999947 ], - "texture": "#bare_antenna" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, 1.0, - 1.0 + 0.39999999999999947 ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - 22.0, - 11.0, - -15.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" - } - } - }, - { - "from": [ - -6.119999999999997, - 11.0, - -15.0 - ], - "to": [ - -6.0, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru" + "texture": "#dark_metal" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/s400_chassis_burnt.json b/airdef-mod/assets/airdef/models/block/s400_chassis_burnt.json index 0678725..1a514c1 100644 --- a/airdef-mod/assets/airdef/models/block/s400_chassis_burnt.json +++ b/airdef-mod/assets/airdef/models/block/s400_chassis_burnt.json @@ -1,29 +1,24 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21_b": "airdef:block/ru_green_s21_b", - "ru_green_s31_b": "airdef:block/ru_green_s31_b", - "ru_green_s23_b": "airdef:block/ru_green_s23_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "glass_cab_b": "airdef:block/glass_cab_b", - "ru_green_b": "airdef:block/ru_green_b", "dark_metal_b": "airdef:block/dark_metal_b", - "metal_b": "airdef:block/metal_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", "bare_lamp_b": "airdef:block/bare_lamp_b", - "mk_ru_b": "airdef:block/mk_ru_b", - "particle": "airdef:block/ru_green_s21_b" + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -6.0, + -2.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, - 10.0, + 18.0, + 9.0, 32.0 ], "faces": { @@ -32,36 +27,36 @@ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 5.0 + 4.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -70,7 +65,7 @@ 16, 16 ], - "texture": "#ru_green_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -79,301 +74,573 @@ 16, 16 ], - "texture": "#ru_green_s23_b" - } - } - }, - { - "from": [ - -6.0, - 10.0, - -16.0 - ], - "to": [ - 3.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab_b" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - } - } - }, - { - "from": [ - 13.0, - 10.0, - -16.0 - ], - "to": [ - 22.0, - 19.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#glass_cab_b" - }, - "south": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "east": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "west": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "up": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - }, - "down": { - "uv": [ - 0, - 0, - 9.0, - 9.0 - ], - "texture": "#ru_green_b" - } - } - }, - { - "from": [ - 3.0, - 10.0, - -16.0 - ], - "to": [ - 13.0, - 15.0, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 5.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 10.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -5.0, - 10.0, - -6.0 - ], - "to": [ - 21.0, - 13.0, - 4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 3.0 - ], - "texture": "#ru_green_s21_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 3.0 - ], - "texture": "#ru_green_s21_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#ru_green_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#ru_green_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#ru_green_s21_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 10.0 - ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" } } }, { "from": [ -3.0, - 10.0, - 26.0 + 9.0, + -16.0 ], "to": [ 19.0, + 15.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.5, + 15.0, + -15.0 + ], + "to": [ + 18.5, + 19.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 8.5, + 15.2, + -15.95 + ], + "to": [ + 18.0, + 18.0, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 18.95, 14.0, + -14.0 + ], + "to": [ + 19.06, + 17.5, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -2.5, + 19.0, + -15.0 + ], + "to": [ + 18.5, + 19.4, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.0, + 9.0, + -6.5 + ], + "to": [ + 18.0, + 11.0, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 0.0, + 9.1, + 7.0 + ], + "to": [ + 16.0, + 10.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 28.0 + ], + "to": [ + 19.0, + 12.0, 32.0 ], "faces": { "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { "uv": [ 0, 0, 16, 4.0 ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.5, + 7.0, + -16.0 + ], + "to": [ + 19.5, + 8.0, + -15.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], "texture": "#dark_metal_b" }, "south": { @@ -381,7 +648,7 @@ 0, 0, 16, - 4.0 + 1.0 ], "texture": "#dark_metal_b" }, @@ -389,8 +656,8 @@ "uv": [ 0, 0, - 6.0, - 4.0 + 0.5999999999999996, + 1.0 ], "texture": "#dark_metal_b" }, @@ -398,8 +665,8 @@ "uv": [ 0, 0, - 6.0, - 4.0 + 0.5999999999999996, + 1.0 ], "texture": "#dark_metal_b" }, @@ -408,7 +675,7 @@ 0, 0, 16, - 6.0 + 0.5999999999999996 ], "texture": "#dark_metal_b" }, @@ -417,1326 +684,2478 @@ 0, 0, 16, - 6.0 + 0.5999999999999996 ], "texture": "#dark_metal_b" } } }, + { + "from": [ + 19.0, + 0.0, + -12.959999999999999 + ], + "to": [ + 22.0, + 1.6400000000000001, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -14.6 + ], + "to": [ + -3.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -14.6 + ], + "to": [ + 22.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -14.272 + ], + "to": [ + -3.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -14.272 + ], + "to": [ + 22.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + -3.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + 22.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -5.6 + ], + "to": [ + -3.0, + 4.92, + 2.5999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -5.6 + ], + "to": [ + 22.0, + 4.92, + 2.5999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -5.272 + ], + "to": [ + -3.0, + 6.559999999999999, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -5.272 + ], + "to": [ + 22.0, + 6.559999999999999, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -3.9599999999999995 + ], + "to": [ + -3.0, + 8.2, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -3.9599999999999995 + ], + "to": [ + 22.0, + 8.2, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + -3.7 + ], + "to": [ + -6.0, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + } + } + }, { "from": [ 22.0, + 1.8999999999999995, + -3.7 + ], + "to": [ + 22.05, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 19.0, 0.0, - 28.0 + 14.040000000000001 ], "to": [ - 24.0, - 6.0, - 31.0 + 22.0, + 1.6400000000000001, + 18.96 ], "faces": { "north": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#metal_b" - }, - "east": { "uv": [ 0, 0, 3.0, - 6.0 + 1.6400000000000001 ], - "texture": "#metal_b" + "texture": "#tire_b" }, - "west": { + "south": { "uv": [ 0, 0, 3.0, - 6.0 + 1.6400000000000001 ], - "texture": "#metal_b" + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire_b" }, "up": { "uv": [ 0, 0, - 2.0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 12.728 + ], + "to": [ + -3.0, + 3.28, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 12.728 + ], + "to": [ + 22.0, + 3.28, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 12.4 + ], + "to": [ + -3.0, + 4.92, + 20.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 12.4 + ], + "to": [ + 22.0, + 4.92, + 20.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 12.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + -3.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + 22.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + 14.3 + ], + "to": [ + -6.0, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 14.3 + ], + "to": [ + 22.05, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -6.0, + 0.0, + 23.04 + ], + "to": [ + -3.0, + 1.6400000000000001, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + -3.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + 22.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 21.4 + ], + "to": [ + -3.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 21.4 + ], + "to": [ + 22.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 21.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 23.04 + ], + "to": [ + -3.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 23.04 + ], + "to": [ + 22.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 23.3 + ], + "to": [ + 22.05, + 6.3, + 27.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -5.0, + 7.0, + 4.0 + ], + "to": [ + -2.5, + 10.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, 3.0 ], - "texture": "#metal_b" + "texture": "#dark_metal_b" }, - "down": { + "south": { "uv": [ 0, 0, - 2.0, + 2.5, 3.0 ], - "texture": "#metal_b" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.5, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" + "texture": "#dark_metal_b" }, "east": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.0, + 3.0 ], - "texture": "#hub_b" + "texture": "#dark_metal_b" }, "west": { "uv": [ 0, 0, - 8.315100000000001, - 8.315100000000001 + 8.0, + 3.0 ], - "texture": "#hub_b" + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 2.5, + 8.0 ], - "texture": "#tire_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, - 3.5, - 8.315100000000001 + 2.5, + 8.0 ], - "texture": "#tire_b" + "texture": "#dark_metal_b" } } }, - { - "from": [ - 21.5, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 25.0, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -14.65755 - ], - "to": [ - -5.53, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -14.65755 - ], - "to": [ - 24.97, - 8.65755, - -6.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -10.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -5.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 2.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -1.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.5, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 25.0, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 12.34245 - ], - "to": [ - -5.53, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 12.34245 - ], - "to": [ - 24.97, - 8.65755, - 20.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 16.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.5, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 25.0, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 21.34245 - ], - "to": [ - -5.53, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 25.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 21.34245 - ], - "to": [ - 24.97, - 8.65755, - 29.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 25.5 - ] - } - }, { "from": [ 18.5, - 11.0, - -16.0 + 7.0, + 4.0 ], "to": [ - 20.5, - 12.6, - -15.6 + 21.0, + 10.0, + 12.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 2.5, + 3.0 ], - "texture": "#bare_lamp_b" + "texture": "#dark_metal_b" }, "south": { "uv": [ 0, 0, - 2.0, - 1.5999999999999996 + 2.5, + 3.0 ], - "texture": "#bare_lamp_b" + "texture": "#dark_metal_b" }, "east": { "uv": [ 0, 0, - 0.40000000000000036, - 1.5999999999999996 + 8.0, + 3.0 ], - "texture": "#bare_lamp_b" + "texture": "#dark_metal_b" }, "west": { "uv": [ 0, 0, - 0.40000000000000036, - 1.5999999999999996 + 8.0, + 3.0 ], - "texture": "#bare_lamp_b" + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 2.5, + 8.0 ], - "texture": "#bare_lamp_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 2.5, + 8.0 ], - "texture": "#bare_lamp_b" + "texture": "#dark_metal_b" } } }, { "from": [ -4.5, - 14.5, - -16.0 + 10.0, + -14.0 ], "to": [ - -2.5, - 15.5, - -15.6 + -3.0, + 10.8, + -12.0 ], "faces": { "north": { "uv": [ 0, 0, - 2.0, - 1.0 + 1.5, + 0.8000000000000007 ], "texture": "#bare_lamp_b" }, @@ -1744,8 +3163,8 @@ "uv": [ 0, 0, - 2.0, - 1.0 + 1.5, + 0.8000000000000007 ], "texture": "#bare_lamp_b" }, @@ -1753,8 +3172,8 @@ "uv": [ 0, 0, - 0.40000000000000036, - 1.0 + 2.0, + 0.8000000000000007 ], "texture": "#bare_lamp_b" }, @@ -1762,8 +3181,8 @@ "uv": [ 0, 0, - 0.40000000000000036, - 1.0 + 2.0, + 0.8000000000000007 ], "texture": "#bare_lamp_b" }, @@ -1771,8 +3190,8 @@ "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 1.5, + 2.0 ], "texture": "#bare_lamp_b" }, @@ -1780,8 +3199,8 @@ "uv": [ 0, 0, - 2.0, - 0.40000000000000036 + 1.5, + 2.0 ], "texture": "#bare_lamp_b" } @@ -1789,99 +3208,99 @@ }, { "from": [ - -1.0, - 5.5, - -16.0 + 19.0, + 10.0, + -14.0 ], "to": [ - 17.0, - 7.5, - -15.0 + 20.5, + 10.8, + -12.0 ], "faces": { "north": { "uv": [ 0, 0, - 16, - 2.0 + 1.5, + 0.8000000000000007 ], - "texture": "#dark_metal_b" + "texture": "#bare_lamp_b" }, "south": { "uv": [ 0, 0, - 16, - 2.0 + 1.5, + 0.8000000000000007 ], - "texture": "#dark_metal_b" + "texture": "#bare_lamp_b" }, "east": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 19.0, + 12.0, + -8.2 + ], + "to": [ + 20.0, + 12.6, + -7.8 + ], + "faces": { + "north": { "uv": [ 0, 0, 1.0, - 2.0 + 0.5999999999999996 ], "texture": "#dark_metal_b" }, - "west": { + "south": { "uv": [ 0, 0, 1.0, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 1.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -10.5, - 7.0, - 4.0 - ], - "to": [ - -7.0, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 + 0.5999999999999996 ], "texture": "#dark_metal_b" }, @@ -1889,8 +3308,8 @@ "uv": [ 0, 0, - 10.0, - 3.0 + 0.39999999999999947, + 0.5999999999999996 ], "texture": "#dark_metal_b" }, @@ -1898,8 +3317,8 @@ "uv": [ 0, 0, - 10.0, - 3.0 + 0.39999999999999947, + 0.5999999999999996 ], "texture": "#dark_metal_b" }, @@ -1907,8 +3326,8 @@ "uv": [ 0, 0, - 3.5, - 10.0 + 1.0, + 0.39999999999999947 ], "texture": "#dark_metal_b" }, @@ -1916,216 +3335,12 @@ "uv": [ 0, 0, - 3.5, - 10.0 + 1.0, + 0.39999999999999947 ], "texture": "#dark_metal_b" } } - }, - { - "from": [ - 23.0, - 7.0, - 4.0 - ], - "to": [ - 26.5, - 10.0, - 14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 10.0, - 3.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 10.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - 22.0, - 11.0, - -15.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - } - } - }, - { - "from": [ - -6.119999999999997, - 11.0, - -15.0 - ], - "to": [ - -6.0, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 4.0 - ], - "texture": "#mk_ru_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru_b" - } - } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/s400_tubes.json b/airdef-mod/assets/airdef/models/block/s400_tubes.json index b962ee8..8c1a389 100644 --- a/airdef-mod/assets/airdef/models/block/s400_tubes.json +++ b/airdef-mod/assets/airdef/models/block/s400_tubes.json @@ -9,31 +9,22 @@ "elements": [ { "from": [ - 0.5, + 1.9, 0.0, -16.0 ], "to": [ - 7.5, - 7.0, + 6.1, + 1.4, 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 7.0, - 7.0 - ], - "texture": "#tube_cap" - }, "south": { "uv": [ 0, 0, - 7.0, - 7.0 + 4.199999999999999, + 1.4 ], "texture": "#tube_cap" }, @@ -42,7 +33,7 @@ 0, 0, 16, - 7.0 + 1.4 ], "texture": "#tube" }, @@ -51,7 +42,7 @@ 0, 0, 16, - 7.0 + 1.4 ], "texture": "#tube" }, @@ -59,7 +50,7 @@ "uv": [ 0, 0, - 7.0, + 4.199999999999999, 16 ], "texture": "#tube" @@ -68,7 +59,66 @@ "uv": [ 0, 0, - 7.0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.7799999999999998, + 1.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 2.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, 16 ], "texture": "#tube" @@ -78,30 +128,21 @@ { "from": [ 0.5, - 8.0, + 2.8, -16.0 ], "to": [ 7.5, - 15.0, + 4.2, 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 7.0, - 7.0 - ], - "texture": "#tube_cap" - }, "south": { "uv": [ 0, 0, 7.0, - 7.0 + 1.4000000000000004 ], "texture": "#tube_cap" }, @@ -110,7 +151,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube" }, @@ -119,7 +160,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube" }, @@ -145,31 +186,81 @@ }, { "from": [ - 8.5, - 0.0, + 0.7799999999999998, + 4.2, -16.0 ], "to": [ - 15.5, + 7.220000000000001, + 5.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 1.9, + 5.6, + -16.0 + ], + "to": [ + 6.1, 7.0, 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 7.0, - 7.0 - ], - "texture": "#tube_cap" - }, "south": { "uv": [ 0, 0, - 7.0, - 7.0 + 4.199999999999999, + 1.4000000000000004 ], "texture": "#tube_cap" }, @@ -178,7 +269,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube" }, @@ -187,7 +278,320 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.35, + 2.6, + -1.0 + ], + "to": [ + 7.65, + 4.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.35, + 2.6, + 26.0 + ], + "to": [ + 7.65, + 4.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 1.9, + 8.0, + -16.0 + ], + "to": [ + 6.1, + 9.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.7799999999999998, + 9.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 10.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.5, + 10.8, + -16.0 + ], + "to": [ + 7.5, + 12.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 ], "texture": "#tube" }, @@ -213,31 +617,22 @@ }, { "from": [ - 8.5, - 8.0, + 0.7799999999999998, + 12.2, -16.0 ], "to": [ - 15.5, - 15.0, + 7.220000000000001, + 13.6, 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 7.0, - 7.0 - ], - "texture": "#tube_cap" - }, "south": { "uv": [ 0, 0, - 7.0, - 7.0 + 6.440000000000001, + 1.4000000000000004 ], "texture": "#tube_cap" }, @@ -246,7 +641,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube" }, @@ -255,7 +650,379 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 1.9, + 13.6, + -16.0 + ], + "to": [ + 6.1, + 15.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.35, + 10.6, + -1.0 + ], + "to": [ + 7.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.35, + 10.6, + 26.0 + ], + "to": [ + 7.65, + 12.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 9.9, + 0.0, + -16.0 + ], + "to": [ + 14.1, + 1.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 1.4, + -16.0 + ], + "to": [ + 15.22, + 2.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.5, + 2.8, + -16.0 + ], + "to": [ + 15.5, + 4.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 ], "texture": "#tube" }, @@ -279,6 +1046,691 @@ } } }, + { + "from": [ + 8.78, + 4.2, + -16.0 + ], + "to": [ + 15.22, + 5.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 9.9, + 5.6, + -16.0 + ], + "to": [ + 14.1, + 7.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.35, + 2.6, + -1.0 + ], + "to": [ + 15.65, + 4.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 8.35, + 2.6, + 26.0 + ], + "to": [ + 15.65, + 4.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 9.9, + 8.0, + -16.0 + ], + "to": [ + 14.1, + 9.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 9.4, + -16.0 + ], + "to": [ + 15.22, + 10.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.5, + 10.8, + -16.0 + ], + "to": [ + 15.5, + 12.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 12.2, + -16.0 + ], + "to": [ + 15.22, + 13.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 9.9, + 13.6, + -16.0 + ], + "to": [ + 14.1, + 15.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.35, + 10.6, + -1.0 + ], + "to": [ + 15.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 8.35, + 10.6, + 26.0 + ], + "to": [ + 15.65, + 12.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, { "from": [ -0.5, @@ -287,8 +1739,8 @@ ], "to": [ 16.5, - 15.0, - 28.0 + 1.0, + 29.0 ], "faces": { "north": { @@ -296,7 +1748,7 @@ 0, 0, 16, - 15.0 + 1.0 ], "texture": "#dark_metal" }, @@ -305,7 +1757,7 @@ 0, 0, 16, - 15.0 + 1.0 ], "texture": "#dark_metal" }, @@ -313,8 +1765,8 @@ "uv": [ 0, 0, - 2.0, - 15.0 + 3.0, + 1.0 ], "texture": "#dark_metal" }, @@ -322,8 +1774,8 @@ "uv": [ 0, 0, - 2.0, - 15.0 + 3.0, + 1.0 ], "texture": "#dark_metal" }, @@ -332,7 +1784,7 @@ 0, 0, 16, - 2.0 + 3.0 ], "texture": "#dark_metal" }, @@ -341,79 +1793,11 @@ 0, 0, 16, - 2.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -0.5, - 0.0, - -2.0 - ], - "to": [ - 16.5, - 15.0, - 0.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 15.0 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 15.0 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 15.0 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 15.0 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 2.0 + 3.0 ], "texture": "#dark_metal" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/s400_tubes_burnt.json b/airdef-mod/assets/airdef/models/block/s400_tubes_burnt.json index 2bc876f..f7b62d7 100644 --- a/airdef-mod/assets/airdef/models/block/s400_tubes_burnt.json +++ b/airdef-mod/assets/airdef/models/block/s400_tubes_burnt.json @@ -9,13 +9,13 @@ "elements": [ { "from": [ - 0.5, + 1.9, 0.0, -16.0 ], "to": [ - 7.5, - 7.0, + 6.1, + 1.4, 32.0 ], "faces": { @@ -23,8 +23,8 @@ "uv": [ 0, 0, - 7.0, - 7.0 + 4.199999999999999, + 1.4 ], "texture": "#tube_cap_b" }, @@ -32,8 +32,8 @@ "uv": [ 0, 0, - 7.0, - 7.0 + 4.199999999999999, + 1.4 ], "texture": "#tube_cap_b" }, @@ -42,7 +42,7 @@ 0, 0, 16, - 7.0 + 1.4 ], "texture": "#tube_b" }, @@ -51,7 +51,7 @@ 0, 0, 16, - 7.0 + 1.4 ], "texture": "#tube_b" }, @@ -59,7 +59,7 @@ "uv": [ 0, 0, - 7.0, + 4.199999999999999, 16 ], "texture": "#tube_b" @@ -68,7 +68,75 @@ "uv": [ 0, 0, - 7.0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.7799999999999998, + 1.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 2.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, 16 ], "texture": "#tube_b" @@ -78,12 +146,12 @@ { "from": [ 0.5, - 8.0, + 2.8, -16.0 ], "to": [ 7.5, - 15.0, + 4.2, 32.0 ], "faces": { @@ -92,7 +160,7 @@ 0, 0, 7.0, - 7.0 + 1.4000000000000004 ], "texture": "#tube_cap_b" }, @@ -101,7 +169,7 @@ 0, 0, 7.0, - 7.0 + 1.4000000000000004 ], "texture": "#tube_cap_b" }, @@ -110,7 +178,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube_b" }, @@ -119,7 +187,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube_b" }, @@ -145,12 +213,80 @@ }, { "from": [ - 8.5, - 0.0, + 0.7799999999999998, + 4.2, -16.0 ], "to": [ - 15.5, + 7.220000000000001, + 5.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 1.9, + 5.6, + -16.0 + ], + "to": [ + 6.1, 7.0, 32.0 ], @@ -159,8 +295,8 @@ "uv": [ 0, 0, - 7.0, - 7.0 + 4.199999999999999, + 1.4000000000000004 ], "texture": "#tube_cap_b" }, @@ -168,8 +304,8 @@ "uv": [ 0, 0, - 7.0, - 7.0 + 4.199999999999999, + 1.4000000000000004 ], "texture": "#tube_cap_b" }, @@ -178,7 +314,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube_b" }, @@ -187,7 +323,279 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.35, + 2.6, + 26.0 + ], + "to": [ + 7.65, + 4.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 1.9, + 8.0, + -16.0 + ], + "to": [ + 6.1, + 9.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.7799999999999998, + 9.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 10.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.5, + 10.8, + -16.0 + ], + "to": [ + 7.5, + 12.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 ], "texture": "#tube_b" }, @@ -213,13 +621,13 @@ }, { "from": [ - 8.5, - 8.0, + 0.7799999999999998, + 12.2, -16.0 ], "to": [ - 15.5, - 15.0, + 7.220000000000001, + 13.6, 32.0 ], "faces": { @@ -227,8 +635,8 @@ "uv": [ 0, 0, - 7.0, - 7.0 + 6.440000000000001, + 1.4000000000000004 ], "texture": "#tube_cap_b" }, @@ -236,8 +644,8 @@ "uv": [ 0, 0, - 7.0, - 7.0 + 6.440000000000001, + 1.4000000000000004 ], "texture": "#tube_cap_b" }, @@ -246,7 +654,7 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 ], "texture": "#tube_b" }, @@ -255,7 +663,415 @@ 0, 0, 16, - 7.0 + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 1.9, + 13.6, + -16.0 + ], + "to": [ + 6.1, + 15.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.35, + 10.6, + -1.0 + ], + "to": [ + 7.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 0.35, + 10.6, + 26.0 + ], + "to": [ + 7.65, + 12.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 9.9, + 0.0, + -16.0 + ], + "to": [ + 14.1, + 1.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 1.4, + -16.0 + ], + "to": [ + 15.22, + 2.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.5, + 2.8, + -16.0 + ], + "to": [ + 15.5, + 4.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 ], "texture": "#tube_b" }, @@ -279,6 +1095,618 @@ } } }, + { + "from": [ + 8.78, + 4.2, + -16.0 + ], + "to": [ + 15.22, + 5.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 9.9, + 5.6, + -16.0 + ], + "to": [ + 14.1, + 7.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.35, + 2.6, + -1.0 + ], + "to": [ + 15.65, + 4.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 9.9, + 8.0, + -16.0 + ], + "to": [ + 14.1, + 9.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 9.4, + -16.0 + ], + "to": [ + 15.22, + 10.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.5, + 10.8, + -16.0 + ], + "to": [ + 15.5, + 12.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 12.2, + -16.0 + ], + "to": [ + 15.22, + 13.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 9.9, + 13.6, + -16.0 + ], + "to": [ + 14.1, + 15.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.35, + 10.6, + -1.0 + ], + "to": [ + 15.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, { "from": [ -0.5, @@ -287,8 +1715,8 @@ ], "to": [ 16.5, - 15.0, - 28.0 + 1.0, + 29.0 ], "faces": { "north": { @@ -296,7 +1724,7 @@ 0, 0, 16, - 15.0 + 1.0 ], "texture": "#dark_metal_b" }, @@ -305,7 +1733,7 @@ 0, 0, 16, - 15.0 + 1.0 ], "texture": "#dark_metal_b" }, @@ -313,8 +1741,8 @@ "uv": [ 0, 0, - 2.0, - 15.0 + 3.0, + 1.0 ], "texture": "#dark_metal_b" }, @@ -322,8 +1750,8 @@ "uv": [ 0, 0, - 2.0, - 15.0 + 3.0, + 1.0 ], "texture": "#dark_metal_b" }, @@ -332,7 +1760,7 @@ 0, 0, 16, - 2.0 + 3.0 ], "texture": "#dark_metal_b" }, @@ -341,79 +1769,11 @@ 0, 0, 16, - 2.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - -0.5, - 0.0, - -2.0 - ], - "to": [ - 16.5, - 15.0, - 0.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 15.0 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 15.0 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 15.0 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 15.0 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 2.0 + 3.0 ], "texture": "#dark_metal_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/s500_tubes.json b/airdef-mod/assets/airdef/models/block/s500_tubes.json index f626dcd..cd94e74 100644 --- a/airdef-mod/assets/airdef/models/block/s500_tubes.json +++ b/airdef-mod/assets/airdef/models/block/s500_tubes.json @@ -19,15 +19,6 @@ 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 9.5, - 9.5 - ], - "texture": "#tube_cap" - }, "south": { "uv": [ 0, @@ -87,15 +78,6 @@ 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 9.5, - 9.5 - ], - "texture": "#tube_cap" - }, "south": { "uv": [ 0, diff --git a/airdef-mod/assets/airdef/models/block/s500_tubes_burnt.json b/airdef-mod/assets/airdef/models/block/s500_tubes_burnt.json index 3208fab..672ae13 100644 --- a/airdef-mod/assets/airdef/models/block/s500_tubes_burnt.json +++ b/airdef-mod/assets/airdef/models/block/s500_tubes_burnt.json @@ -19,15 +19,6 @@ 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 9.5, - 9.5 - ], - "texture": "#tube_cap_b" - }, "south": { "uv": [ 0, @@ -87,15 +78,6 @@ 32.0 ], "faces": { - "north": { - "uv": [ - 0, - 0, - 9.5, - 9.5 - ], - "texture": "#tube_cap_b" - }, "south": { "uv": [ 0, diff --git a/airdef-mod/assets/airdef/models/block/searchlight.json b/airdef-mod/assets/airdef/models/block/searchlight.json index 8b4bd4a..6988a0d 100644 --- a/airdef-mod/assets/airdef/models/block/searchlight.json +++ b/airdef-mod/assets/airdef/models/block/searchlight.json @@ -2,27 +2,28 @@ "ambientocclusion": false, "textures": { "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", "particle": "airdef:block/dark_metal" }, "elements": [ { "from": [ - 2.0, + 1.0, 0.0, - 2.0 + 1.0 ], "to": [ - 14.0, - 3.0, - 14.0 + 15.0, + 2.0, + 15.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal" }, @@ -30,8 +31,8 @@ "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal" }, @@ -39,8 +40,8 @@ "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal" }, @@ -48,8 +49,8 @@ "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal" }, @@ -57,8 +58,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#dark_metal" }, @@ -66,8 +67,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#dark_metal" } @@ -75,8 +76,76 @@ }, { "from": [ - 6.0, 3.0, + 2.0, + 3.0 + ], + "to": [ + 13.0, + 4.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 6.0, + 4.0, 6.0 ], "to": [ @@ -90,7 +159,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal" }, @@ -99,7 +168,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal" }, @@ -108,7 +177,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal" }, @@ -117,7 +186,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal" }, @@ -140,6 +209,142 @@ "texture": "#dark_metal" } } + }, + { + "from": [ + 2.0, + 8.0, + 7.0 + ], + "to": [ + 3.0, + 14.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 13.0, + 8.0, + 7.0 + ], + "to": [ + 14.0, + 14.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/searchlight_burnt.json b/airdef-mod/assets/airdef/models/block/searchlight_burnt.json index 0f29afc..fb66c2f 100644 --- a/airdef-mod/assets/airdef/models/block/searchlight_burnt.json +++ b/airdef-mod/assets/airdef/models/block/searchlight_burnt.json @@ -2,27 +2,28 @@ "ambientocclusion": false, "textures": { "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", "particle": "airdef:block/dark_metal_b" }, "elements": [ { "from": [ - 2.0, + 1.0, 0.0, - 2.0 + 1.0 ], "to": [ - 14.0, - 3.0, - 14.0 + 15.0, + 2.0, + 15.0 ], "faces": { "north": { "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal_b" }, @@ -30,8 +31,8 @@ "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal_b" }, @@ -39,8 +40,8 @@ "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal_b" }, @@ -48,8 +49,8 @@ "uv": [ 0, 0, - 12.0, - 3.0 + 14.0, + 2.0 ], "texture": "#dark_metal_b" }, @@ -57,8 +58,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#dark_metal_b" }, @@ -66,8 +67,8 @@ "uv": [ 0, 0, - 12.0, - 12.0 + 14.0, + 14.0 ], "texture": "#dark_metal_b" } @@ -75,8 +76,76 @@ }, { "from": [ - 6.0, 3.0, + 2.0, + 3.0 + ], + "to": [ + 13.0, + 4.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 6.0, + 4.0, 6.0 ], "to": [ @@ -90,7 +159,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal_b" }, @@ -99,7 +168,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal_b" }, @@ -108,7 +177,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal_b" }, @@ -117,7 +186,7 @@ 0, 0, 4.0, - 9.0 + 8.0 ], "texture": "#dark_metal_b" }, @@ -140,6 +209,74 @@ "texture": "#dark_metal_b" } } + }, + { + "from": [ + 2.0, + 8.0, + 7.0 + ], + "to": [ + 3.0, + 14.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal_b" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/searchlight_head.json b/airdef-mod/assets/airdef/models/block/searchlight_head.json index b52127c..80f8e2a 100644 --- a/airdef-mod/assets/airdef/models/block/searchlight_head.json +++ b/airdef-mod/assets/airdef/models/block/searchlight_head.json @@ -3,6 +3,7 @@ "textures": { "dark_metal": "airdef:block/dark_metal", "lamp": "airdef:block/lamp", + "metal": "airdef:block/metal", "particle": "airdef:block/dark_metal" }, "elements": [ @@ -74,6 +75,74 @@ } } }, + { + "from": [ + 4.0, + 2.0, + 0.0 + ], + "to": [ + 12.0, + 14.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + } + } + }, { "from": [ 3.5, @@ -141,6 +210,74 @@ "texture": "#lamp" } } + }, + { + "from": [ + 5.0, + 5.0, + 14.0 + ], + "to": [ + 11.0, + 11.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#metal" + } + } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/searchlight_head_burnt.json b/airdef-mod/assets/airdef/models/block/searchlight_head_burnt.json index 90b3557..2a6fcef 100644 --- a/airdef-mod/assets/airdef/models/block/searchlight_head_burnt.json +++ b/airdef-mod/assets/airdef/models/block/searchlight_head_burnt.json @@ -74,6 +74,74 @@ } } }, + { + "from": [ + 4.0, + 2.0, + 0.0 + ], + "to": [ + 12.0, + 14.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + } + } + }, { "from": [ 3.5, @@ -143,4 +211,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/shahed.json b/airdef-mod/assets/airdef/models/block/shahed.json index 0222a5a..caeeef5 100644 --- a/airdef-mod/assets/airdef/models/block/shahed.json +++ b/airdef-mod/assets/airdef/models/block/shahed.json @@ -6,29 +6,27 @@ "kh101_belly": "airdef:block/kh101_belly", "dark_metal": "airdef:block/dark_metal", "bare_nozzle": "airdef:block/bare_nozzle", - "bare_antenna": "airdef:block/bare_antenna", - "bare_lamp": "airdef:block/bare_lamp", "particle": "airdef:block/shahed_nose" }, "elements": [ { "from": [ - 6.921089183743392, - 6.921089183743392, + 7.114403477412995, + 7.114403477412995, -16.0 ], "to": [ - 9.078910816256608, - 9.078910816256608, - -13.666666666666666 + 8.885596522587004, + 8.885596522587004, + -14.25 ], "faces": { "north": { "uv": [ 0, 0, - 2.1578216325132153, - 2.1578216325132153 + 1.7711930451740097, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -36,8 +34,8 @@ "uv": [ 0, 0, - 2.1578216325132153, - 2.1578216325132153 + 1.7711930451740097, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -45,8 +43,8 @@ "uv": [ 0, 0, - 2.333333333333334, - 2.1578216325132153 + 1.75, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -54,8 +52,8 @@ "uv": [ 0, 0, - 2.333333333333334, - 2.1578216325132153 + 1.75, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -63,8 +61,8 @@ "uv": [ 0, 0, - 2.1578216325132153, - 2.333333333333334 + 1.7711930451740097, + 1.75 ], "texture": "#shahed_nose" }, @@ -72,8 +70,8 @@ "uv": [ 0, 0, - 2.1578216325132153, - 2.333333333333334 + 1.7711930451740097, + 1.75 ], "texture": "#shahed_nose" } @@ -81,22 +79,22 @@ }, { "from": [ - 6.921089183743392, - 6.921089183743392, + 7.114403477412995, + 7.114403477412995, -15.97 ], "to": [ - 9.078910816256608, - 9.078910816256608, - -13.696666666666665 + 8.885596522587004, + 8.885596522587004, + -14.28 ], "faces": { "north": { "uv": [ 0, 0, - 2.1578216325132153, - 2.1578216325132153 + 1.7711930451740097, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -104,8 +102,8 @@ "uv": [ 0, 0, - 2.1578216325132153, - 2.1578216325132153 + 1.7711930451740097, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -113,8 +111,8 @@ "uv": [ 0, 0, - 2.273333333333335, - 2.1578216325132153 + 1.6900000000000013, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -122,8 +120,8 @@ "uv": [ 0, 0, - 2.273333333333335, - 2.1578216325132153 + 1.6900000000000013, + 1.7711930451740097 ], "texture": "#shahed_nose" }, @@ -131,8 +129,8 @@ "uv": [ 0, 0, - 2.1578216325132153, - 2.273333333333335 + 1.7711930451740097, + 1.6900000000000013 ], "texture": "#shahed_nose" }, @@ -140,8 +138,8 @@ "uv": [ 0, 0, - 2.1578216325132153, - 2.273333333333335 + 1.7711930451740097, + 1.6900000000000013 ], "texture": "#shahed_nose" } @@ -152,28 +150,28 @@ "origin": [ 8.0, 8.0, - -14.833333333333332 + -15.125 ] } }, { "from": [ - 6.242365649216982, - 6.242365649216982, - -13.666666666666666 + 6.557290514119103, + 6.557290514119103, + -14.25 ], "to": [ - 9.757634350783018, - 9.757634350783018, - -11.333333333333332 + 9.442709485880897, + 9.442709485880897, + -12.5 ], "faces": { "north": { "uv": [ 0, 0, - 3.515268701566036, - 3.515268701566036 + 2.885418971761794, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -181,8 +179,8 @@ "uv": [ 0, 0, - 3.515268701566036, - 3.515268701566036 + 2.885418971761794, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -190,8 +188,8 @@ "uv": [ 0, 0, - 2.333333333333334, - 3.515268701566036 + 1.75, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -199,8 +197,8 @@ "uv": [ 0, 0, - 2.333333333333334, - 3.515268701566036 + 1.75, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -208,8 +206,8 @@ "uv": [ 0, 0, - 3.515268701566036, - 2.333333333333334 + 2.885418971761794, + 1.75 ], "texture": "#shahed_nose" }, @@ -217,8 +215,8 @@ "uv": [ 0, 0, - 3.515268701566036, - 2.333333333333334 + 2.885418971761794, + 1.75 ], "texture": "#shahed_nose" } @@ -226,22 +224,22 @@ }, { "from": [ - 6.242365649216982, - 6.242365649216982, - -13.636666666666667 + 6.557290514119103, + 6.557290514119103, + -14.22 ], "to": [ - 9.757634350783018, - 9.757634350783018, - -11.363333333333332 + 9.442709485880897, + 9.442709485880897, + -12.53 ], "faces": { "north": { "uv": [ 0, 0, - 3.515268701566036, - 3.515268701566036 + 2.885418971761794, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -249,8 +247,8 @@ "uv": [ 0, 0, - 3.515268701566036, - 3.515268701566036 + 2.885418971761794, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -258,8 +256,8 @@ "uv": [ 0, 0, - 2.273333333333335, - 3.515268701566036 + 1.6900000000000013, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -267,8 +265,8 @@ "uv": [ 0, 0, - 2.273333333333335, - 3.515268701566036 + 1.6900000000000013, + 2.885418971761794 ], "texture": "#shahed_nose" }, @@ -276,8 +274,8 @@ "uv": [ 0, 0, - 3.515268701566036, - 2.273333333333335 + 2.885418971761794, + 1.6900000000000013 ], "texture": "#shahed_nose" }, @@ -285,8 +283,8 @@ "uv": [ 0, 0, - 3.515268701566036, - 2.273333333333335 + 2.885418971761794, + 1.6900000000000013 ], "texture": "#shahed_nose" } @@ -297,19 +295,164 @@ "origin": [ 8.0, 8.0, - -12.5 + -13.375 ] } }, { "from": [ - 5.733103482595327, - 5.733103482595327, - -11.333333333333332 + 6.139275607743379, + 6.139275607743379, + -12.5 ], "to": [ - 10.266896517404673, - 10.266896517404673, + 9.860724392256621, + 9.860724392256621, + -10.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.75, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.75, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.75 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.75 + ], + "texture": "#shahed_nose" + } + } + }, + { + "from": [ + 6.139275607743379, + 6.139275607743379, + -12.47 + ], + "to": [ + 9.860724392256621, + 9.860724392256621, + -10.78 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -11.625 + ] + } + }, + { + "from": [ + 5.7871960969890655, + 5.7871960969890655, + -10.75 + ], + "to": [ + 10.212803903010935, + 10.212803903010935, -9.0 ], "faces": { @@ -317,8 +460,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 4.533793034809346 + 4.425607806021869, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -326,8 +469,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 4.533793034809346 + 4.425607806021869, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -335,8 +478,8 @@ "uv": [ 0, 0, - 2.333333333333332, - 4.533793034809346 + 1.75, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -344,8 +487,8 @@ "uv": [ 0, 0, - 2.333333333333332, - 4.533793034809346 + 1.75, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -353,8 +496,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 2.333333333333332 + 4.425607806021869, + 1.75 ], "texture": "#shahed_nose" }, @@ -362,8 +505,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 2.333333333333332 + 4.425607806021869, + 1.75 ], "texture": "#shahed_nose" } @@ -371,13 +514,13 @@ }, { "from": [ - 5.733103482595327, - 5.733103482595327, - -11.303333333333333 + 5.7871960969890655, + 5.7871960969890655, + -10.72 ], "to": [ - 10.266896517404673, - 10.266896517404673, + 10.212803903010935, + 10.212803903010935, -9.03 ], "faces": { @@ -385,8 +528,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 4.533793034809346 + 4.425607806021869, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -394,8 +537,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 4.533793034809346 + 4.425607806021869, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -403,8 +546,8 @@ "uv": [ 0, 0, - 2.2733333333333334, - 4.533793034809346 + 1.6900000000000013, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -412,8 +555,8 @@ "uv": [ 0, 0, - 2.2733333333333334, - 4.533793034809346 + 1.6900000000000013, + 4.425607806021869 ], "texture": "#shahed_nose" }, @@ -421,8 +564,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 2.2733333333333334 + 4.425607806021869, + 1.6900000000000013 ], "texture": "#shahed_nose" }, @@ -430,8 +573,8 @@ "uv": [ 0, 0, - 4.533793034809346, - 2.2733333333333334 + 4.425607806021869, + 1.6900000000000013 ], "texture": "#shahed_nose" } @@ -442,19 +585,19 @@ "origin": [ 8.0, 8.0, - -10.166666666666666 + -9.875 ] } }, { "from": [ - 5.59786, - 5.59786, + 5.69025, + 5.69025, -9.0 ], "to": [ - 10.40214, - 10.40214, + 10.309750000000001, + 10.309750000000001, 22.0 ], "faces": { @@ -462,8 +605,8 @@ "uv": [ 0, 0, - 4.804279999999999, - 4.804279999999999 + 4.619500000000001, + 4.619500000000001 ], "texture": "#shahed" }, @@ -471,8 +614,8 @@ "uv": [ 0, 0, - 4.804279999999999, - 4.804279999999999 + 4.619500000000001, + 4.619500000000001 ], "texture": "#shahed" }, @@ -481,7 +624,7 @@ 0, 0, 16, - 4.804279999999999 + 4.619500000000001 ], "texture": "#shahed" }, @@ -490,7 +633,7 @@ 0, 0, 16, - 4.804279999999999 + 4.619500000000001 ], "texture": "#shahed" }, @@ -498,7 +641,7 @@ "uv": [ 0, 0, - 4.804279999999999, + 4.619500000000001, 16 ], "texture": "#shahed" @@ -507,7 +650,7 @@ "uv": [ 0, 0, - 4.804279999999999, + 4.619500000000001, 16 ], "texture": "#shahed" @@ -516,13 +659,13 @@ }, { "from": [ - 5.59786, - 5.59786, + 5.69025, + 5.69025, -8.97 ], "to": [ - 10.40214, - 10.40214, + 10.309750000000001, + 10.309750000000001, 21.97 ], "faces": { @@ -530,8 +673,8 @@ "uv": [ 0, 0, - 4.804279999999999, - 4.804279999999999 + 4.619500000000001, + 4.619500000000001 ], "texture": "#shahed" }, @@ -539,8 +682,8 @@ "uv": [ 0, 0, - 4.804279999999999, - 4.804279999999999 + 4.619500000000001, + 4.619500000000001 ], "texture": "#shahed" }, @@ -549,7 +692,7 @@ 0, 0, 16, - 4.804279999999999 + 4.619500000000001 ], "texture": "#shahed" }, @@ -558,7 +701,7 @@ 0, 0, 16, - 4.804279999999999 + 4.619500000000001 ], "texture": "#shahed" }, @@ -566,7 +709,7 @@ "uv": [ 0, 0, - 4.804279999999999, + 4.619500000000001, 16 ], "texture": "#shahed" @@ -575,7 +718,7 @@ "uv": [ 0, 0, - 4.804279999999999, + 4.619500000000001, 16 ], "texture": "#shahed" @@ -593,13 +736,13 @@ }, { "from": [ - 5.666666666666666, - 7.2, + 7.25, + 7.5, -2.0 ], "to": [ - 10.333333333333334, - 8.4, + 8.75, + 8.3, 26.0 ], "faces": { @@ -607,8 +750,8 @@ "uv": [ 0, 0, - 4.666666666666668, - 1.2000000000000002 + 1.5, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -616,8 +759,8 @@ "uv": [ 0, 0, - 4.666666666666668, - 1.2000000000000002 + 1.5, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -626,7 +769,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -635,7 +778,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -643,7 +786,7 @@ "uv": [ 0, 0, - 4.666666666666668, + 1.5, 16 ], "texture": "#shahed" @@ -652,7 +795,7 @@ "uv": [ 0, 0, - 4.666666666666668, + 1.5, 16 ], "texture": "#kh101_belly" @@ -661,13 +804,13 @@ }, { "from": [ - 3.333333333333333, - 7.2, - 0.5555555555555554 + 6.5, + 7.5, + -1.1785714285714286 ], "to": [ - 12.666666666666668, - 8.4, + 9.5, + 8.3, 26.0 ], "faces": { @@ -675,8 +818,8 @@ "uv": [ 0, 0, - 9.333333333333336, - 1.2000000000000002 + 3.0, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -684,8 +827,8 @@ "uv": [ 0, 0, - 9.333333333333336, - 1.2000000000000002 + 3.0, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -694,7 +837,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -703,7 +846,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -711,7 +854,7 @@ "uv": [ 0, 0, - 9.333333333333336, + 3.0, 16 ], "texture": "#shahed" @@ -720,7 +863,7 @@ "uv": [ 0, 0, - 9.333333333333336, + 3.0, 16 ], "texture": "#kh101_belly" @@ -729,13 +872,13 @@ }, { "from": [ - 1.0, - 7.2, - 3.1111111111111107 + 5.75, + 7.5, + -0.3571428571428572 ], "to": [ - 15.0, - 8.4, + 10.25, + 8.3, 26.0 ], "faces": { @@ -743,8 +886,8 @@ "uv": [ 0, 0, - 14.0, - 1.2000000000000002 + 4.5, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -752,8 +895,8 @@ "uv": [ 0, 0, - 14.0, - 1.2000000000000002 + 4.5, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -762,7 +905,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -771,7 +914,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -779,7 +922,7 @@ "uv": [ 0, 0, - 14.0, + 4.5, 16 ], "texture": "#shahed" @@ -788,7 +931,7 @@ "uv": [ 0, 0, - 14.0, + 4.5, 16 ], "texture": "#kh101_belly" @@ -797,13 +940,13 @@ }, { "from": [ - -1.333333333333334, - 7.2, - 5.666666666666667 + 5.0, + 7.5, + 0.4642857142857144 ], "to": [ - 17.333333333333336, - 8.4, + 11.0, + 8.3, 26.0 ], "faces": { @@ -811,8 +954,8 @@ "uv": [ 0, 0, - 16, - 1.2000000000000002 + 6.0, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -820,8 +963,8 @@ "uv": [ 0, 0, - 16, - 1.2000000000000002 + 6.0, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -830,7 +973,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -839,7 +982,483 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 4.25, + 7.5, + 1.2857142857142856 + ], + "to": [ + 11.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 7.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 7.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 7.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 3.5, + 7.5, + 2.1071428571428568 + ], + "to": [ + 12.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 2.75, + 7.5, + 2.928571428571429 + ], + "to": [ + 13.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 10.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 10.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 10.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 2.0, + 7.5, + 3.75 + ], + "to": [ + 14.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 1.25, + 7.5, + 4.571428571428571 + ], + "to": [ + 14.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 13.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 13.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 13.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 13.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 0.5, + 7.5, + 5.392857142857143 + ], + "to": [ + 15.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 15.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 15.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 15.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 15.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -0.25, + 7.5, + 6.2142857142857135 + ], + "to": [ + 16.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -865,13 +1484,13 @@ }, { "from": [ - -3.666666666666666, - 7.2, - 8.222222222222221 + -1.0, + 7.5, + 7.0357142857142865 ], "to": [ - 19.666666666666664, - 8.4, + 17.0, + 8.3, 26.0 ], "faces": { @@ -880,7 +1499,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -889,7 +1508,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -898,7 +1517,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -907,7 +1526,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -933,13 +1552,13 @@ }, { "from": [ - -6.0, - 7.2, - 10.777777777777779 + -1.75, + 7.5, + 7.857142857142858 ], "to": [ - 22.0, - 8.4, + 17.75, + 8.3, 26.0 ], "faces": { @@ -948,7 +1567,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -957,7 +1576,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -965,8 +1584,8 @@ "uv": [ 0, 0, - 15.222222222222221, - 1.2000000000000002 + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -974,8 +1593,8 @@ "uv": [ 0, 0, - 15.222222222222221, - 1.2000000000000002 + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -984,7 +1603,7 @@ 0, 0, 16, - 15.222222222222221 + 16 ], "texture": "#shahed" }, @@ -993,7 +1612,7 @@ 0, 0, 16, - 15.222222222222221 + 16 ], "texture": "#kh101_belly" } @@ -1001,13 +1620,13 @@ }, { "from": [ - -8.333333333333332, - 7.2, - 13.333333333333334 + -2.5, + 7.5, + 8.678571428571429 ], "to": [ - 24.333333333333332, - 8.4, + 18.5, + 8.3, 26.0 ], "faces": { @@ -1016,7 +1635,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1025,7 +1644,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1033,8 +1652,8 @@ "uv": [ 0, 0, - 12.666666666666666, - 1.2000000000000002 + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1042,8 +1661,8 @@ "uv": [ 0, 0, - 12.666666666666666, - 1.2000000000000002 + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1052,7 +1671,7 @@ 0, 0, 16, - 12.666666666666666 + 16 ], "texture": "#shahed" }, @@ -1061,7 +1680,7 @@ 0, 0, 16, - 12.666666666666666 + 16 ], "texture": "#kh101_belly" } @@ -1069,13 +1688,13 @@ }, { "from": [ - -10.666666666666668, - 7.2, - 15.88888888888889 + -3.25, + 7.5, + 9.5 ], "to": [ - 26.666666666666668, - 8.4, + 19.25, + 8.3, 26.0 ], "faces": { @@ -1084,7 +1703,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1093,7 +1712,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1101,8 +1720,8 @@ "uv": [ 0, 0, - 10.11111111111111, - 1.2000000000000002 + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1110,8 +1729,8 @@ "uv": [ 0, 0, - 10.11111111111111, - 1.2000000000000002 + 16, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1120,7 +1739,7 @@ 0, 0, 16, - 10.11111111111111 + 16 ], "texture": "#shahed" }, @@ -1129,7 +1748,823 @@ 0, 0, 16, - 10.11111111111111 + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -4.0, + 7.5, + 10.321428571428571 + ], + "to": [ + 20.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 15.678571428571429, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 15.678571428571429, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 15.678571428571429 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 15.678571428571429 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -4.75, + 7.5, + 11.142857142857142 + ], + "to": [ + 20.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 14.857142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 14.857142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 14.857142857142858 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 14.857142857142858 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -5.5, + 7.5, + 11.964285714285714 + ], + "to": [ + 21.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 14.035714285714286, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 14.035714285714286, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 14.035714285714286 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 14.035714285714286 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -6.25, + 7.5, + 12.785714285714286 + ], + "to": [ + 22.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 13.214285714285714, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 13.214285714285714, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 13.214285714285714 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 13.214285714285714 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -7.0, + 7.5, + 13.607142857142858 + ], + "to": [ + 23.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 12.392857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 12.392857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 12.392857142857142 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 12.392857142857142 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -7.75, + 7.5, + 14.428571428571427 + ], + "to": [ + 23.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 11.571428571428573, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 11.571428571428573, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 11.571428571428573 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 11.571428571428573 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -8.5, + 7.5, + 15.25 + ], + "to": [ + 24.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 10.75, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 10.75, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.75 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.75 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -9.25, + 7.5, + 16.071428571428573 + ], + "to": [ + 25.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 9.928571428571427, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 9.928571428571427, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.928571428571427 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.928571428571427 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -10.0, + 7.5, + 16.892857142857142 + ], + "to": [ + 26.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 9.107142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 9.107142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.107142857142858 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.107142857142858 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -10.75, + 7.5, + 17.714285714285715 + ], + "to": [ + 26.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 8.285714285714285, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 8.285714285714285, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.285714285714285 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.285714285714285 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -11.5, + 7.5, + 18.535714285714285 + ], + "to": [ + 27.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 7.464285714285715, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 7.464285714285715, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.464285714285715 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.464285714285715 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -12.25, + 7.5, + 19.357142857142858 + ], + "to": [ + 28.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 6.642857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 6.642857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 6.642857142857142 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 6.642857142857142 ], "texture": "#kh101_belly" } @@ -1138,12 +2573,12 @@ { "from": [ -13.0, - 7.2, - 18.444444444444443 + 7.5, + 20.178571428571427 ], "to": [ 29.0, - 8.4, + 8.3, 26.0 ], "faces": { @@ -1152,7 +2587,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1161,7 +2596,7 @@ 0, 0, 16, - 1.2000000000000002 + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1169,8 +2604,8 @@ "uv": [ 0, 0, - 7.555555555555557, - 1.2000000000000002 + 5.821428571428573, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1178,8 +2613,8 @@ "uv": [ 0, 0, - 7.555555555555557, - 1.2000000000000002 + 5.821428571428573, + 0.8000000000000007 ], "texture": "#shahed" }, @@ -1188,7 +2623,7 @@ 0, 0, 16, - 7.555555555555557 + 5.821428571428573 ], "texture": "#shahed" }, @@ -1197,7 +2632,7 @@ 0, 0, 16, - 7.555555555555557 + 5.821428571428573 ], "texture": "#kh101_belly" } @@ -1206,12 +2641,12 @@ { "from": [ -12.8, - 5.5, - 17.0 + 7.6, + 18.0 ], "to": [ - -11.8, - 13.5, + -12.100000000000001, + 13.2, 26.0 ], "faces": { @@ -1219,8 +2654,8 @@ "uv": [ 0, 0, - 1.0, - 8.0 + 0.6999999999999993, + 5.6 ], "texture": "#shahed" }, @@ -1228,8 +2663,8 @@ "uv": [ 0, 0, - 1.0, - 8.0 + 0.6999999999999993, + 5.6 ], "texture": "#shahed" }, @@ -1237,8 +2672,8 @@ "uv": [ 0, 0, - 9.0, - 8.0 + 8.0, + 5.6 ], "texture": "#shahed" }, @@ -1246,8 +2681,8 @@ "uv": [ 0, 0, - 9.0, - 8.0 + 8.0, + 5.6 ], "texture": "#shahed" }, @@ -1255,8 +2690,8 @@ "uv": [ 0, 0, - 1.0, - 9.0 + 0.6999999999999993, + 8.0 ], "texture": "#shahed" }, @@ -1264,8 +2699,8 @@ "uv": [ 0, 0, - 1.0, - 9.0 + 0.6999999999999993, + 8.0 ], "texture": "#shahed" } @@ -1273,13 +2708,13 @@ }, { "from": [ - 27.8, - 5.5, - 17.0 + 28.1, + 7.6, + 18.0 ], "to": [ 28.8, - 13.5, + 13.2, 26.0 ], "faces": { @@ -1287,8 +2722,8 @@ "uv": [ 0, 0, - 1.0, - 8.0 + 0.6999999999999993, + 5.6 ], "texture": "#shahed" }, @@ -1296,8 +2731,8 @@ "uv": [ 0, 0, - 1.0, - 8.0 + 0.6999999999999993, + 5.6 ], "texture": "#shahed" }, @@ -1305,8 +2740,8 @@ "uv": [ 0, 0, - 9.0, - 8.0 + 8.0, + 5.6 ], "texture": "#shahed" }, @@ -1314,8 +2749,8 @@ "uv": [ 0, 0, - 9.0, - 8.0 + 8.0, + 5.6 ], "texture": "#shahed" }, @@ -1323,8 +2758,8 @@ "uv": [ 0, 0, - 1.0, - 9.0 + 0.6999999999999993, + 8.0 ], "texture": "#shahed" }, @@ -1332,8 +2767,8 @@ "uv": [ 0, 0, - 1.0, - 9.0 + 0.6999999999999993, + 8.0 ], "texture": "#shahed" } @@ -1341,13 +2776,13 @@ }, { "from": [ - 6.2445900000000005, - 6.2445900000000005, + 6.33698, + 6.33698, 22.0 ], "to": [ - 9.75541, - 9.75541, + 9.66302, + 9.66302, 28.0 ], "faces": { @@ -1355,8 +2790,8 @@ "uv": [ 0, 0, - 3.510819999999999, - 3.510819999999999 + 3.32604, + 3.32604 ], "texture": "#dark_metal" }, @@ -1364,8 +2799,8 @@ "uv": [ 0, 0, - 3.510819999999999, - 3.510819999999999 + 3.32604, + 3.32604 ], "texture": "#dark_metal" }, @@ -1374,7 +2809,7 @@ 0, 0, 6.0, - 3.510819999999999 + 3.32604 ], "texture": "#dark_metal" }, @@ -1383,7 +2818,7 @@ 0, 0, 6.0, - 3.510819999999999 + 3.32604 ], "texture": "#dark_metal" }, @@ -1391,7 +2826,7 @@ "uv": [ 0, 0, - 3.510819999999999, + 3.32604, 6.0 ], "texture": "#dark_metal" @@ -1400,7 +2835,7 @@ "uv": [ 0, 0, - 3.510819999999999, + 3.32604, 6.0 ], "texture": "#dark_metal" @@ -1409,13 +2844,13 @@ }, { "from": [ - 6.2445900000000005, - 6.2445900000000005, + 6.33698, + 6.33698, 22.03 ], "to": [ - 9.75541, - 9.75541, + 9.66302, + 9.66302, 27.97 ], "faces": { @@ -1423,8 +2858,8 @@ "uv": [ 0, 0, - 3.510819999999999, - 3.510819999999999 + 3.32604, + 3.32604 ], "texture": "#dark_metal" }, @@ -1432,8 +2867,8 @@ "uv": [ 0, 0, - 3.510819999999999, - 3.510819999999999 + 3.32604, + 3.32604 ], "texture": "#dark_metal" }, @@ -1442,7 +2877,7 @@ 0, 0, 5.939999999999998, - 3.510819999999999 + 3.32604 ], "texture": "#dark_metal" }, @@ -1451,7 +2886,7 @@ 0, 0, 5.939999999999998, - 3.510819999999999 + 3.32604 ], "texture": "#dark_metal" }, @@ -1459,7 +2894,7 @@ "uv": [ 0, 0, - 3.510819999999999, + 3.32604, 5.939999999999998 ], "texture": "#dark_metal" @@ -1468,7 +2903,7 @@ "uv": [ 0, 0, - 3.510819999999999, + 3.32604, 5.939999999999998 ], "texture": "#dark_metal" @@ -1487,12 +2922,12 @@ { "from": [ 8.8, - 5.2, + 5.5, 24.0 ], "to": [ - 9.6, - 6.8, + 9.5, + 6.6, 28.0 ], "faces": { @@ -1500,8 +2935,8 @@ "uv": [ 0, 0, - 0.7999999999999989, - 1.5999999999999996 + 0.6999999999999993, + 1.0999999999999996 ], "texture": "#bare_nozzle" }, @@ -1509,8 +2944,8 @@ "uv": [ 0, 0, - 0.7999999999999989, - 1.5999999999999996 + 0.6999999999999993, + 1.0999999999999996 ], "texture": "#bare_nozzle" }, @@ -1519,7 +2954,7 @@ 0, 0, 4.0, - 1.5999999999999996 + 1.0999999999999996 ], "texture": "#bare_nozzle" }, @@ -1528,7 +2963,7 @@ 0, 0, 4.0, - 1.5999999999999996 + 1.0999999999999996 ], "texture": "#bare_nozzle" }, @@ -1536,7 +2971,7 @@ "uv": [ 0, 0, - 0.7999999999999989, + 0.6999999999999993, 4.0 ], "texture": "#bare_nozzle" @@ -1545,75 +2980,7 @@ "uv": [ 0, 0, - 0.7999999999999989, - 4.0 - ], - "texture": "#bare_nozzle" - } - } - }, - { - "from": [ - 6.4, - 5.2, - 24.0 - ], - "to": [ - 7.199999999999999, - 6.8, - 28.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.7999999999999989, - 1.5999999999999996 - ], - "texture": "#bare_nozzle" - }, - "south": { - "uv": [ - 0, - 0, - 0.7999999999999989, - 1.5999999999999996 - ], - "texture": "#bare_nozzle" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 1.5999999999999996 - ], - "texture": "#bare_nozzle" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 1.5999999999999996 - ], - "texture": "#bare_nozzle" - }, - "up": { - "uv": [ - 0, - 0, - 0.7999999999999989, - 4.0 - ], - "texture": "#bare_nozzle" - }, - "down": { - "uv": [ - 0, - 0, - 0.7999999999999989, + 0.6999999999999993, 4.0 ], "texture": "#bare_nozzle" @@ -1623,206 +2990,138 @@ { "from": [ 6.5, + 5.5, + 24.0 + ], + "to": [ + 7.199999999999999, + 6.6, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + 6.7, 10.5, - -4.0 + -3.0 ], "to": [ - 9.5, - 12.0, - 8.0 + 9.3, + 10.8, + 7.0 ], "faces": { "north": { "uv": [ 0, 0, - 3.0, - 1.5 + 2.6000000000000005, + 0.3000000000000007 ], - "texture": "#dark_metal" + "texture": "#shahed" }, "south": { "uv": [ 0, 0, - 3.0, - 1.5 + 2.6000000000000005, + 0.3000000000000007 ], - "texture": "#dark_metal" + "texture": "#shahed" }, "east": { "uv": [ 0, 0, - 12.0, - 1.5 + 10.0, + 0.3000000000000007 ], - "texture": "#dark_metal" + "texture": "#shahed" }, "west": { "uv": [ 0, 0, - 12.0, - 1.5 + 10.0, + 0.3000000000000007 ], - "texture": "#dark_metal" + "texture": "#shahed" }, "up": { "uv": [ 0, 0, - 3.0, - 12.0 + 2.6000000000000005, + 10.0 ], - "texture": "#dark_metal" + "texture": "#shahed" }, "down": { "uv": [ 0, 0, - 3.0, - 12.0 + 2.6000000000000005, + 10.0 ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 7.7, - 12.0, - 2.0 - ], - "to": [ - 8.3, - 14.5, - 3.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.6000000000000005, - 2.5 - ], - "texture": "#bare_antenna" - }, - "south": { - "uv": [ - 0, - 0, - 0.6000000000000005, - 2.5 - ], - "texture": "#bare_antenna" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.5 - ], - "texture": "#bare_antenna" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.5 - ], - "texture": "#bare_antenna" - }, - "up": { - "uv": [ - 0, - 0, - 0.6000000000000005, - 1.0 - ], - "texture": "#bare_antenna" - }, - "down": { - "uv": [ - 0, - 0, - 0.6000000000000005, - 1.0 - ], - "texture": "#bare_antenna" - } - } - }, - { - "from": [ - 6.8, - 4.8, - -12.0 - ], - "to": [ - 9.2, - 5.6, - -6.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.3999999999999995, - 0.7999999999999998 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.3999999999999995, - 0.7999999999999998 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 0.7999999999999998 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 0.7999999999999998 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.3999999999999995, - 6.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.3999999999999995, - 6.0 - ], - "texture": "#bare_lamp" + "texture": "#shahed" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/tor_chassis.json b/airdef-mod/assets/airdef/models/block/tor_chassis.json index cad7ec5..07c4625 100644 --- a/airdef-mod/assets/airdef/models/block/tor_chassis.json +++ b/airdef-mod/assets/airdef/models/block/tor_chassis.json @@ -1,30 +1,24 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21": "airdef:block/ru_khaki_s21", - "ru_khaki_s31": "airdef:block/ru_khaki_s31", - "ru_khaki_s23": "airdef:block/ru_khaki_s23", - "ru_khaki": "airdef:block/ru_khaki", + "pilot_olive": "airdef:block/pilot_olive", "dark_metal": "airdef:block/dark_metal", - "bare_nozzle": "airdef:block/bare_nozzle", "track": "airdef:block/track", - "track_side_s31": "airdef:block/track_side_s31", - "ru_khaki_s13": "airdef:block/ru_khaki_s13", - "bare_lamp": "airdef:block/bare_lamp", - "mk_ru2": "airdef:block/mk_ru2", - "particle": "airdef:block/ru_khaki_s21" + "hub": "airdef:block/hub", + "metal": "airdef:block/metal", + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -10.0, + -9.0, 4.0, - -14.0 + -13.0 ], "to": [ - 26.0, - 15.0, - 32.0 + 25.0, + 13.0, + 30.0 ], "faces": { "north": { @@ -32,36 +26,36 @@ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -70,7 +64,7 @@ 16, 16 ], - "texture": "#ru_khaki_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -79,88 +73,88 @@ 16, 16 ], - "texture": "#ru_khaki_s23" - } - } - }, - { - "from": [ - -10.0, - 4.0, - -16.0 - ], - "to": [ - 26.0, - 10.0, - -14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_khaki_s21" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_khaki_s21" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_khaki" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_khaki" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_khaki_s21" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" } } }, { "from": [ -9.0, - 10.0, - -15.0 + 4.0, + -16.0 ], "to": [ 25.0, - 14.0, - -12.0 + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 ], "faces": { "north": { @@ -170,7 +164,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -179,43 +173,43 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_khaki" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_khaki" + "texture": "#pilot_olive" }, "up": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "down": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" } }, "rotation": { @@ -223,21 +217,89 @@ "axis": "x", "origin": [ 8.0, - 12.0, - -13.5 + 11.0, + -11.5 ] } }, { "from": [ -8.0, - 15.0, - 22.0 + 13.0, + -7.0 ], "to": [ 24.0, - 16.0, - 32.0 + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 ], "faces": { "north": { @@ -262,7 +324,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal" @@ -271,7 +333,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal" @@ -281,7 +343,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal" }, @@ -290,7 +352,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal" } @@ -298,13 +360,13 @@ }, { "from": [ - -5.0, - 15.2, - 24.0 + -13.0, + 0.0, + -15.0 ], "to": [ - 21.0, - 15.8, + -9.0, + 8.5, 31.0 ], "faces": { @@ -312,76 +374,3136 @@ "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "south": { "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "east": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "west": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "up": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle" + "texture": "#track" }, "down": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle" + "texture": "#track" } } }, { "from": [ - -1.0, + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -12.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -13.5 + ], + "to": [ + -13.02, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -13.5 + ], + "to": [ + 29.08, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -12.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -6.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -0.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -0.75 + ], + "to": [ + -13.02, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -0.75 + ], + "to": [ + 29.08, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 5.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 4.5 + ], + "to": [ + 29.08, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 5.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 11.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 11.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 10.5 + ], + "to": [ + -13.02, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 10.5 + ], + "to": [ + 29.08, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 11.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 11.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 17.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 17.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 16.5 + ], + "to": [ + 29.08, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 17.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 17.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 23.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -7.0, 15.0, -10.0 ], "to": [ - 9.0, - 16.0, - -4.0 + 2.0, + 15.7, + -5.0 ], "faces": { "north": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], "texture": "#dark_metal" }, @@ -389,8 +3511,8 @@ "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], "texture": "#dark_metal" }, @@ -398,8 +3520,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], "texture": "#dark_metal" }, @@ -407,8 +3529,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], "texture": "#dark_metal" }, @@ -416,769 +3538,89 @@ "uv": [ 0, 0, - 10.0, - 6.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 10.0, - 6.0 - ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - -14.0, - 0.0, - -15.0 - ], - "to": [ - -10.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - } - } - }, - { - "from": [ - 26.0, - 0.0, - -15.0 - ], - "to": [ - 30.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track" - } - } - }, - { - "from": [ - -14.5, - 5.0, - -15.0 - ], - "to": [ - -10.0, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, + 9.0, 5.0 ], - "texture": "#ru_khaki" + "texture": "#dark_metal" }, - "south": { + "down": { "uv": [ 0, 0, - 4.5, + 9.0, 5.0 ], - "texture": "#ru_khaki" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13" - } - } - }, - { - "from": [ - 26.0, - 5.0, - -15.0 - ], - "to": [ - 30.5, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_khaki" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_khaki" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13" - } - } - }, - { - "from": [ - -4.0, - 11.0, - -16.0 - ], - "to": [ - -1.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 17.0, - 11.0, - -16.0 - ], - "to": [ - 20.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 26.0, - 11.0, - 0.0 - ], - "to": [ - 26.12, - 14.5, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - } - } - }, - { - "from": [ - -10.120000000000001, - 11.0, - 0.0 - ], - "to": [ - -10.0, - 14.5, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2" - }, - "east": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "west": { - "uv": [ - 0, - 0, - 8.0, - 3.5 - ], - "texture": "#mk_ru2" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 8.0 - ], - "texture": "#mk_ru2" - } - } - }, - { - "from": [ - 3.0, - 16.0, - 2.0 - ], - "to": [ - 7.0, - 17.4, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], "texture": "#dark_metal" } } }, { "from": [ - 9.0, - 16.0, - 2.0 + 15.0, + 14.0, + 21.0 ], "to": [ - 13.0, - 17.4, - 8.0 + 22.0, + 15.0, + 28.0 ], "faces": { "north": { "uv": [ 0, 0, - 4.0, - 1.3999999999999986 + 7.0, + 1.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "south": { "uv": [ 0, 0, - 4.0, - 1.3999999999999986 + 7.0, + 1.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "east": { "uv": [ 0, 0, - 6.0, - 1.3999999999999986 + 7.0, + 1.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "west": { "uv": [ 0, 0, - 6.0, - 1.3999999999999986 + 7.0, + 1.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "up": { "uv": [ 0, 0, - 4.0, - 6.0 + 7.0, + 7.0 ], - "texture": "#dark_metal" + "texture": "#metal" }, "down": { "uv": [ 0, 0, - 4.0, - 6.0 + 7.0, + 7.0 ], - "texture": "#dark_metal" - } - } - }, - { - "from": [ - 5.0, - 16.0, - -8.0 - ], - "to": [ - 11.0, - 17.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 6.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 6.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 1.0 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#bare_lamp" + "texture": "#metal" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/tor_chassis_burnt.json b/airdef-mod/assets/airdef/models/block/tor_chassis_burnt.json index 17ac271..b417f39 100644 --- a/airdef-mod/assets/airdef/models/block/tor_chassis_burnt.json +++ b/airdef-mod/assets/airdef/models/block/tor_chassis_burnt.json @@ -1,30 +1,24 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21_b": "airdef:block/ru_khaki_s21_b", - "ru_khaki_s31_b": "airdef:block/ru_khaki_s31_b", - "ru_khaki_s23_b": "airdef:block/ru_khaki_s23_b", - "ru_khaki_b": "airdef:block/ru_khaki_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "dark_metal_b": "airdef:block/dark_metal_b", - "bare_nozzle_b": "airdef:block/bare_nozzle_b", "track_b": "airdef:block/track_b", - "track_side_s31_b": "airdef:block/track_side_s31_b", - "ru_khaki_s13_b": "airdef:block/ru_khaki_s13_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "mk_ru2_b": "airdef:block/mk_ru2_b", - "particle": "airdef:block/ru_khaki_s21_b" + "hub_b": "airdef:block/hub_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -10.0, + -9.0, 4.0, - -14.0 + -13.0 ], "to": [ - 26.0, - 15.0, - 32.0 + 25.0, + 13.0, + 30.0 ], "faces": { "north": { @@ -32,36 +26,36 @@ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 11.0 + 9.0 ], - "texture": "#ru_khaki_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -70,7 +64,7 @@ 16, 16 ], - "texture": "#ru_khaki_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -79,88 +73,88 @@ 16, 16 ], - "texture": "#ru_khaki_s23_b" - } - } - }, - { - "from": [ - -10.0, - 4.0, - -16.0 - ], - "to": [ - 26.0, - 10.0, - -14.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_khaki_s21_b" - }, - "south": { - "uv": [ - 0, - 0, - 16, - 6.0 - ], - "texture": "#ru_khaki_s21_b" - }, - "east": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_khaki_b" - }, - "west": { - "uv": [ - 0, - 0, - 2.0, - 6.0 - ], - "texture": "#ru_khaki_b" - }, - "up": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_khaki_s21_b" - }, - "down": { - "uv": [ - 0, - 0, - 16, - 2.0 - ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" } } }, { "from": [ -9.0, - 10.0, - -15.0 + 4.0, + -16.0 ], "to": [ 25.0, - 14.0, - -12.0 + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 ], "faces": { "north": { @@ -170,7 +164,7 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -179,43 +173,43 @@ 16, 4.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_khaki_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, - 3.0, + 5.0, 4.0 ], - "texture": "#ru_khaki_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ 0, 0, 16, - 3.0 + 5.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" } }, "rotation": { @@ -223,21 +217,89 @@ "axis": "x", "origin": [ 8.0, - 12.0, - -13.5 + 11.0, + -11.5 ] } }, { "from": [ -8.0, - 15.0, - 22.0 + 13.0, + -7.0 ], "to": [ 24.0, - 16.0, - 32.0 + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 ], "faces": { "north": { @@ -262,7 +324,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal_b" @@ -271,7 +333,7 @@ "uv": [ 0, 0, - 10.0, + 9.0, 1.0 ], "texture": "#dark_metal_b" @@ -281,7 +343,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal_b" }, @@ -290,7 +352,7 @@ 0, 0, 16, - 10.0 + 9.0 ], "texture": "#dark_metal_b" } @@ -298,13 +360,13 @@ }, { "from": [ - -5.0, - 15.2, - 24.0 + -13.0, + 0.0, + -15.0 ], "to": [ - 21.0, - 15.8, + -9.0, + 8.5, 31.0 ], "faces": { @@ -312,76 +374,2388 @@ "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "south": { "uv": [ 0, 0, - 16, - 0.6000000000000014 + 4.0, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "east": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "west": { "uv": [ 0, 0, - 7.0, - 0.6000000000000014 + 16, + 8.5 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "up": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" }, "down": { "uv": [ 0, 0, - 16, - 7.0 + 4.0, + 16 ], - "texture": "#bare_nozzle_b" + "texture": "#track_b" } } }, { "from": [ - -1.0, + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -13.5 + ], + "to": [ + -13.02, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -6.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -0.75 + ], + "to": [ + -13.02, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -0.75 + ], + "to": [ + 29.08, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 5.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 4.5 + ], + "to": [ + 29.08, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 11.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 10.5 + ], + "to": [ + -13.02, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 11.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 17.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 17.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 23.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -7.0, 15.0, -10.0 ], "to": [ - 9.0, - 16.0, - -4.0 + 2.0, + 15.7, + -5.0 ], "faces": { "north": { "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], "texture": "#dark_metal_b" }, @@ -389,8 +2763,8 @@ "uv": [ 0, 0, - 10.0, - 1.0 + 9.0, + 0.6999999999999993 ], "texture": "#dark_metal_b" }, @@ -398,8 +2772,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], "texture": "#dark_metal_b" }, @@ -407,8 +2781,8 @@ "uv": [ 0, 0, - 6.0, - 1.0 + 5.0, + 0.6999999999999993 ], "texture": "#dark_metal_b" }, @@ -416,8 +2790,8 @@ "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], "texture": "#dark_metal_b" }, @@ -425,8 +2799,8 @@ "uv": [ 0, 0, - 10.0, - 6.0 + 9.0, + 5.0 ], "texture": "#dark_metal_b" } @@ -434,547 +2808,71 @@ }, { "from": [ - -14.0, - 0.0, - -15.0 + 15.0, + 14.0, + 21.0 ], "to": [ - -10.0, - 9.0, - 31.0 + 22.0, + 15.0, + 28.0 ], "faces": { "north": { "uv": [ 0, 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - } - } - }, - { - "from": [ - 26.0, - 0.0, - -15.0 - ], - "to": [ - 30.0, - 9.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 9.0 - ], - "texture": "#track_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 9.0 - ], - "texture": "#track_side_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 16 - ], - "texture": "#track_b" - } - } - }, - { - "from": [ - -14.5, - 5.0, - -15.0 - ], - "to": [ - -10.0, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_khaki_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_khaki_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13_b" - } - } - }, - { - "from": [ - 26.0, - 5.0, - -15.0 - ], - "to": [ - 30.5, - 10.0, - 31.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_khaki_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.5, - 5.0 - ], - "texture": "#ru_khaki_b" - }, - "east": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31_b" - }, - "west": { - "uv": [ - 0, - 0, - 16, - 5.0 - ], - "texture": "#ru_khaki_s31_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.5, - 16 - ], - "texture": "#ru_khaki_s13_b" - } - } - }, - { - "from": [ - 17.0, - 11.0, - -16.0 - ], - "to": [ - 20.0, - 13.0, - -15.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 1.0, - 2.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, + 7.0, 1.0 ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 1.0 - ], - "texture": "#bare_lamp_b" - } - } - }, - { - "from": [ - 26.0, - 11.0, - 0.0 - ], - "to": [ - 26.12, - 14.5, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 3.5 - ], - "texture": "#mk_ru2_b" + "texture": "#metal_b" }, "south": { "uv": [ 0, 0, - 0.25, - 3.5 + 7.0, + 1.0 ], - "texture": "#mk_ru2_b" + "texture": "#metal_b" }, "east": { "uv": [ 0, 0, - 8.0, - 3.5 + 7.0, + 1.0 ], - "texture": "#mk_ru2_b" + "texture": "#metal_b" }, "west": { "uv": [ 0, 0, - 8.0, - 3.5 + 7.0, + 1.0 ], - "texture": "#mk_ru2_b" + "texture": "#metal_b" }, "up": { "uv": [ 0, 0, - 0.25, - 8.0 + 7.0, + 7.0 ], - "texture": "#mk_ru2_b" + "texture": "#metal_b" }, "down": { "uv": [ 0, 0, - 0.25, - 8.0 + 7.0, + 7.0 ], - "texture": "#mk_ru2_b" - } - } - }, - { - "from": [ - 9.0, - 16.0, - 2.0 - ], - "to": [ - 13.0, - 17.4, - 8.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 4.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 6.0, - 1.3999999999999986 - ], - "texture": "#dark_metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 4.0, - 6.0 - ], - "texture": "#dark_metal_b" - } - } - }, - { - "from": [ - 5.0, - 16.0, - -8.0 - ], - "to": [ - 11.0, - 17.0, - -4.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 6.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 6.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 4.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 4.0, - 1.0 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 6.0, - 4.0 - ], - "texture": "#bare_lamp_b" + "texture": "#metal_b" } } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/tor_turret.json b/airdef-mod/assets/airdef/models/block/tor_turret.json index c3bd5db..437aeaf 100644 --- a/airdef-mod/assets/airdef/models/block/tor_turret.json +++ b/airdef-mod/assets/airdef/models/block/tor_turret.json @@ -1,12 +1,11 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21": "airdef:block/ru_khaki_s21", - "ru_khaki_s22": "airdef:block/ru_khaki_s22", + "pilot_olive": "airdef:block/pilot_olive", "radar_face": "airdef:block/radar_face", "dark_metal": "airdef:block/dark_metal", "tube_cap": "airdef:block/tube_cap", - "particle": "airdef:block/ru_khaki_s21" + "particle": "airdef:block/pilot_olive" }, "elements": [ { @@ -17,7 +16,7 @@ ], "to": [ 20.0, - 16.0, + 12.0, 22.0 ], "faces": { @@ -26,36 +25,36 @@ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "west": { "uv": [ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -64,7 +63,7 @@ 16, 16 ], - "texture": "#ru_khaki_s22" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -73,7 +72,75 @@ 16, 16 ], - "texture": "#ru_khaki_s22" + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.0, + 12.0, + -3.0 + ], + "to": [ + 19.0, + 16.0, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" } } }, @@ -758,4 +825,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/tor_turret_burnt.json b/airdef-mod/assets/airdef/models/block/tor_turret_burnt.json index 0f681c4..bb8e41a 100644 --- a/airdef-mod/assets/airdef/models/block/tor_turret_burnt.json +++ b/airdef-mod/assets/airdef/models/block/tor_turret_burnt.json @@ -1,12 +1,11 @@ { "ambientocclusion": false, "textures": { - "ru_khaki_s21_b": "airdef:block/ru_khaki_s21_b", - "ru_khaki_s22_b": "airdef:block/ru_khaki_s22_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "radar_face_b": "airdef:block/radar_face_b", "dark_metal_b": "airdef:block/dark_metal_b", "tube_cap_b": "airdef:block/tube_cap_b", - "particle": "airdef:block/ru_khaki_s21_b" + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { @@ -17,7 +16,7 @@ ], "to": [ 20.0, - 16.0, + 12.0, 22.0 ], "faces": { @@ -26,36 +25,36 @@ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ 0, 0, 16, - 16 + 12.0 ], - "texture": "#ru_khaki_s21_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -64,7 +63,7 @@ 16, 16 ], - "texture": "#ru_khaki_s22_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -73,7 +72,75 @@ 16, 16 ], - "texture": "#ru_khaki_s22_b" + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.0, + 12.0, + -3.0 + ], + "to": [ + 19.0, + 16.0, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" } } }, @@ -622,4 +689,4 @@ } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/tpk_cap.json b/airdef-mod/assets/airdef/models/block/tpk_cap.json new file mode 100644 index 0000000..4403e51 --- /dev/null +++ b/airdef-mod/assets/airdef/models/block/tpk_cap.json @@ -0,0 +1,282 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap": "airdef:block/tube_cap", + "dark_metal": "airdef:block/dark_metal", + "particle": "airdef:block/tube_cap" + }, + "elements": [ + { + "from": [ + 2.5, + 0.0, + 7.7 + ], + "to": [ + 13.5, + 16.0, + 8.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.0, + 16 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 11.0, + 16 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 16 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 16 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 11.0, + 0.5499999999999998 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 11.0, + 0.5499999999999998 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 0.0, + 2.5, + 7.7 + ], + "to": [ + 2.5, + 13.5, + 8.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 13.5, + 2.5, + 7.7 + ], + "to": [ + 16.0, + 13.5, + 8.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 5.0, + 5.0, + 8.25 + ], + "to": [ + 11.0, + 11.0, + 8.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 0.25 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 0.25 + ], + "texture": "#dark_metal" + } + } + } + ] +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/models/block/tpk_cap_burnt.json b/airdef-mod/assets/airdef/models/block/tpk_cap_burnt.json new file mode 100644 index 0000000..a0b1e65 --- /dev/null +++ b/airdef-mod/assets/airdef/models/block/tpk_cap_burnt.json @@ -0,0 +1,282 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap_b": "airdef:block/tube_cap_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "particle": "airdef:block/tube_cap_b" + }, + "elements": [ + { + "from": [ + 2.5, + 0.0, + 7.7 + ], + "to": [ + 13.5, + 16.0, + 8.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.0, + 16 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.0, + 16 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 16 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 16 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.0, + 0.5499999999999998 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.0, + 0.5499999999999998 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 0.0, + 2.5, + 7.7 + ], + "to": [ + 2.5, + 13.5, + 8.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 13.5, + 2.5, + 7.7 + ], + "to": [ + 16.0, + 13.5, + 8.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5499999999999998, + 11.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 0.5499999999999998 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 5.0, + 5.0, + 8.25 + ], + "to": [ + 11.0, + 11.0, + 8.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 0.25 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 0.25 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} \ No newline at end of file diff --git a/airdef-mod/assets/airdef/models/block/tzm_truck.json b/airdef-mod/assets/airdef/models/block/tzm_truck.json index bd37957..4090a6f 100644 --- a/airdef-mod/assets/airdef/models/block/tzm_truck.json +++ b/airdef-mod/assets/airdef/models/block/tzm_truck.json @@ -1,30 +1,26 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21": "airdef:block/ru_green_s21", - "ru_green_s31": "airdef:block/ru_green_s31", - "ru_green_s23": "airdef:block/ru_green_s23", + "pilot_olive": "airdef:block/pilot_olive", "glass_cab": "airdef:block/glass_cab", - "ru_green": "airdef:block/ru_green", "dark_metal": "airdef:block/dark_metal", - "tube_cap": "airdef:block/tube_cap", - "tube": "airdef:block/tube", - "metal": "airdef:block/metal", + "bare_lamp": "airdef:block/bare_lamp", "tire": "airdef:block/tire", "hub": "airdef:block/hub", - "mk_ru2": "airdef:block/mk_ru2", - "bare_lamp": "airdef:block/bare_lamp", - "particle": "airdef:block/ru_green_s21" + "metal": "airdef:block/metal", + "tube_cap": "airdef:block/tube_cap", + "tube": "airdef:block/tube", + "particle": "airdef:block/pilot_olive" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -36,7 +32,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "south": { "uv": [ @@ -45,7 +41,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21" + "texture": "#pilot_olive" }, "east": { "uv": [ @@ -54,7 +50,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "west": { "uv": [ @@ -63,7 +59,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31" + "texture": "#pilot_olive" }, "up": { "uv": [ @@ -72,7 +68,7 @@ 16, 16 ], - "texture": "#ru_green_s23" + "texture": "#pilot_olive" }, "down": { "uv": [ @@ -81,18 +77,86 @@ 16, 16 ], - "texture": "#ru_green_s23" + "texture": "#pilot_olive" } } }, { "from": [ - -6.0, + -5.0, 9.0, -16.0 ], "to": [ - 22.0, + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, 20.0, -6.0 ], @@ -102,7 +166,75 @@ 0, 0, 16, - 11.0 + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 ], "texture": "#glass_cab" }, @@ -110,46 +242,318 @@ "uv": [ 0, 0, - 16, - 11.0 + 11.5, + 3.3000000000000007 ], - "texture": "#ru_green_s21" + "texture": "#glass_cab" }, "east": { "uv": [ 0, 0, - 10.0, - 11.0 + 0.25, + 3.3000000000000007 ], - "texture": "#ru_green" + "texture": "#glass_cab" }, "west": { "uv": [ 0, 0, - 10.0, - 11.0 + 0.25, + 3.3000000000000007 ], - "texture": "#ru_green" + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.0 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" }, "up": { "uv": [ 0, 0, 16, - 10.0 + 0.6999999999999993 ], - "texture": "#ru_green_s21" + "texture": "#dark_metal" }, "down": { "uv": [ 0, 0, 16, - 10.0 + 0.6999999999999993 ], - "texture": "#ru_green_s21" + "texture": "#dark_metal" } } }, @@ -157,7 +561,3407 @@ "from": [ -4.0, 9.0, - -2.0 + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -3.0 ], "to": [ 20.0, @@ -221,6 +4025,210 @@ } } }, + { + "from": [ + 18.0, + 12.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 6.0, + 24.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 6.0, + 20.0, + -4.0 + ], + "to": [ + 7.0, + 24.0, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, { "from": [ -3.0, @@ -424,1574 +4432,6 @@ "texture": "#tube" } } - }, - { - "from": [ - 18.0, - 12.0, - -4.0 - ], - "to": [ - 21.0, - 26.0, - -1.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - 6.0, - 24.0, - -4.0 - ], - "to": [ - 21.0, - 26.0, - -1.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 15.0, - 2.0 - ], - "texture": "#metal" - }, - "south": { - "uv": [ - 0, - 0, - 15.0, - 2.0 - ], - "texture": "#metal" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#metal" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#metal" - }, - "up": { - "uv": [ - 0, - 0, - 15.0, - 3.0 - ], - "texture": "#metal" - }, - "down": { - "uv": [ - 0, - 0, - 15.0, - 3.0 - ], - "texture": "#metal" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.5, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 25.0, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.53, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 24.97, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.5, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 25.0, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.53, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 24.97, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.5, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 25.0, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.53, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 24.97, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 22.0, - 11.0, - -14.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru2" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru2" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru2" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru2" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru2" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru2" - } - } - }, - { - "from": [ - -6.119999999999997, - 11.0, - -14.0 - ], - "to": [ - -6.0, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru2" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru2" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru2" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru2" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru2" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru2" - } - } - }, - { - "from": [ - -4.5, - 10.0, - -16.0 - ], - "to": [ - -2.5, - 11.6, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - } - } - }, - { - "from": [ - 18.5, - 10.0, - -16.0 - ], - "to": [ - 20.5, - 11.6, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp" - } - } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/models/block/tzm_truck_burnt.json b/airdef-mod/assets/airdef/models/block/tzm_truck_burnt.json index ccbe7bc..7c79455 100644 --- a/airdef-mod/assets/airdef/models/block/tzm_truck_burnt.json +++ b/airdef-mod/assets/airdef/models/block/tzm_truck_burnt.json @@ -1,30 +1,26 @@ { "ambientocclusion": false, "textures": { - "ru_green_s21_b": "airdef:block/ru_green_s21_b", - "ru_green_s31_b": "airdef:block/ru_green_s31_b", - "ru_green_s23_b": "airdef:block/ru_green_s23_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", "glass_cab_b": "airdef:block/glass_cab_b", - "ru_green_b": "airdef:block/ru_green_b", "dark_metal_b": "airdef:block/dark_metal_b", - "tube_cap_b": "airdef:block/tube_cap_b", - "tube_b": "airdef:block/tube_b", - "metal_b": "airdef:block/metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", "tire_b": "airdef:block/tire_b", "hub_b": "airdef:block/hub_b", - "mk_ru2_b": "airdef:block/mk_ru2_b", - "bare_lamp_b": "airdef:block/bare_lamp_b", - "particle": "airdef:block/ru_green_s21_b" + "metal_b": "airdef:block/metal_b", + "tube_cap_b": "airdef:block/tube_cap_b", + "tube_b": "airdef:block/tube_b", + "particle": "airdef:block/pilot_olive_b" }, "elements": [ { "from": [ - -6.0, + -5.0, 5.0, - -16.0 + -15.0 ], "to": [ - 22.0, + 21.0, 9.0, 32.0 ], @@ -36,7 +32,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "south": { "uv": [ @@ -45,7 +41,7 @@ 16, 4.0 ], - "texture": "#ru_green_s21_b" + "texture": "#pilot_olive_b" }, "east": { "uv": [ @@ -54,7 +50,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "west": { "uv": [ @@ -63,7 +59,7 @@ 16, 4.0 ], - "texture": "#ru_green_s31_b" + "texture": "#pilot_olive_b" }, "up": { "uv": [ @@ -72,7 +68,7 @@ 16, 16 ], - "texture": "#ru_green_s23_b" + "texture": "#pilot_olive_b" }, "down": { "uv": [ @@ -81,18 +77,86 @@ 16, 16 ], - "texture": "#ru_green_s23_b" + "texture": "#pilot_olive_b" } } }, { "from": [ - -6.0, + -5.0, 9.0, -16.0 ], "to": [ - 22.0, + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, 20.0, -6.0 ], @@ -102,7 +166,75 @@ 0, 0, 16, - 11.0 + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 ], "texture": "#glass_cab_b" }, @@ -110,46 +242,2154 @@ "uv": [ 0, 0, - 16, - 11.0 + 11.5, + 3.3000000000000007 ], - "texture": "#ru_green_s21_b" + "texture": "#glass_cab_b" }, "east": { "uv": [ 0, 0, - 10.0, - 11.0 + 0.25, + 3.3000000000000007 ], - "texture": "#ru_green_b" + "texture": "#glass_cab_b" }, "west": { "uv": [ 0, 0, - 10.0, - 11.0 + 0.25, + 3.3000000000000007 ], - "texture": "#ru_green_b" + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" }, "up": { "uv": [ 0, 0, 16, - 10.0 + 0.6999999999999993 ], - "texture": "#ru_green_s21_b" + "texture": "#dark_metal_b" }, "down": { "uv": [ 0, 0, 16, - 10.0 + 0.6999999999999993 ], - "texture": "#ru_green_s21_b" + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" } } }, @@ -157,7 +2397,7 @@ "from": [ -4.0, 9.0, - -2.0 + -3.0 ], "to": [ 20.0, @@ -221,6 +2461,210 @@ } } }, + { + "from": [ + 18.0, + 12.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 6.0, + 24.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 6.0, + 20.0, + -4.0 + ], + "to": [ + 7.0, + 24.0, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, { "from": [ -3.0, @@ -424,1438 +2868,6 @@ "texture": "#tube_b" } } - }, - { - "from": [ - 18.0, - 12.0, - -4.0 - ], - "to": [ - 21.0, - 26.0, - -1.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 14.0 - ], - "texture": "#metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.0, - 3.0 - ], - "texture": "#metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.0, - 3.0 - ], - "texture": "#metal_b" - } - } - }, - { - "from": [ - 6.0, - 24.0, - -4.0 - ], - "to": [ - 21.0, - 26.0, - -1.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 15.0, - 2.0 - ], - "texture": "#metal_b" - }, - "south": { - "uv": [ - 0, - 0, - 15.0, - 2.0 - ], - "texture": "#metal_b" - }, - "east": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#metal_b" - }, - "west": { - "uv": [ - 0, - 0, - 3.0, - 2.0 - ], - "texture": "#metal_b" - }, - "up": { - "uv": [ - 0, - 0, - 15.0, - 3.0 - ], - "texture": "#metal_b" - }, - "down": { - "uv": [ - 0, - 0, - 15.0, - 3.0 - ], - "texture": "#metal_b" - } - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.5, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 25.0, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -12.65755 - ], - "to": [ - -5.53, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -12.65755 - ], - "to": [ - 24.97, - 8.65755, - -4.3424499999999995 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - -8.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.5, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 25.0, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - -5.53, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - -3.6575500000000005 - ], - "to": [ - 24.97, - 8.65755, - 4.6575500000000005 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 0.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.5, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 25.0, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 15.34245 - ], - "to": [ - -5.53, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 15.34245 - ], - "to": [ - 24.97, - 8.65755, - 23.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 19.5 - ] - } - }, - { - "from": [ - -9.0, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.5, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - 21.5, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 25.0, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.5, - 8.315100000000001 - ], - "texture": "#tire_b" - } - } - }, - { - "from": [ - -8.97, - 0.3424499999999995, - 23.34245 - ], - "to": [ - -5.53, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4400000000000004, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - -7.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 21.53, - 0.3424499999999995, - 23.34245 - ], - "to": [ - 24.97, - 8.65755, - 31.65755 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "south": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "east": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "west": { - "uv": [ - 0, - 0, - 8.315100000000001, - 8.315100000000001 - ], - "texture": "#hub_b" - }, - "up": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - }, - "down": { - "uv": [ - 0, - 0, - 3.4399999999999977, - 8.315100000000001 - ], - "texture": "#tire_b" - } - }, - "rotation": { - "angle": 45, - "axis": "x", - "origin": [ - 23.25, - 4.5, - 27.5 - ] - } - }, - { - "from": [ - 22.0, - 11.0, - -14.0 - ], - "to": [ - 22.119999999999997, - 15.0, - -7.0 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru2_b" - }, - "south": { - "uv": [ - 0, - 0, - 0.25, - 4.0 - ], - "texture": "#mk_ru2_b" - }, - "east": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru2_b" - }, - "west": { - "uv": [ - 0, - 0, - 7.0, - 4.0 - ], - "texture": "#mk_ru2_b" - }, - "up": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru2_b" - }, - "down": { - "uv": [ - 0, - 0, - 0.25, - 7.0 - ], - "texture": "#mk_ru2_b" - } - } - }, - { - "from": [ - -4.5, - 10.0, - -16.0 - ], - "to": [ - -2.5, - 11.6, - -15.4 - ], - "faces": { - "north": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "south": { - "uv": [ - 0, - 0, - 2.0, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "east": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "west": { - "uv": [ - 0, - 0, - 0.5999999999999996, - 1.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "up": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp_b" - }, - "down": { - "uv": [ - 0, - 0, - 2.0, - 0.5999999999999996 - ], - "texture": "#bare_lamp_b" - } - } } ] -} \ No newline at end of file +} diff --git a/airdef-mod/assets/airdef/textures/block/pilot_nato.png b/airdef-mod/assets/airdef/textures/block/pilot_nato.png new file mode 100644 index 0000000..53549cb Binary files /dev/null and b/airdef-mod/assets/airdef/textures/block/pilot_nato.png differ diff --git a/airdef-mod/assets/airdef/textures/block/pilot_nato_b.png b/airdef-mod/assets/airdef/textures/block/pilot_nato_b.png new file mode 100644 index 0000000..005a924 Binary files /dev/null and b/airdef-mod/assets/airdef/textures/block/pilot_nato_b.png differ diff --git a/airdef-mod/assets/airdef/textures/block/pilot_olive.png b/airdef-mod/assets/airdef/textures/block/pilot_olive.png new file mode 100644 index 0000000..875dd0e Binary files /dev/null and b/airdef-mod/assets/airdef/textures/block/pilot_olive.png differ diff --git a/airdef-mod/assets/airdef/textures/block/pilot_olive_b.png b/airdef-mod/assets/airdef/textures/block/pilot_olive_b.png new file mode 100644 index 0000000..005a924 Binary files /dev/null and b/airdef-mod/assets/airdef/textures/block/pilot_olive_b.png differ diff --git a/airdef-mod/data/airdef/horizon/block/radar_mast.json b/airdef-mod/data/airdef/horizon/block/radar_mast.json new file mode 100644 index 0000000..47be203 --- /dev/null +++ b/airdef-mod/data/airdef/horizon/block/radar_mast.json @@ -0,0 +1,4 @@ +{ + "template": "minecraft:stone", + "item": false +} \ No newline at end of file diff --git a/airdef-mod/data/airdef/horizon/block/radar_mast_burnt.json b/airdef-mod/data/airdef/horizon/block/radar_mast_burnt.json new file mode 100644 index 0000000..47be203 --- /dev/null +++ b/airdef-mod/data/airdef/horizon/block/radar_mast_burnt.json @@ -0,0 +1,4 @@ +{ + "template": "minecraft:stone", + "item": false +} \ No newline at end of file diff --git a/airdef-mod/data/airdef/horizon/block/rm48u.json b/airdef-mod/data/airdef/horizon/block/rm48u.json new file mode 100644 index 0000000..47be203 --- /dev/null +++ b/airdef-mod/data/airdef/horizon/block/rm48u.json @@ -0,0 +1,4 @@ +{ + "template": "minecraft:stone", + "item": false +} \ No newline at end of file diff --git a/airdef-mod/data/airdef/horizon/block/tpk_cap.json b/airdef-mod/data/airdef/horizon/block/tpk_cap.json new file mode 100644 index 0000000..47be203 --- /dev/null +++ b/airdef-mod/data/airdef/horizon/block/tpk_cap.json @@ -0,0 +1,4 @@ +{ + "template": "minecraft:stone", + "item": false +} \ No newline at end of file diff --git a/airdef-mod/data/airdef/horizon/block/tpk_cap_burnt.json b/airdef-mod/data/airdef/horizon/block/tpk_cap_burnt.json new file mode 100644 index 0000000..47be203 --- /dev/null +++ b/airdef-mod/data/airdef/horizon/block/tpk_cap_burnt.json @@ -0,0 +1,4 @@ +{ + "template": "minecraft:stone", + "item": false +} \ No newline at end of file diff --git a/airdef-mod/examples/intel_bench.rs b/airdef-mod/examples/intel_bench.rs new file mode 100644 index 0000000..b4d2025 --- /dev/null +++ b/airdef-mod/examples/intel_bench.rs @@ -0,0 +1,57 @@ +//! Enemy-picture outcomes on fixed seeds (#127): what changed for the raids, measured. +//! +//! Runs the standard scenarios over a fixed seed set and prints one JSON object with the +//! completion, losses and failure reasons of every run — the before/after comparison of the +//! honest EnemyPicture is judged on these, not on "it got smarter". +//! +//! cargo run --release --example intel_bench +#[path = "sim_run/harness.rs"] +mod harness; + +use harness::{big_smart, ew_adaptive, logistics, Scenario}; + +fn esc(s: &str) -> String { + s.chars().flat_map(|c| match c { + '"' => "\\\"".chars().collect::>(), + '\\' => "\\\\".chars().collect::>(), + c => vec![c], + }).collect() +} + +fn run(sc: &Scenario) -> String { + let r = harness::run(sc); + format!( + "{{\"scenario\":\"{}\",\"seed\":{},\"ticks_run\":{},\"raids_ordered\":{},\"raids_ended\":{},\"all_ended\":{},\"launched\":{},\"kills\":{},\"hits\":{},\"crashes\":{},\"sams_lost\":{},\"fired\":{},\"retargets_max\":{},\"air_bytes\":{},\"switches_max\":{},\"intents\":\"{}\",\"failures\":\"{}\"}}", + esc(&r.name), + r.seed, + r.ticks_run, + r.raids_ordered, + r.raids_ended, + r.all_ended, + r.launched, + r.kills, + r.hits, + r.crashes, + r.sams_lost, + r.fired, + r.retargets_max, + r.air_bytes, + r.switches_max, + esc(&r.intents), + esc(&r.failures), + ) +} + +pub fn main() { + let scenarios: Vec Scenario> = vec![big_smart, logistics, ew_adaptive]; + let seeds = [1u64, 7, 42]; + let mut rows = Vec::new(); + for make in &scenarios { + for &seed in &seeds { + let mut sc = make(); + sc.seed = seed; + rows.push(run(&sc)); + } + } + println!("[{}]", rows.join(",\n")); +} diff --git a/airdef-mod/examples/sim_run/harness.rs b/airdef-mod/examples/sim_run/harness.rs index e6c4c01..ef5d4f5 100644 --- a/airdef-mod/examples/sim_run/harness.rs +++ b/airdef-mod/examples/sim_run/harness.rs @@ -5,6 +5,8 @@ //! Native timings say nothing about the WASM build or the GPU; use them to compare runs of the //! same machine. #![allow(dead_code)] +#[path = "../../src/world_view.rs"] +mod world_view; use airdef::sim::{Aim, Approach, DroneKind, Ground, Out, RaidOrder, Sam, SamKind, Sim}; use glam::DVec3; use std::collections::HashMap; @@ -126,6 +128,25 @@ pub struct Report { pub raids_ordered: usize, pub raids_ended: usize, pub all_ended: bool, + /// Systems the defence lost (destroyed units at the end). + pub sams_lost: u32, + /// The most retarget route jobs waiting at once (#127). + pub retargets_max: usize, + /// The largest air section a save would have taken, in bytes (#127). + pub air_bytes: usize, + /// The aims every drone ended with, by intent: "plan:1,sead:2,react:3,replace:0". + pub intents: String, + /// The most aim switches one drone went through. + pub switches_max: u16, +} + +/// The last intent every smart drone of the run was seen carrying, as `name:count`. +fn intent_counts(seen: &HashMap) -> String { + let mut counts = std::collections::BTreeMap::new(); + for name in seen.values() { + *counts.entry(*name).or_insert(0usize) += 1; + } + ["plan", "sead", "react", "replace"].iter().map(|k| format!("{k}:{}", counts.get(*k).copied().unwrap_or(0))).collect::>().join(",") } fn finite(v: DVec3) -> bool { @@ -160,6 +181,8 @@ pub fn run(sc: &Scenario) -> Report { let mut ended = vec![false; sc.raids.len()]; // truck id -> (last position, ticks standing while busy) let mut standing: HashMap = HashMap::new(); + // drone id -> the last structured intent it carried (#127). + let mut intents_seen: HashMap = HashMap::new(); let mut out = Out::default(); for now in 1..=sc.ticks { // Raids come in order: one is ordered once the previous one has been sent. @@ -192,6 +215,18 @@ pub fn run(sc: &Scenario) -> Report { pending = wanted.into_iter().map(|(x, z)| (x, z, sc.terrain.height(x, z))).collect(); } r.ticks_run = now; + r.retargets_max = r.retargets_max.max(sim.retargets_len()); + r.switches_max = sim.drones.iter().chain(sim.queue.iter().map(|(_, d)| d)).map(|d| d.aim_switches()).max().unwrap_or(0).max(r.switches_max); + for d in sim.drones.iter().chain(sim.queue.iter().map(|(_, d)| d)) { + if d.alive() && d.smart() { + intents_seen.insert(d.id, d.intent().name()); + } + } + // The air section a save would take, sampled when the sky is fullest (writing it every tick + // would distort the timings the bench measures). + if sim.drones.len() > r.air_bytes { + r.air_bytes = sim.air_text().map_or(0, |t| t.len()); + } // NaN check on everything with a position. let bad = sim.drones.iter().filter(|d| !finite(d.pos) || !finite(d.vel)).count() + sim.missiles.iter().filter(|m| !finite(m.pos)).count() @@ -225,15 +260,17 @@ pub fn run(sc: &Scenario) -> Report { } } r.launched = sim.launched; + r.sams_lost = sim.sams.iter().filter(|s| !s.alive()).count() as u32; r.kills = sim.kills; r.hits = sim.hits; r.fired = sim.sams.iter().map(|s| s.fired).sum(); r.raids_ordered = ordered.iter().filter(|o| **o).count(); - r.world = ground.world.text(); + r.world = world_view::text(&ground.world); r.crashes = sim.crashes; r.failures = sim.tally.text(); r.raids_ended = ended.iter().filter(|o| **o).count(); r.all_ended = r.raids_ended == sc.raids.len(); + r.intents = intent_counts(&intents_seen); r.tick_total = times.iter().sum(); times.sort(); if !times.is_empty() { diff --git a/airdef-mod/examples/sim_run/main.rs b/airdef-mod/examples/sim_run/main.rs index 10a583b..3e401cb 100644 --- a/airdef-mod/examples/sim_run/main.rs +++ b/airdef-mod/examples/sim_run/main.rs @@ -97,7 +97,7 @@ fn parse_layout(path: &str) -> Vec { units } -fn main() { +pub fn main() { let args: Vec = std::env::args().skip(1).collect(); let mut name = "big-smart".to_string(); let mut i = 0; diff --git a/airdef-mod/generated-manifest.txt b/airdef-mod/generated-manifest.txt index c89f95e..3fe489c 100644 --- a/airdef-mod/generated-manifest.txt +++ b/airdef-mod/generated-manifest.txt @@ -63,11 +63,14 @@ c3b0b6f1d9b7da434c95f2794b97b9e2c05c62ccbce9f0884df071e4ec28a468 assets/airdef/ 3ecae997dc4fe1939848fc60347979c02075396a164b31dec3995a2e907a8447 assets/airdef/blockstates/patriot_trailer.json 31fecc6476129990a7d368f6d7cdb954b13e777b28d1396a68168770024112ce assets/airdef/blockstates/patriot_trailer_burnt.json 183d0b2ae9c24c94c3d83f2819e1167b5e2d134cc7889e0e3bda12aa784c7baa assets/airdef/blockstates/patriot_truck.json +421b5be0a4c80e892850f4a77d5ebb386e04153201be63241c7edc11659d03de assets/airdef/blockstates/radar_mast.json +fcc6bbe526baf5551923e2a69cdf09f506cdfd96669e1fffbea643401dd2a1de assets/airdef/blockstates/radar_mast_burnt.json 37714e19e555cc50a9f195e1a8845a28c9985c27ed38b577c0813f4629b66fcb assets/airdef/blockstates/radar_panel.json 95fe14b9d3e30597f5505aa93f31c3357cebea6117596bd9f5a5bc840ab22415 assets/airdef/blockstates/radar_trailer.json a95a1b8731d6bedda7d8b352ff3c448ea8be691b0ead01f14acab39234590c45 assets/airdef/blockstates/radar_truck.json 332e6f39c40063ca690064e13a1a256ef215fc2bb96985b842d9dda58abee2eb assets/airdef/blockstates/radar_truck_burnt.json 67b93302bea867248b6fa0109a4b5af2065e5c857d4a5c7e088bc9849fe624de assets/airdef/blockstates/reentry.json +ac9ff6b121adfc64ff60110a4e49bd5ec8e6b5ad57d45ee348b4ef1af7361264 assets/airdef/blockstates/rm48u.json 7f3e952f5de83880549012945827f8469883355b0a3dd966e7f03c44aee532ab assets/airdef/blockstates/s400_chassis.json 411d0a0ac102b21016bf48d30482e073a7e8054c2775d8fd5a7c91364a2cb1f7 assets/airdef/blockstates/s400_chassis_burnt.json 3ffc2edef1362f318d6c087a20cd730feda6f35f7381942cfc8012f06f4dc174 assets/airdef/blockstates/s400_tubes.json @@ -87,6 +90,8 @@ e766e18158d00f0480f6dfe32dfe142890b8d452bc493b7474fda0984cc2dd4c assets/airdef/ ff125150e206ada5a7fb47394e809039d338c22a843207a32352e9ee59e278a3 assets/airdef/blockstates/tor_chassis_burnt.json 04a5fb931f419086995791c1defd9486b36b3c725581bf5a10191a2c71c69984 assets/airdef/blockstates/tor_turret.json fdcbe36a0f4968005e8c6588f2fa5d8196c55da3183cf89760ea221a5c4cb431 assets/airdef/blockstates/tor_turret_burnt.json +923340f2233c363a880fffeb40dba04038f26ff42b072f28418e0a6ab757aac5 assets/airdef/blockstates/tpk_cap.json +f3e430750f5cb60f44c2a97687d7cc1ac58232b27274a82758c23861d704662a assets/airdef/blockstates/tpk_cap_burnt.json cf7c26abeb1d8f7d84106bd83b84bf541a3d11369b7745a77802b7e51dcdf447 assets/airdef/blockstates/tracer.json 8b547ccbc890fa53b613cdb2cf55ceaa5833348479b0863651c6df01a9f778b3 assets/airdef/blockstates/tzm_truck.json 11fca31a88e658c819954fbb86b53d7709b66b971af8747a5edda24be34e5dbb assets/airdef/blockstates/tzm_truck_burnt.json @@ -131,100 +136,105 @@ b4d98a938486d49db28c6c7a608ca3b6ab0c9c3eac266ecf12c7dd931cc28f93 assets/airdef/ 489799dcb5b441e9d4be8bfb3d73a6ebd5fdf2b5ee614b62f38b85bb2fbfad75 assets/airdef/items/launch_zircon.json 6b6da5887aa8d9bc987b2c181c243bc2afdd5846a869818f699b59204e559929 assets/airdef/items/mover.json 177f7535e4c52ed975d6f737c2b2acb7741b7a928955c8899aced85211443bbf assets/airdef/items/remover.json -9c54b89d2840841494bdfb2161c038385b23e41a42a87a24e95d485a1a541a82 assets/airdef/lang/en_us.json -9c54b89d2840841494bdfb2161c038385b23e41a42a87a24e95d485a1a541a82 assets/airdef/lang/ru_ru.json +cede33ba58f61891a8afe42e1f4311cb293ca93f4d7eb3a57636caff17317a4d assets/airdef/lang/en_us.json +cede33ba58f61891a8afe42e1f4311cb293ca93f4d7eb3a57636caff17317a4d assets/airdef/lang/ru_ru.json b9b5b5bec33a77e944552c225ee2f526ab9820fa8fca4b3ccf311f2a325925ee assets/airdef/models/block/amraam_missile.json c54e6b24ab69e9d9a2dbbbc63fe2ac60fa37735bf644500ddd5c3de0b91908cf assets/airdef/models/block/barge.json a4198ff647de5866919f9b82a3d53e6486fc56cc9f175f7907951d063a6f4526 assets/airdef/models/block/beam.json -050bc585d82f48b39fee175ba795fafb642fc2658c932901549d8907e5d63401 assets/airdef/models/block/buk_chassis.json -384ef97ea938470c82e68ab9659b9cb67cb69494fe3b9eb14357107bc6ad416b assets/airdef/models/block/buk_chassis_burnt.json -e2b031f4edbfd8395ff04311bca5bb3c4a2fd5cd7022d1070c8c7f67a6aa7177 assets/airdef/models/block/buk_rails.json -a76b58dd0c282353b9101beb48774d5619369303e0665fc821458a47cd3d0402 assets/airdef/models/block/buk_rails_burnt.json -bdbb639071f3ff18fd624f3179aeb5dcd1216828b599e976617c13e5c77d115b assets/airdef/models/block/buk_turret.json -7f1248fe849972d51a37c4162d92991b5567487b5415e2e9ea2cf43644bb500b assets/airdef/models/block/buk_turret_burnt.json +87c5b37677b74fc0eb83db9d946f63a4b05651734e5a6c00f49c2bd30acaade2 assets/airdef/models/block/buk_chassis.json +e06845ed1a8c3b6bc737ae0415c8170c24fbbf30ee6c03d92e69558047647018 assets/airdef/models/block/buk_chassis_burnt.json +e51314abb51660f2e06d896084802ae913c3a233a05f5531b9d2fb83f1066f7c assets/airdef/models/block/buk_rails.json +b305b7fe737f9a303bd60dacff4e71468835e63b09fc6af5e63ecb248e390572 assets/airdef/models/block/buk_rails_burnt.json +3a0f03c100dd8811349798e3baa5cdab53be12381dd1faabbe5375cfdb1fe332 assets/airdef/models/block/buk_turret.json +d64884c030716bc212bbfa04b2308714f8f0a116a14e0249847859a0cc96fa3d assets/airdef/models/block/buk_turret_burnt.json 684edfac71d15f328b92aee2f2cc508532f6654d5a664ac78c13b6868c3307ba assets/airdef/models/block/debris.json -aef93931af7ed7bf71eca89070adaf52af9e0d8894d3d4e36c926b219e196e02 assets/airdef/models/block/depot.json -107aec61d1dcbbfc6ec9203cf8590c23cde3cb22e8debc121b6ea30d675e6401 assets/airdef/models/block/depot_burnt.json -89ea90f53a06814d71cec4d8e89a43d7dbbc4536a8104d099b743332cabafedd assets/airdef/models/block/ew_truck.json -b7cd8f04aaf8a6c5b7021a59dc7f6c60e29b1159237ba666d7eeefb91254f348 assets/airdef/models/block/ew_truck_burnt.json +de1e494d4b9543ffb72f6b351830d2821ba3e344baaf0f7d365f771fa9d5ba24 assets/airdef/models/block/depot.json +b0f94e748ecc44fffa6711b1affa8e2a4ccf95e6fb101c8577e0f118f31133c2 assets/airdef/models/block/depot_burnt.json +6e99fc5296a9251bbe04379da7122faee34df156d77f4888daa27b9f9d54c6e4 assets/airdef/models/block/ew_truck.json +4e270f0092d3c10acaaec5f3824f43c0e16c1191b837187af10aa3b0b5791a9f assets/airdef/models/block/ew_truck_burnt.json c00df220e46a265c5a33e68df1cf407dc1fd25bb70978b6b8d850d7826044f3c assets/airdef/models/block/fireball.json fd5ca8c85d054580e6f95cb88bfed6216c9b41ddab9efec2eb4a642ad408c17c assets/airdef/models/block/flame.json 3962ad6a9bfd88ed4638dd72d27ddbd0933583e7f0d40b808ddabfd6e3a35399 assets/airdef/models/block/flare.json 6bf4a388b9a38a5359053be38cf09f1c86d0ea47d61e5de0016b2b1c7f7dbb4a assets/airdef/models/block/flash.json 9fa54aaabfc0dd1b6af4fdd1fd4ac9a4248c4837801a7d74e441627f565a18fa assets/airdef/models/block/gepard_dish.json -993b426346250e248abea70a461a7e9343d264585997adee252d8f803d635acd assets/airdef/models/block/gepard_guns.json -9f5b572110c02fbae42a1aff8db716094d198b209d19dfc2e6a72430378fc179 assets/airdef/models/block/gepard_guns_burnt.json -288029fc93a84ee5d68605d061bde9fdb31b759a4a17766bb8df008beb9c2064 assets/airdef/models/block/gepard_hull.json -743dba3635ae9683e744c1691315a104e48e10011cfbe6e74565eecd6ce7dfcb assets/airdef/models/block/gepard_hull_burnt.json -38ae7a16ba13e5ac0cff7f8f1856e24b512f4e68bff7b71297b3d2838333fc2a assets/airdef/models/block/gepard_turret.json -cf464948fd36c74cf6f49b10f4f2e0e4b9e52b9d8324c27302e8e1c2ae8ae73f assets/airdef/models/block/gepard_turret_burnt.json +c8146e2999b4b94633abd1fbafdb39fca8896e9df92aa37c78544ee594337218 assets/airdef/models/block/gepard_guns.json +e8a9c326e2630d7484e1a11ce8a30d2a0005dc4c847c62b5e425904d6ac4fcf1 assets/airdef/models/block/gepard_guns_burnt.json +48a3496b78b37dda0a6eff1ed610458824949dd11c56626dffaf6158f8c1c7ae assets/airdef/models/block/gepard_hull.json +eec91700830aaea1c7d80a4af3ca44b99756bba18ca634e0107004f4ed59ae06 assets/airdef/models/block/gepard_hull_burnt.json +e666f027e7cd54c6c9596d896931db7c51a5b74564e7038f9bceb6824c6c29dc assets/airdef/models/block/gepard_turret.json +da2509d47a048c59c81430a83fa71ac0a0d312a2ec617ed1d3175b5ceb75ccd1 assets/airdef/models/block/gepard_turret_burnt.json d43f9cda780b1821a6e778555a1be5bd112a1d9655ff96d35acebec61cf11e0b assets/airdef/models/block/geran3.json 83cf0a42e59f6aee1039925f849398960334f2545558409e7ccd7173ac841d73 assets/airdef/models/block/geran3_flame.json ac93e872743844465f0688162dddd7433352e8710612dd391a1e92b7f36e6b0d assets/airdef/models/block/gerbera.json -8c273c2e02dd1884012f5597a6ad2022a0cfcf56c9c5b23043729d3c34635783 assets/airdef/models/block/irist_chassis.json -417c68e386d715da987c2d2e9fe86d25dcf0d923546b602d5f01c1eba1d25543 assets/airdef/models/block/irist_chassis_burnt.json +3ed12c12f9c1490ef08585cd4a55632cd94528e9bbde1e993310f1e9ca57c534 assets/airdef/models/block/irist_chassis.json +122e7dbb3cba0b8e249c8a06251d3ef83dcbe1880d540b0d4c2430910314f34d assets/airdef/models/block/irist_chassis_burnt.json db7b914b2d027b8ebde485b940951244e4bc4cb614eb2eefc49ccf0b26c98bdd assets/airdef/models/block/irist_missile.json -89453602195bc437ecb70885ad4475bc04ee300eff377682321bc3cd1113e9d7 assets/airdef/models/block/irist_tubes.json -c8cb7d80202b72070e955015d4abeffb33586c9fa94a3ca29311ae68cfd7ca9a assets/airdef/models/block/irist_tubes_burnt.json +e6cb3f578b875d3683d2a027f1ac34226b56c4db410fecc6479e2683485b8c3e assets/airdef/models/block/irist_tubes.json +449284669a58d71ae2de7e68cd7cf18ae21fec70ceb3124cd24fb4a09fc49909 assets/airdef/models/block/irist_tubes_burnt.json be87037b84cd45b2c53d7fde6653f51769e782eea1b8586383afcfababf19ab6 assets/airdef/models/block/iskander.json 98d488ca797fb3d0193957b8a93a82f48376a89f8e091032e6365d1845016a90 assets/airdef/models/block/kalibr.json -be9eb1389e169a76732f16e604499260b147b524b4c929d2a4e77b580a032fb7 assets/airdef/models/block/kh101.json +20deb68dbba6eb64b7c51685497110ef7d1d3bf0aa7e7167c558e0a8e842e759 assets/airdef/models/block/kh101.json ea7c9af888f1285502b2ca51114f226573311be6fb90b8d449b9f75cfbb6b885 assets/airdef/models/block/kh22.json 7b73015e33d85d4cef31660ef874ba8dcd6e07ae3a95416d0ead1d325bd0cb6c assets/airdef/models/block/kinzhal.json -911e55de28058e1723a61d15de80676789e57ab15bfc09fd75757cc85095fbcb assets/airdef/models/block/lancet.json +69135e90a1a7fb21d9f47b1854c5a3648e296136c2375acd9db5a48b0ad40887 assets/airdef/models/block/lancet.json 15c9884ce51311cf4b9b9d106e142e33d72c5ba18dc9236c5b51da01232f8e44 assets/airdef/models/block/launch_ramp.json 159fa5e3b6a096a1872dec2a550bfa30575d57519654a860607e20dab8f35f53 assets/airdef/models/block/launcher_truck.json 2069198ba24a932816f504fb52f53dc4bc740cacaf45637698a17d9175da851c assets/airdef/models/block/m48n6.json 61968f4d63adb6dfebd5770ce5225f4cf5610a7d7ef863c4665875e82abb4f25 assets/airdef/models/block/m57e6.json 238ed59a5dfa261272c0a4dabbda3ee62d2c1c7b9829cced644bf8b2322d4bfc assets/airdef/models/block/m77n6.json -e8786fdbf6feb21f4819015c7faaf87983f4a7ed88c6f7917bd49d1b14eb9140 assets/airdef/models/block/nasams_launcher.json -574e069129ff20e6137b469740cb279e16e70beeafd1e933c22cd7dca583d74d assets/airdef/models/block/nasams_launcher_burnt.json -56a0a41e54b5056689525909179651dd5ec0fe551c06bd2bd2b1edb63c369aec assets/airdef/models/block/nasams_trailer.json -79b26bc489f644afbff7a9f616481b81034fb13a0590e88ca1753601eca4f3ea assets/airdef/models/block/nasams_trailer_burnt.json +266565d9ed8dc836c46028c8b8bdc83c9725db587552bf8848cd9059f3917ed4 assets/airdef/models/block/nasams_launcher.json +94c8beaf3226c86d5ad018a31846286b451eaa96073e07be4465c9de3583ef61 assets/airdef/models/block/nasams_launcher_burnt.json +99d3a530ca47536443db0e2c8a8f9236d68f67b6c78a5313c44dd80fb6edbde0 assets/airdef/models/block/nasams_trailer.json +ab0d875ad069ccd241f3c75fd32ecbe3507cfe740aa072a3a75955c489629ec6 assets/airdef/models/block/nasams_trailer_burnt.json ce9f0dc61bd07cfcb3be5cadf12df0d2ef1b3167aa2260bc4b42414dc3825b58 assets/airdef/models/block/p1sun.json b5346ab109ffb8c259592574b091a47bd396de34b0fe96e684b672316cee6b86 assets/airdef/models/block/p1sun_launcher.json 4457a527977ad5c7d381991ca1c9d304cac0b7074f632a04328e836bfd83f784 assets/airdef/models/block/p1sun_launcher_burnt.json 2e11e64942baaf057ccc60f42dc629f7519eb3892198c1906c22fcfa95584e7b assets/airdef/models/block/pac3.json ba90d89595c3c5d6d5a30f1041540f50504676bc64df5cf34038f8682307bd3e assets/airdef/models/block/pantsir_dish.json -622c6f537f6adb99b1a536e38eb0a5d30c05c62f671997365ff0b2e9fa9e6562 assets/airdef/models/block/pantsir_pack.json -5413c0a546ee6b82dcf3e782d6cdab4e7080642b71bbb08a4b800a9105f39216 assets/airdef/models/block/pantsir_pack_burnt.json -52aacf44022a6826bd88fb161bce81966033de89f67b9d505c1b649bcf294eb0 assets/airdef/models/block/pantsir_truck.json -53e0a5c2bec25e3a52eb52bb098c9aa186f737fff7d6c5b2ccf927c313416e1e assets/airdef/models/block/pantsir_truck_burnt.json -84857ebb50b8e98796e3907d8ce4eeb8427c29de9f81aec7a8bd8d9ba4ca516e assets/airdef/models/block/pantsir_turret.json -fc3555a87f290805ad723607f034d90645799cd8d9ef3ba42fedee2a50248059 assets/airdef/models/block/pantsir_turret_burnt.json +9a461291c90e18c50f3a82afe7f17bc8a0a299b9f2b925c244580087fc3b5fa5 assets/airdef/models/block/pantsir_pack.json +79af2971ac88a93bbc42c0e193f61e585f6ae5330153f4b40b7720c5ce8e4cd0 assets/airdef/models/block/pantsir_pack_burnt.json +ef73f453ea0abcd97121c845207efcf2438d47991d49bac1cae66acc9a416419 assets/airdef/models/block/pantsir_truck.json +1d3a00601801268636165e8fea79f9c9d141e1434f0268e2f65ef67ecddd4447 assets/airdef/models/block/pantsir_truck_burnt.json +d01efdcfafa92243e745aea7870269c07a0224c9010010e2dbed720ced1662c5 assets/airdef/models/block/pantsir_turret.json +0036037578cf2df9f565acd8ac3553af9038ebafb5b9a4e6d8fd0d225b39149c assets/airdef/models/block/pantsir_turret_burnt.json 33884253bc3ab463d15a7fe952187b1f9fb3b78632e825e1fabc85ca5335b543 assets/airdef/models/block/patriot_launcher.json 3fed073655020b72dd515dca7eb362f642af50251b473012429a5ca946426f8d assets/airdef/models/block/patriot_launcher_burnt.json 2a8ca10c933e236270810ce9de40c83dfd8016b0f0d32413433e3a5f8622329f assets/airdef/models/block/patriot_radar.json -eb9a8af98374220657fa722a281bc18f6d884d51c5869975d43eeabcb171677b assets/airdef/models/block/patriot_trailer.json -428fbe50bea9dbca55bc4933b15ccecf57164dc915e661ec9ae610151810a50a assets/airdef/models/block/patriot_trailer_burnt.json -4c3dcfe38527de677cc1d56f6fdf17980604b93ce24f4337b3f825a2f446620b assets/airdef/models/block/patriot_truck.json +548bc63e46d96e5110598e39269851c48f750164981a433f75971b0229c268df assets/airdef/models/block/patriot_trailer.json +190726b64bef4baadb0bc2a00147069685ef148100c8935402e09c3bef66e65b assets/airdef/models/block/patriot_trailer_burnt.json +6f027b020e4066f673c376e289cc627846a084e5733d62744fe78ab23013fb08 assets/airdef/models/block/patriot_truck.json +8070d24b511ac733d73f5961762e6f5995379814f33f54c56c1a45d1dd7ff28a assets/airdef/models/block/radar_mast.json +7343f6085138f974d581706cc135e582040a46069c215e95ba14ad31dc1a8d63 assets/airdef/models/block/radar_mast_burnt.json e5733ece9b976998c9c6a6c968b17cada868b62d9e2a7bc67e1272e333e35399 assets/airdef/models/block/radar_panel.json b0ae344fbad3b982e7821cd1b26f9922e2a5713b38107521ddc5a5b2abd9e39c assets/airdef/models/block/radar_trailer.json -088f42bb17f4d6099c16adf6cc0426b6d04ff9a43abdf125476a3571e924fcbd assets/airdef/models/block/radar_truck.json -4cd60dfb1e598efbd127894b0ad1b1737569dfc0fc5c9b193034f9125daa9c15 assets/airdef/models/block/radar_truck_burnt.json +5545094620c27fae6b3d97a71f3d68ee8e0b49804a1dcd61b79ca847277e3b61 assets/airdef/models/block/radar_truck.json +5c756abfb412b31076cd84d8735ad4e41ca26c8d5896e3795aaddb976aefbd1c assets/airdef/models/block/radar_truck_burnt.json f102765390762d45954b26405b6acdde845df7f0a319bac53d25508e768957b5 assets/airdef/models/block/reentry.json -0a27047ed66a18b513c5113e4b4ba1d8667ac75d36f661e58415a9d6870c0bc9 assets/airdef/models/block/s400_chassis.json -b86ccc0a31dc63239b598542070173ebbc6d354b3d8dec880c62ea192d6ea9f7 assets/airdef/models/block/s400_chassis_burnt.json -0107a6689048c7267ea1c68713593026fa495cb33443a6b1aab815a17602fe81 assets/airdef/models/block/s400_tubes.json -6e4a35843d29bba30b9969af7ed9a60ad05ea4e4b9fde0507ea6ba6dcb9d7dd7 assets/airdef/models/block/s400_tubes_burnt.json +971c11e58eafa17ad798f6d27d91a9ee0531f33692eb657d31f2d83e8cee7e39 assets/airdef/models/block/rm48u.json +a6bb637011c176edae474ae7b5d6e3b5af0d2a448845e3f95b026f789e21560f assets/airdef/models/block/s400_chassis.json +192076b361a57ee26a1dd449717f30f50b612ee6c88d4dc1fb9dc6aef3f3b829 assets/airdef/models/block/s400_chassis_burnt.json +44bf27184842d0e3c86bed035a7090add0a79f6b5bdea7a37e6ddaaae728d39a assets/airdef/models/block/s400_tubes.json +973e02b2058c0b3d59c156f2ac94fbba4ac90a97736e8a4cc9be177b5e0f19a9 assets/airdef/models/block/s400_tubes_burnt.json 088f42bb17f4d6099c16adf6cc0426b6d04ff9a43abdf125476a3571e924fcbd assets/airdef/models/block/s500_chassis.json 3e35ed19ea22c8309ac7c0ccc9c12e31b2387de97c6b72ba519bae34f681c58c assets/airdef/models/block/s500_chassis_burnt.json -6ce00881e2a5107874e82e94a243fcd95c3eb6e9a24e8839c11227c8e330485f assets/airdef/models/block/s500_tubes.json -88109407ec749d6f45a4a38a9a43fd1594ac6190d7f08433b07da572c040458d assets/airdef/models/block/s500_tubes_burnt.json -536928e90a907c50b078a488bad49061f79aafe12479f58a2b2ce79aaad5adda assets/airdef/models/block/searchlight.json -0e3cbae5029377886325aedc43705d9975cb1dc889a37613b92a97bdf36b4db7 assets/airdef/models/block/searchlight_burnt.json -2d790e8845598e8cf7ae577492632ef52e075d590011e66fa8ab686096cda313 assets/airdef/models/block/searchlight_head.json -f0f9ec6cefc0750e4d6cb330c21df8c2bcf02b1b7408f90f40ebb35a572a13cb assets/airdef/models/block/searchlight_head_burnt.json -b2e28d73f50b238f6bc7d6dae560695e70ae8b843f6853f9e468bf8d2b371e6c assets/airdef/models/block/shahed.json +8c6bc46647f3df32febd96fccdba894e4c986b717b006e3c3e5607a73f0f252f assets/airdef/models/block/s500_tubes.json +135d34c4059ea7aca794cce201ef27c3c17cdb21ea27205634cea95bc8d16515 assets/airdef/models/block/s500_tubes_burnt.json +3d61559d5bde298d6dc8987122c68edc3b02bd0d1afed1d9458bd7c3315fb0fe assets/airdef/models/block/searchlight.json +6391c155f45656f63ac579bd1ff0d3512c54830a03561b8dd04e5e63ed751cf4 assets/airdef/models/block/searchlight_burnt.json +357165a1a1ad382257e68768bf1b4158294e0e080eab9a9570ed6bd824e7f3aa assets/airdef/models/block/searchlight_head.json +1ca6eff892a0101bcd950323d2f9c68cc81d6c714086a44555ee49441797fbc3 assets/airdef/models/block/searchlight_head_burnt.json +60c44090d52fd08f230b3f37b58e888824e680b5411dd4c3c6220da6ff3d15d8 assets/airdef/models/block/shahed.json d5b9ed76b1744d0c93a809b6a4c16bc3dfa7ff4784e3cb9968aa36b12fc30484 assets/airdef/models/block/shahed_prop.json 465e6aa2a8ac3c563b1e546fc844a0520fc89f3ce528d49f193aa6c21e19308f assets/airdef/models/block/target.json -69c5c103c228abe6162f2e1252139689b768ff8dd3f835f8f79ce7d324c3f124 assets/airdef/models/block/tor_chassis.json -81e66314bfcd4786950ab4c23c6ede79ff9b30687b44f23e2b7b9be233fe380f assets/airdef/models/block/tor_chassis_burnt.json -b2d2f856a4004853cb799785c0a586d605c623759d5a099dc70e19ba6a67b100 assets/airdef/models/block/tor_turret.json -e95be4a89de1384038df47861705b934b0091f61f7b135c9ea6933fe2197f31f assets/airdef/models/block/tor_turret_burnt.json +8f4d29f5cff68b74880d2120a0f183a35018d36576330bb33de3789cf769683a assets/airdef/models/block/tor_chassis.json +cacaa7265489f1083f7de351515a7d08eac5d2b072cf58b32ee51840473ab44e assets/airdef/models/block/tor_chassis_burnt.json +d130179a7717f1bff9f045a3e0b93788eff17737c7c1c9c3b5b287d811eb6fad assets/airdef/models/block/tor_turret.json +a98d2a545b4bdea5cff1c5826d55dc8c3fd341ecebeb8b00ee2e1e1b0739b2cc assets/airdef/models/block/tor_turret_burnt.json +a6785198c98f5b2f20acc26ddeca3b5432e6b521b68541ea65ca2592c3cae4a1 assets/airdef/models/block/tpk_cap.json +0391ce9eeac9aa0cfcc05b31b0bd5594e10a5df2dc46c355b0b95a8ea8234ff5 assets/airdef/models/block/tpk_cap_burnt.json b2380510f56dc60e914919cb6e20b82e3613a9d62b08a1c81740f960569c3425 assets/airdef/models/block/tracer.json -447d237b290255f01c7ce51047df3de1b9261cb75cfeb83ab190f6abe914747d assets/airdef/models/block/tzm_truck.json -1954ece9e339d89a6ce63b78d255637011b3661572fdfe051ea4814a891746ce assets/airdef/models/block/tzm_truck_burnt.json +753c46e821b4fce9fd9240de2ec627a27688740a4c31c9eff24593034ffa33e7 assets/airdef/models/block/tzm_truck.json +fa29c0224a5a7ad2e3252cde099b17c6c8241ede7cf0e52766734672843a6432 assets/airdef/models/block/tzm_truck_burnt.json b85d09a73a1c251bbc76945aa8f99b5e7e606ed41e1d120f811a435f441b1e96 assets/airdef/models/block/zircon.json 8360c341dbe0aac6c485bf0d2c9ace49378173f1749f5b52793a79b98697685b assets/airdef/models/item/cam_geran3.json 5b35d42715fa9c4afe8d0bccf5060380a8b5216edc9509ba0d859e8e953232d7 assets/airdef/models/item/cam_gerbera.json @@ -346,6 +356,10 @@ dd8c9056df254fe788a6848c96af4e56c5951f08717b25ebaa8815d1fb823751 assets/airdef/ 311932fa43f0b513342e8f3746208bd845b936f883f6f5c8d453fd4ef6d315dc assets/airdef/textures/block/nato_tan.png 6a1289cfba0b17732cedfadceb6522b0b9f847f754dd56975716b3e2e21e777a assets/airdef/textures/block/nose_dark.png a8c0d707e86dedb7591b7e89f07ffd2df85ab0763e903a4c4c9ea17c97c4baee assets/airdef/textures/block/nozzle.png +85c25585f0701d719a4750f77c1eb1d63fcb469741caa27aeb5b7a04fdff7279 assets/airdef/textures/block/pilot_nato.png +e2a967933fe524abf37135e61b1aede6443a8a44d7cbe0438a0c4b4e49d6721f assets/airdef/textures/block/pilot_nato_b.png +c912a25df82af9bdfb864e7e7a018accdd048fd729a672fa4b088c663555beaf assets/airdef/textures/block/pilot_olive.png +e2a967933fe524abf37135e61b1aede6443a8a44d7cbe0438a0c4b4e49d6721f assets/airdef/textures/block/pilot_olive_b.png 3f908df37b1456884d840e346f2da60ccce5b61bd226317de9ca24430bd59a8c assets/airdef/textures/block/prop.png 98442b32c81dd7523c8d38bc6ae9810d8e0295474c814533b7fbde33f4910e3b assets/airdef/textures/block/radar_big.png 5dff7780ae2f934bc05bb58742f1965b7cf06ad28459d6dd63b574a0a327f949 assets/airdef/textures/block/radar_face.png @@ -502,11 +516,14 @@ cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/ho cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/patriot_trailer.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/patriot_trailer_burnt.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/patriot_truck.json +cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/radar_mast.json +cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/radar_mast_burnt.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/radar_panel.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/radar_trailer.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/radar_truck.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/radar_truck_burnt.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/reentry.json +cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/rm48u.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/s400_chassis.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/s400_chassis_burnt.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/s400_tubes.json @@ -526,6 +543,8 @@ cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/ho cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tor_chassis_burnt.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tor_turret.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tor_turret_burnt.json +cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tpk_cap.json +cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tpk_cap_burnt.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tracer.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tzm_truck.json cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/horizon/block/tzm_truck_burnt.json @@ -570,3 +589,4 @@ cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/ho 2a3912a7c9d5bb8025b1ac16a1a1d6b5eff0360bb3184e810d319e498255bf97 data/airdef/horizon/item/launch_zircon.json 2a3912a7c9d5bb8025b1ac16a1a1d6b5eff0360bb3184e810d319e498255bf97 data/airdef/horizon/item/mover.json 2a3912a7c9d5bb8025b1ac16a1a1d6b5eff0360bb3184e810d319e498255bf97 data/airdef/horizon/item/remover.json +5e0c0090b5aaded5b86b245cb30bcd0cbaa82f632a362d490d79bcf2794e25fd src/sim/generated_bounds.rs diff --git a/airdef-mod/source-assets/README.md b/airdef-mod/source-assets/README.md new file mode 100644 index 0000000..a346adc --- /dev/null +++ b/airdef-mod/source-assets/README.md @@ -0,0 +1,15 @@ +# Authored source assets + +Place artist-authored overrides under `assets/airdef/` in this directory, preserving the runtime-relative path. +For example, an authored icon at `source-assets/assets/airdef/textures/item/deploy_gepard.png` replaces the +procedural icon at the generated runtime path while the source copy remains untouched. The asset generator +copies this tree into its temporary staging tree, validates the merged runtime assets, then installs them using +the generated manifest. Do not edit generated files under `airdef-mod/assets/` directly; an untracked or +hand-edited output is treated as a conflict and left untouched. + +This directory is intentionally kept separate from runtime and generated output. The initial art pilots are +S-400 chassis/canisters, Gepard hull/turret and Shahed, including burnt variants for the ground vehicles. +The JSON is editable directly. `python tools/pilot_models.py` is an optional authoring helper that overwrites +only those named pilot JSONs; the normal regeneration never invokes it. After editing, run +`python tools/gen_models.py` to validate and install the authored models. `generated_bounds.rs` is derived +from the final staged geometry, including overrides and element rotation, for chassis navigation. diff --git a/airdef-mod/source-assets/assets/airdef/models/block/buk_chassis.json b/airdef-mod/source-assets/assets/airdef/models/block/buk_chassis.json new file mode 100644 index 0000000..695c8c2 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/buk_chassis.json @@ -0,0 +1,3627 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "dark_metal": "airdef:block/dark_metal", + "track": "airdef:block/track", + "hub": "airdef:block/hub", + "metal": "airdef:block/metal", + "bare_nozzle": "airdef:block/bare_nozzle", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -9.0, + 4.0, + -13.0 + ], + "to": [ + 25.0, + 13.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -9.0, + 4.0, + -16.0 + ], + "to": [ + 25.0, + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -11.5 + ] + } + }, + { + "from": [ + -8.0, + 13.0, + -7.0 + ], + "to": [ + 24.0, + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -13.0, + 0.0, + -15.0 + ], + "to": [ + -9.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -12.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -13.5 + ], + "to": [ + -13.02, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -13.5 + ], + "to": [ + 29.08, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -12.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -6.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -0.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -0.75 + ], + "to": [ + -13.02, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -0.75 + ], + "to": [ + 29.08, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 5.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 4.5 + ], + "to": [ + 29.08, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 5.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 11.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 11.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 10.5 + ], + "to": [ + -13.02, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 10.5 + ], + "to": [ + 29.08, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 11.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 11.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 17.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 17.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 16.5 + ], + "to": [ + 29.08, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 17.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 17.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 23.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -7.0, + 15.0, + -10.0 + ], + "to": [ + 2.0, + 15.7, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 3.0, + 14.0, + 23.0 + ], + "to": [ + 20.0, + 14.5, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.5 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.5 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#bare_nozzle" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/buk_chassis_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/buk_chassis_burnt.json new file mode 100644 index 0000000..92ed4b1 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/buk_chassis_burnt.json @@ -0,0 +1,2743 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "track_b": "airdef:block/track_b", + "hub_b": "airdef:block/hub_b", + "metal_b": "airdef:block/metal_b", + "bare_nozzle_b": "airdef:block/bare_nozzle_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -9.0, + 4.0, + -13.0 + ], + "to": [ + 25.0, + 13.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -9.0, + 4.0, + -16.0 + ], + "to": [ + 25.0, + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -11.5 + ] + } + }, + { + "from": [ + -8.0, + 13.0, + -7.0 + ], + "to": [ + 24.0, + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -13.0, + 0.0, + -15.0 + ], + "to": [ + -9.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -12.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -13.5 + ], + "to": [ + 29.08, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -12.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -0.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 11.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 10.5 + ], + "to": [ + 29.08, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 11.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 17.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 16.5 + ], + "to": [ + 29.08, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 17.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -7.0, + 15.0, + -10.0 + ], + "to": [ + 2.0, + 15.7, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 3.0, + 14.0, + 23.0 + ], + "to": [ + 20.0, + 14.5, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#bare_nozzle_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#bare_nozzle_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.5 + ], + "texture": "#bare_nozzle_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.5 + ], + "texture": "#bare_nozzle_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#bare_nozzle_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#bare_nozzle_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/buk_rails.json b/airdef-mod/source-assets/assets/airdef/models/block/buk_rails.json new file mode 100644 index 0000000..4e95bdb --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/buk_rails.json @@ -0,0 +1,896 @@ +{ + "ambientocclusion": false, + "textures": { + "missile_ru": "airdef:block/missile_ru", + "booster": "airdef:block/booster", + "dark_metal": "airdef:block/dark_metal", + "pilot_olive": "airdef:block/pilot_olive", + "particle": "airdef:block/missile_ru" + }, + "elements": [ + { + "from": [ + -10.5, + 4.0, + -16.0 + ], + "to": [ + -7.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + -10.8, + 3.5, + 20.0 + ], + "to": [ + -7.2, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 4.0 + ], + "texture": "#booster" + }, + "south": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 4.0 + ], + "texture": "#booster" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "up": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 7.0 + ], + "texture": "#booster" + }, + "down": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 7.0 + ], + "texture": "#booster" + } + } + }, + { + "from": [ + -9.5, + 2.0, + -6.0 + ], + "to": [ + -8.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.5, + 4.0, + -16.0 + ], + "to": [ + -1.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + -4.8, + 3.5, + 20.0 + ], + "to": [ + -1.2, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 4.0 + ], + "texture": "#booster" + }, + "south": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 4.0 + ], + "texture": "#booster" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "up": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 7.0 + ], + "texture": "#booster" + }, + "down": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 7.0 + ], + "texture": "#booster" + } + } + }, + { + "from": [ + -3.5, + 2.0, + -6.0 + ], + "to": [ + -2.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 11.5, + 4.0, + -16.0 + ], + "to": [ + 14.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 11.2, + 3.5, + 20.0 + ], + "to": [ + 14.8, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster" + }, + "south": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "up": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster" + }, + "down": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster" + } + } + }, + { + "from": [ + 12.5, + 2.0, + -6.0 + ], + "to": [ + 13.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 17.5, + 4.0, + -16.0 + ], + "to": [ + 20.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru" + } + } + }, + { + "from": [ + 17.2, + 3.5, + 20.0 + ], + "to": [ + 20.8, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster" + }, + "south": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster" + }, + "up": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster" + }, + "down": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster" + } + } + }, + { + "from": [ + 18.5, + 2.0, + -6.0 + ], + "to": [ + 19.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -12.0, + 0.0, + -2.0 + ], + "to": [ + 28.0, + 2.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/buk_rails_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/buk_rails_burnt.json new file mode 100644 index 0000000..cdaf609 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/buk_rails_burnt.json @@ -0,0 +1,896 @@ +{ + "ambientocclusion": false, + "textures": { + "missile_ru_b": "airdef:block/missile_ru_b", + "booster_b": "airdef:block/booster_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", + "particle": "airdef:block/missile_ru_b" + }, + "elements": [ + { + "from": [ + -10.5, + 4.0, + -16.0 + ], + "to": [ + -7.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + } + } + }, + { + "from": [ + -10.8, + 3.5, + 20.0 + ], + "to": [ + -7.2, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 4.0 + ], + "texture": "#booster_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 4.0 + ], + "texture": "#booster_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 7.0 + ], + "texture": "#booster_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 7.0 + ], + "texture": "#booster_b" + } + } + }, + { + "from": [ + -9.5, + 2.0, + -6.0 + ], + "to": [ + -8.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.5, + 4.0, + -16.0 + ], + "to": [ + -1.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + } + } + }, + { + "from": [ + -4.8, + 3.5, + 20.0 + ], + "to": [ + -1.2, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 4.0 + ], + "texture": "#booster_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 4.0 + ], + "texture": "#booster_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 7.0 + ], + "texture": "#booster_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 7.0 + ], + "texture": "#booster_b" + } + } + }, + { + "from": [ + -3.5, + 2.0, + -6.0 + ], + "to": [ + -2.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 11.5, + 4.0, + -16.0 + ], + "to": [ + 14.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + } + } + }, + { + "from": [ + 11.2, + 3.5, + 20.0 + ], + "to": [ + 14.8, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster_b" + } + } + }, + { + "from": [ + 12.5, + 2.0, + -6.0 + ], + "to": [ + 13.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 17.5, + 4.0, + -16.0 + ], + "to": [ + 20.5, + 7.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#missile_ru_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#missile_ru_b" + } + } + }, + { + "from": [ + 17.2, + 3.5, + 20.0 + ], + "to": [ + 20.8, + 7.5, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 4.0 + ], + "texture": "#booster_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#booster_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 7.0 + ], + "texture": "#booster_b" + } + } + }, + { + "from": [ + 18.5, + 2.0, + -6.0 + ], + "to": [ + 19.5, + 4.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -12.0, + 0.0, + -2.0 + ], + "to": [ + 28.0, + 2.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/buk_turret.json b/airdef-mod/source-assets/assets/airdef/models/block/buk_turret.json new file mode 100644 index 0000000..3c2067e --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/buk_turret.json @@ -0,0 +1,420 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "radar_face": "airdef:block/radar_face", + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -4.0, + 0.0, + -4.0 + ], + "to": [ + 20.0, + 7.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 7.0, + 2.0 + ], + "to": [ + 18.0, + 10.0, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 1.0, + 2.0, + -10.0 + ], + "to": [ + 11.0, + 14.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 12.0 + ], + "texture": "#radar_face" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 12.0 + ], + "texture": "#radar_face" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 12.0 + ], + "texture": "#radar_face" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 12.0 + ], + "texture": "#radar_face" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 6.0 + ], + "texture": "#radar_face" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 6.0 + ], + "texture": "#radar_face" + } + } + }, + { + "from": [ + 0.0, + 1.0, + -5.0 + ], + "to": [ + 16.0, + 3.0, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 7.0, + 4.0 + ], + "to": [ + -2.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 18.0, + 7.0, + 4.0 + ], + "to": [ + 20.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/buk_turret_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/buk_turret_burnt.json new file mode 100644 index 0000000..49b5d0a --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/buk_turret_burnt.json @@ -0,0 +1,420 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "radar_face_b": "airdef:block/radar_face_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -4.0, + 0.0, + -4.0 + ], + "to": [ + 20.0, + 7.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.0, + 7.0, + 2.0 + ], + "to": [ + 18.0, + 10.0, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 1.0, + 2.0, + -10.0 + ], + "to": [ + 11.0, + 14.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 12.0 + ], + "texture": "#radar_face_b" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 12.0 + ], + "texture": "#radar_face_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 12.0 + ], + "texture": "#radar_face_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 12.0 + ], + "texture": "#radar_face_b" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 6.0 + ], + "texture": "#radar_face_b" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 6.0 + ], + "texture": "#radar_face_b" + } + } + }, + { + "from": [ + 0.0, + 1.0, + -5.0 + ], + "to": [ + 16.0, + 3.0, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.0, + 7.0, + 4.0 + ], + "to": [ + -2.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 18.0, + 7.0, + 4.0 + ], + "to": [ + 20.0, + 9.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/depot.json b/airdef-mod/source-assets/assets/airdef/models/block/depot.json new file mode 100644 index 0000000..36dc091 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/depot.json @@ -0,0 +1,625 @@ +{ + "ambientocclusion": false, + "textures": { + "concrete": "airdef:block/concrete", + "crate": "airdef:block/crate", + "dark_metal": "airdef:block/dark_metal", + "tube_cap": "airdef:block/tube_cap", + "tube": "airdef:block/tube", + "particle": "airdef:block/concrete" + }, + "elements": [ + { + "from": [ + -14.0, + 0.0, + -14.0 + ], + "to": [ + 30.0, + 2.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#concrete" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#concrete" + } + } + }, + { + "from": [ + -13.0, + 2.0, + -13.0 + ], + "to": [ + 1.0, + 12.0, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate" + } + } + }, + { + "from": [ + -13.0, + 2.0, + 3.0 + ], + "to": [ + 1.0, + 10.0, + 17.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate" + } + } + }, + { + "from": [ + 3.0, + 2.0, + -13.0 + ], + "to": [ + 17.0, + 10.0, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate" + } + } + }, + { + "from": [ + -14.0, + 2.0, + 27.0 + ], + "to": [ + 30.0, + 5.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -14.0, + 2.0, + -14.0 + ], + "to": [ + -12.0, + 5.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 6.0, + 2.0, + 6.0 + ], + "to": [ + 28.0, + 7.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 6.0, + 7.0, + 6.0 + ], + "to": [ + 28.0, + 12.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 6.0, + 12.0, + 6.0 + ], + "to": [ + 28.0, + 17.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/depot_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/depot_burnt.json new file mode 100644 index 0000000..42c6dc6 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/depot_burnt.json @@ -0,0 +1,625 @@ +{ + "ambientocclusion": false, + "textures": { + "concrete_b": "airdef:block/concrete_b", + "crate_b": "airdef:block/crate_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "tube_cap_b": "airdef:block/tube_cap_b", + "tube_b": "airdef:block/tube_b", + "particle": "airdef:block/concrete_b" + }, + "elements": [ + { + "from": [ + -14.0, + 0.0, + -14.0 + ], + "to": [ + 30.0, + 2.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#concrete_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#concrete_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#concrete_b" + } + } + }, + { + "from": [ + -13.0, + 2.0, + -13.0 + ], + "to": [ + 1.0, + 12.0, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate_b" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate_b" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#crate_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate_b" + } + } + }, + { + "from": [ + -13.0, + 2.0, + 3.0 + ], + "to": [ + 1.0, + 10.0, + 17.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate_b" + } + } + }, + { + "from": [ + 3.0, + 2.0, + -13.0 + ], + "to": [ + 17.0, + 10.0, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#crate_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#crate_b" + } + } + }, + { + "from": [ + -14.0, + 2.0, + 27.0 + ], + "to": [ + 30.0, + 5.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -14.0, + 2.0, + -14.0 + ], + "to": [ + -12.0, + 5.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 6.0, + 2.0, + 6.0 + ], + "to": [ + 28.0, + 7.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 6.0, + 7.0, + 6.0 + ], + "to": [ + 28.0, + 12.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 6.0, + 12.0, + 6.0 + ], + "to": [ + 28.0, + 17.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 5.0 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#tube_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/ew_truck.json b/airdef-mod/source-assets/assets/airdef/models/block/ew_truck.json new file mode 100644 index 0000000..02971fc --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/ew_truck.json @@ -0,0 +1,4299 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "bare_antenna": "airdef:block/bare_antenna", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.0 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -4.0 + ], + "to": [ + 21.0, + 22.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 12.0, + 30.0 + ], + "to": [ + 20.0, + 20.0, + 30.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 8.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 8.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.3000000000000007 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.3000000000000007 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 7.0, + 22.0, + 10.0 + ], + "to": [ + 9.0, + 32.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#bare_antenna" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#bare_antenna" + } + } + }, + { + "from": [ + -4.0, + 24.0, + 9.0 + ], + "to": [ + 20.0, + 25.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna" + } + } + }, + { + "from": [ + -4.0, + 28.0, + 9.0 + ], + "to": [ + 20.0, + 29.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/ew_truck_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/ew_truck_burnt.json new file mode 100644 index 0000000..bc3ca8d --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/ew_truck_burnt.json @@ -0,0 +1,3279 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "glass_cab_b": "airdef:block/glass_cab_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "bare_antenna_b": "airdef:block/bare_antenna_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -4.0 + ], + "to": [ + 21.0, + 22.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 12.0, + 30.0 + ], + "to": [ + 20.0, + 20.0, + 30.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.3000000000000007 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.3000000000000007 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 7.0, + 22.0, + 10.0 + ], + "to": [ + 9.0, + 32.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#bare_antenna_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#bare_antenna_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#bare_antenna_b" + } + } + }, + { + "from": [ + -4.0, + 24.0, + 9.0 + ], + "to": [ + 20.0, + 25.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna_b" + } + } + }, + { + "from": [ + -4.0, + 28.0, + 9.0 + ], + "to": [ + 20.0, + 29.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#bare_antenna_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#bare_antenna_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/gepard_guns.json b/airdef-mod/source-assets/assets/airdef/models/block/gepard_guns.json new file mode 100644 index 0000000..9ec9c4d --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/gepard_guns.json @@ -0,0 +1,419 @@ +{ + "ambientocclusion": false, + "textures": { + "bare_barrel": "airdef:block/bare_barrel", + "pilot_nato": "airdef:block/pilot_nato", + "dark_metal": "airdef:block/dark_metal", + "particle": "airdef:block/bare_barrel" + }, + "elements": [ + { + "from": [ + -8.0, + 5.0, + -16.0 + ], + "to": [ + -5.5, + 7.5, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel" + } + } + }, + { + "from": [ + 21.5, + 5.0, + -16.0 + ], + "to": [ + 24.0, + 7.5, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel" + } + } + }, + { + "from": [ + -9.0, + 3.5, + 2.0 + ], + "to": [ + -4.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + 20.0, + 3.5, + 2.0 + ], + "to": [ + 25.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -8.5, + 4.5, + -16.0 + ], + "to": [ + -5.0, + 8.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5, + 3.5 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.5, + 3.5 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 3.5 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 3.5 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.5, + 3.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.5, + 3.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 21.0, + 4.5, + -16.0 + ], + "to": [ + 24.5, + 8.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5, + 3.5 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.5, + 3.5 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 3.5 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 3.5 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.5, + 3.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.5, + 3.0 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/gepard_guns_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/gepard_guns_burnt.json new file mode 100644 index 0000000..899e053 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/gepard_guns_burnt.json @@ -0,0 +1,351 @@ +{ + "ambientocclusion": false, + "textures": { + "bare_barrel_b": "airdef:block/bare_barrel_b", + "pilot_nato_b": "airdef:block/pilot_nato_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "particle": "airdef:block/bare_barrel_b" + }, + "elements": [ + { + "from": [ + -8.0, + 5.0, + -16.0 + ], + "to": [ + -5.5, + 7.5, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel_b" + } + } + }, + { + "from": [ + 21.5, + 5.0, + -16.0 + ], + "to": [ + 24.0, + 7.5, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.5 + ], + "texture": "#bare_barrel_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 16 + ], + "texture": "#bare_barrel_b" + } + } + }, + { + "from": [ + -9.0, + 3.5, + 2.0 + ], + "to": [ + -4.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + 20.0, + 3.5, + 2.0 + ], + "to": [ + 25.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 5.5 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 12.0 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -8.5, + 4.5, + -16.0 + ], + "to": [ + -5.0, + 8.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5, + 3.5 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.5, + 3.5 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 3.5 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 3.5 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.5, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.5, + 3.0 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/gepard_hull.json b/airdef-mod/source-assets/assets/airdef/models/block/gepard_hull.json new file mode 100644 index 0000000..a3c25ff --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/gepard_hull.json @@ -0,0 +1,3763 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato": "airdef:block/pilot_nato", + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "track": "airdef:block/track", + "hub": "airdef:block/hub", + "bare_nozzle": "airdef:block/bare_nozzle", + "particle": "airdef:block/pilot_nato" + }, + "elements": [ + { + "from": [ + -9.0, + 4.0, + -8.0 + ], + "to": [ + 25.0, + 13.5, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -9.0, + 4.0, + -15.0 + ], + "to": [ + 25.0, + 9.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 5.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 5.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -8.5, + 9.0, + -14.0 + ], + "to": [ + 24.5, + 13.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -10.5 + ] + } + }, + { + "from": [ + -8.0, + 13.5, + -4.0 + ], + "to": [ + 24.0, + 15.2, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -7.0, + 13.5, + 18.0 + ], + "to": [ + 23.0, + 14.5, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -6.0, + 15.3, + -6.0 + ], + "to": [ + 3.0, + 15.9, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.5999999999999996 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.5999999999999996 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -14.0, + 0.0, + -14.0 + ], + "to": [ + -10.0, + 8.8, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + 26.0, + 0.0, + -14.0 + ], + "to": [ + 30.0, + 8.8, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -11.82 + ], + "to": [ + -14.02, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + -11.82 + ], + "to": [ + 30.08, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -12.8 + ], + "to": [ + -14.02, + 6.05, + -7.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -12.8 + ], + "to": [ + 30.08, + 6.05, + -7.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + -11.82 + ], + "to": [ + -14.02, + 7.8, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -11.82 + ], + "to": [ + 30.08, + 7.8, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -5.82 + ], + "to": [ + -14.02, + 2.55, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + -5.82 + ], + "to": [ + 30.08, + 2.55, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -6.8 + ], + "to": [ + -14.02, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -6.8 + ], + "to": [ + 30.08, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + -5.82 + ], + "to": [ + -14.02, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -5.82 + ], + "to": [ + 30.08, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 0.18000000000000016 + ], + "to": [ + -14.02, + 2.55, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 0.18000000000000016 + ], + "to": [ + 30.08, + 2.55, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -0.7999999999999998 + ], + "to": [ + -14.02, + 6.05, + 4.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -0.7999999999999998 + ], + "to": [ + 30.08, + 6.05, + 4.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 0.18000000000000016 + ], + "to": [ + -14.02, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 0.18000000000000016 + ], + "to": [ + 30.08, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 6.18 + ], + "to": [ + -14.02, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 6.18 + ], + "to": [ + 30.08, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 5.2 + ], + "to": [ + -14.02, + 6.05, + 10.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 5.2 + ], + "to": [ + 30.08, + 6.05, + 10.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 6.18 + ], + "to": [ + -14.02, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 6.18 + ], + "to": [ + 30.08, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 12.18 + ], + "to": [ + -14.02, + 2.55, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 12.18 + ], + "to": [ + 30.08, + 2.55, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 11.2 + ], + "to": [ + -14.02, + 6.05, + 16.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 11.2 + ], + "to": [ + 30.08, + 6.05, + 16.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 12.18 + ], + "to": [ + -14.02, + 7.8, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 12.18 + ], + "to": [ + 30.08, + 7.8, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 18.18 + ], + "to": [ + -14.02, + 2.55, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 18.18 + ], + "to": [ + 30.08, + 2.55, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 17.2 + ], + "to": [ + -14.02, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 17.2 + ], + "to": [ + 30.08, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 18.18 + ], + "to": [ + -14.02, + 7.8, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 18.18 + ], + "to": [ + 30.08, + 7.8, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 24.18 + ], + "to": [ + -14.02, + 2.55, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 24.18 + ], + "to": [ + 30.08, + 2.55, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 23.2 + ], + "to": [ + -14.02, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 23.2 + ], + "to": [ + 30.08, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 24.18 + ], + "to": [ + -14.02, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 24.18 + ], + "to": [ + 30.08, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -14.5, + 8.6, + -14.0 + ], + "to": [ + -10.0, + 10.0, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + 26.0, + 8.6, + -14.0 + ], + "to": [ + 30.5, + 10.0, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 20.0 + ], + "to": [ + 22.0, + 14.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 24.0 + ], + "to": [ + 22.0, + 14.8, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 28.0 + ], + "to": [ + 22.0, + 14.8, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/gepard_hull_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/gepard_hull_burnt.json new file mode 100644 index 0000000..04683f0 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/gepard_hull_burnt.json @@ -0,0 +1,2811 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato_b": "airdef:block/pilot_nato_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "track_b": "airdef:block/track_b", + "hub_b": "airdef:block/hub_b", + "bare_nozzle_b": "airdef:block/bare_nozzle_b", + "particle": "airdef:block/pilot_nato_b" + }, + "elements": [ + { + "from": [ + -9.0, + 4.0, + -8.0 + ], + "to": [ + 25.0, + 13.5, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -9.0, + 4.0, + -15.0 + ], + "to": [ + 25.0, + 9.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -8.5, + 9.0, + -14.0 + ], + "to": [ + 24.5, + 13.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -10.5 + ] + } + }, + { + "from": [ + -8.0, + 13.5, + -4.0 + ], + "to": [ + 24.0, + 15.2, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.6999999999999993 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -7.0, + 13.5, + 18.0 + ], + "to": [ + 23.0, + 14.5, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -6.0, + 15.3, + -6.0 + ], + "to": [ + 3.0, + 15.9, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.5999999999999996 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.5999999999999996 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -14.0, + 0.0, + -14.0 + ], + "to": [ + -10.0, + 8.8, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + 26.0, + 0.0, + -14.0 + ], + "to": [ + 30.0, + 8.8, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.8 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.8 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -11.82 + ], + "to": [ + -14.02, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + -11.82 + ], + "to": [ + 30.08, + 2.55, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -12.8 + ], + "to": [ + -14.02, + 6.05, + -7.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -11.82 + ], + "to": [ + 30.08, + 7.8, + -8.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + -5.82 + ], + "to": [ + -14.02, + 2.55, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -6.8 + ], + "to": [ + -14.02, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + -6.8 + ], + "to": [ + 30.08, + 6.05, + -1.2000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + -5.82 + ], + "to": [ + -14.02, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + -5.82 + ], + "to": [ + 30.08, + 7.8, + -2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.64, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.64 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 0.18000000000000016 + ], + "to": [ + 30.08, + 2.55, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + -0.7999999999999998 + ], + "to": [ + -14.02, + 6.05, + 4.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 0.18000000000000016 + ], + "to": [ + -14.02, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 0.18000000000000016 + ], + "to": [ + 30.08, + 7.8, + 3.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6399999999999997, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6399999999999997 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 6.18 + ], + "to": [ + -14.02, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 6.18 + ], + "to": [ + 30.08, + 2.55, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 5.2 + ], + "to": [ + -14.02, + 6.05, + 10.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.6000000000000005, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 6.18 + ], + "to": [ + -14.02, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 6.18 + ], + "to": [ + 30.08, + 7.8, + 9.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 12.18 + ], + "to": [ + 30.08, + 2.55, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 11.2 + ], + "to": [ + 30.08, + 6.05, + 16.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 12.18 + ], + "to": [ + -14.02, + 7.8, + 15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 0.7999999999999998, + 18.18 + ], + "to": [ + -14.02, + 2.55, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 17.2 + ], + "to": [ + -14.02, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 17.2 + ], + "to": [ + 30.08, + 6.05, + 22.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 18.18 + ], + "to": [ + -14.02, + 7.8, + 21.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 0.7999999999999998, + 24.18 + ], + "to": [ + 30.08, + 2.55, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 2.55, + 23.2 + ], + "to": [ + -14.02, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 2.55, + 23.2 + ], + "to": [ + 30.08, + 6.05, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.600000000000001, + 3.5 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.600000000000001 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.08, + 6.05, + 24.18 + ], + "to": [ + -14.02, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 30.02, + 6.05, + 24.18 + ], + "to": [ + 30.08, + 7.8, + 27.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.75 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6400000000000006, + 1.75 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6400000000000006 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -14.5, + 8.6, + -14.0 + ], + "to": [ + -10.0, + 10.0, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + 26.0, + 8.6, + -14.0 + ], + "to": [ + 30.5, + 10.0, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.5, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -6.0, + 14.6, + 24.0 + ], + "to": [ + 22.0, + 14.8, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 0.25 + ], + "texture": "#bare_nozzle_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#bare_nozzle_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/gepard_turret.json b/airdef-mod/source-assets/assets/airdef/models/block/gepard_turret.json new file mode 100644 index 0000000..8a11834 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/gepard_turret.json @@ -0,0 +1,779 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato": "airdef:block/pilot_nato", + "radar_face": "airdef:block/radar_face", + "metal": "airdef:block/metal", + "dark_metal": "airdef:block/dark_metal", + "mk_de": "airdef:block/mk_de", + "particle": "airdef:block/pilot_nato" + }, + "elements": [ + { + "from": [ + -4.0, + 0.0, + -5.0 + ], + "to": [ + 20.0, + 5.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -2.5, + 5.0, + -5.0 + ], + "to": [ + 18.5, + 12.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -2.0, + 12.0, + -2.0 + ], + "to": [ + 18.0, + 13.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + 3.0, + 4.0, + -10.0 + ], + "to": [ + 13.0, + 12.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#radar_face" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#radar_face" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#radar_face" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#radar_face" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 4.0 + ], + "texture": "#radar_face" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 4.0 + ], + "texture": "#radar_face" + } + } + }, + { + "from": [ + 7.0, + 13.0, + 10.0 + ], + "to": [ + 9.0, + 18.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -4.7, + 4.8, + -4.0 + ], + "to": [ + -2.5, + 10.7, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.2, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 2.2, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 2.2, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 2.2, + 16 + ], + "texture": "#pilot_nato" + } + }, + "rotation": { + "angle": 22.5, + "axis": "z", + "origin": [ + -3.6, + 7.75, + 5.0 + ] + } + }, + { + "from": [ + 18.5, + 4.8, + -4.0 + ], + "to": [ + 20.7, + 10.7, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 16 + ], + "texture": "#pilot_nato" + } + }, + "rotation": { + "angle": -22.5, + "axis": "z", + "origin": [ + 19.6, + 7.75, + 5.0 + ] + } + }, + { + "from": [ + 0.0, + 13.05, + 2.0 + ], + "to": [ + 5.0, + 13.7, + 8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 10.0, + 13.05, + 2.0 + ], + "to": [ + 15.0, + 13.7, + 8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 18.5, + 6.0, + -4.0 + ], + "to": [ + 18.619999999999997, + 9.0, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#mk_de" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#mk_de" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#mk_de" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#mk_de" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#mk_de" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#mk_de" + } + } + }, + { + "from": [ + -2.6199999999999974, + 6.0, + -4.0 + ], + "to": [ + -2.5, + 9.0, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#mk_de" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#mk_de" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#mk_de" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#mk_de" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#mk_de" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#mk_de" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/gepard_turret_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/gepard_turret_burnt.json new file mode 100644 index 0000000..cb3613b --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/gepard_turret_burnt.json @@ -0,0 +1,711 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato_b": "airdef:block/pilot_nato_b", + "radar_face_b": "airdef:block/radar_face_b", + "metal_b": "airdef:block/metal_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "mk_de_b": "airdef:block/mk_de_b", + "particle": "airdef:block/pilot_nato_b" + }, + "elements": [ + { + "from": [ + -4.0, + 0.0, + -5.0 + ], + "to": [ + 20.0, + 5.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -2.5, + 5.0, + -5.0 + ], + "to": [ + 18.5, + 12.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -2.0, + 12.0, + -2.0 + ], + "to": [ + 18.0, + 13.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + 3.0, + 4.0, + -10.0 + ], + "to": [ + 13.0, + 12.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#radar_face_b" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#radar_face_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#radar_face_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#radar_face_b" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 4.0 + ], + "texture": "#radar_face_b" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 4.0 + ], + "texture": "#radar_face_b" + } + } + }, + { + "from": [ + 7.0, + 13.0, + 10.0 + ], + "to": [ + 9.0, + 18.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -4.7, + 4.8, + -4.0 + ], + "to": [ + -2.5, + 10.7, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.2, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.2, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.2, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.2, + 16 + ], + "texture": "#pilot_nato_b" + } + }, + "rotation": { + "angle": 22.5, + "axis": "z", + "origin": [ + -3.6, + 7.75, + 5.0 + ] + } + }, + { + "from": [ + 18.5, + 4.8, + -4.0 + ], + "to": [ + 20.7, + 10.7, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.8999999999999995 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 16 + ], + "texture": "#pilot_nato_b" + } + }, + "rotation": { + "angle": -22.5, + "axis": "z", + "origin": [ + 19.6, + 7.75, + 5.0 + ] + } + }, + { + "from": [ + 0.0, + 13.05, + 2.0 + ], + "to": [ + 5.0, + 13.7, + 8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 10.0, + 13.05, + 2.0 + ], + "to": [ + 15.0, + 13.7, + 8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 5.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.6499999999999986 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 5.0, + 6.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -2.6199999999999974, + 6.0, + -4.0 + ], + "to": [ + -2.5, + 9.0, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#mk_de_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#mk_de_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#mk_de_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#mk_de_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#mk_de_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#mk_de_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/irist_chassis.json b/airdef-mod/source-assets/assets/airdef/models/block/irist_chassis.json new file mode 100644 index 0000000..327ac05 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/irist_chassis.json @@ -0,0 +1,4367 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato": "airdef:block/pilot_nato", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", + "metal": "airdef:block/metal", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "particle": "airdef:block/pilot_nato" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -5.0, + 8.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.5 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 11.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -3.0, + 11.0, + -4.0 + ], + "to": [ + 19.0, + 13.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -7.0, + 6.0, + 4.0 + ], + "to": [ + -5.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 21.0, + 6.0, + 4.0 + ], + "to": [ + 23.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 25.0 + ], + "to": [ + -5.0, + 6.0, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 25.0 + ], + "to": [ + 23.0, + 6.0, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/irist_chassis_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/irist_chassis_burnt.json new file mode 100644 index 0000000..7afe773 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/irist_chassis_burnt.json @@ -0,0 +1,3415 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato_b": "airdef:block/pilot_nato_b", + "glass_cab_b": "airdef:block/glass_cab_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", + "metal_b": "airdef:block/metal_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "particle": "airdef:block/pilot_nato_b" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -5.0, + 8.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.5 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 11.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -3.0, + 11.0, + -4.0 + ], + "to": [ + 19.0, + 13.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -7.0, + 6.0, + 4.0 + ], + "to": [ + -5.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 21.0, + 6.0, + 4.0 + ], + "to": [ + 23.0, + 9.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 10.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 25.0 + ], + "to": [ + -5.0, + 6.0, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/irist_tubes.json b/airdef-mod/source-assets/assets/airdef/models/block/irist_tubes.json new file mode 100644 index 0000000..a54c4e3 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/irist_tubes.json @@ -0,0 +1,827 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap": "airdef:block/tube_cap", + "canister": "airdef:block/canister", + "dark_metal": "airdef:block/dark_metal", + "particle": "airdef:block/tube_cap" + }, + "elements": [ + { + "from": [ + -1.5, + 0.0, + -16.0 + ], + "to": [ + 2.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + -1.5, + 5.0, + -16.0 + ], + "to": [ + 2.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 3.5, + 0.0, + -16.0 + ], + "to": [ + 7.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 3.5, + 5.0, + -16.0 + ], + "to": [ + 7.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 8.5, + 0.0, + -16.0 + ], + "to": [ + 12.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 8.5, + 5.0, + -16.0 + ], + "to": [ + 12.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 13.5, + 0.0, + -16.0 + ], + "to": [ + 17.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 13.5, + 5.0, + -16.0 + ], + "to": [ + 17.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + -2.5, + 0.0, + 26.0 + ], + "to": [ + 18.5, + 9.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -2.5, + 0.0, + -2.0 + ], + "to": [ + 18.5, + 9.0, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -1.9, + 0.0, + -2.0 + ], + "to": [ + 17.9, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -1.9, + 0.0, + 22.0 + ], + "to": [ + 17.9, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/irist_tubes_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/irist_tubes_burnt.json new file mode 100644 index 0000000..4a342e3 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/irist_tubes_burnt.json @@ -0,0 +1,827 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap_b": "airdef:block/tube_cap_b", + "canister_b": "airdef:block/canister_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "particle": "airdef:block/tube_cap_b" + }, + "elements": [ + { + "from": [ + -1.5, + 0.0, + -16.0 + ], + "to": [ + 2.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + -1.5, + 5.0, + -16.0 + ], + "to": [ + 2.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 3.5, + 0.0, + -16.0 + ], + "to": [ + 7.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 3.5, + 5.0, + -16.0 + ], + "to": [ + 7.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 8.5, + 0.0, + -16.0 + ], + "to": [ + 12.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 8.5, + 5.0, + -16.0 + ], + "to": [ + 12.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 13.5, + 0.0, + -16.0 + ], + "to": [ + 17.5, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 13.5, + 5.0, + -16.0 + ], + "to": [ + 17.5, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + -2.5, + 0.0, + 26.0 + ], + "to": [ + 18.5, + 9.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -2.5, + 0.0, + -2.0 + ], + "to": [ + 18.5, + 9.0, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -1.9, + 0.0, + -2.0 + ], + "to": [ + 17.9, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -1.9, + 0.0, + 22.0 + ], + "to": [ + 17.9, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/kh101.json b/airdef-mod/source-assets/assets/airdef/models/block/kh101.json new file mode 100644 index 0000000..dd9f8f6 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/kh101.json @@ -0,0 +1,2098 @@ +{ + "ambientocclusion": false, + "textures": { + "kh101": "airdef:block/kh101", + "kh101_belly": "airdef:block/kh101_belly", + "dark_metal": "airdef:block/dark_metal", + "bare_nozzle": "airdef:block/bare_nozzle", + "glow_ember": "airdef:block/glow_ember", + "particle": "airdef:block/kh101" + }, + "elements": [ + { + "from": [ + 6.972708033799074, + 6.472708033799074, + -16.0 + ], + "to": [ + 9.027291966200925, + 8.527291966200925, + -14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 2.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 2.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 6.972708033799074, + 6.472708033799074, + -15.97 + ], + "to": [ + 9.027291966200925, + 8.527291966200925, + -14.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 2.0545839324018518 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 1.9400000000000013 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 2.0545839324018518, + 1.9400000000000013 + ], + "texture": "#kh101" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 7.5, + -15.0 + ] + } + }, + { + "from": [ + 6.32645699637816, + 5.82645699637816, + -14.0 + ], + "to": [ + 9.673543003621841, + 9.173543003621841, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.347086007243681, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.347086007243681, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.347086007243681, + 2.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.347086007243681, + 2.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 6.32645699637816, + 5.82645699637816, + -13.97 + ], + "to": [ + 9.673543003621841, + 9.173543003621841, + -12.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.347086007243681, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.347086007243681, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 3.347086007243681 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.347086007243681, + 1.9400000000000013 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.347086007243681, + 1.9400000000000013 + ], + "texture": "#kh101" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 7.5, + -13.0 + ] + } + }, + { + "from": [ + 5.84155970498232, + 5.34155970498232, + -12.0 + ], + "to": [ + 10.15844029501768, + 9.65844029501768, + -10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.31688059003536, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 4.31688059003536, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 4.31688059003536, + 2.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 4.31688059003536, + 2.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 5.84155970498232, + 5.34155970498232, + -11.97 + ], + "to": [ + 10.15844029501768, + 9.65844029501768, + -10.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.31688059003536, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 4.31688059003536, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 4.31688059003536 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 4.31688059003536, + 1.9400000000000013 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 4.31688059003536, + 1.9400000000000013 + ], + "texture": "#kh101" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 7.5, + -11.0 + ] + } + }, + { + "from": [ + 5.433147472507316, + 4.933147472507316, + -10.0 + ], + "to": [ + 10.566852527492685, + 10.066852527492685, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.133705054985369, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 5.133705054985369, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 5.133705054985369, + 2.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 5.133705054985369, + 2.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 5.433147472507316, + 4.933147472507316, + -9.97 + ], + "to": [ + 10.566852527492685, + 10.066852527492685, + -8.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.133705054985369, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 5.133705054985369, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 1.9400000000000013, + 5.133705054985369 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 5.133705054985369, + 1.9400000000000013 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 5.133705054985369, + 1.9400000000000013 + ], + "texture": "#kh101" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 7.5, + -9.0 + ] + } + }, + { + "from": [ + 5.32069, + 4.82069, + -8.0 + ], + "to": [ + 10.679310000000001, + 10.179310000000001, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.358620000000001, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 5.358620000000001, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 5.358620000000001, + 16 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 5.358620000000001, + 16 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 5.32069, + 4.82069, + -7.97 + ], + "to": [ + 10.679310000000001, + 10.179310000000001, + 26.97 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 5.358620000000001, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 5.358620000000001, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.358620000000001 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 5.358620000000001, + 16 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 5.358620000000001, + 16 + ], + "texture": "#kh101" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 7.5, + 9.5 + ] + } + }, + { + "from": [ + 10.5, + 6.9, + 2.0 + ], + "to": [ + 13.55, + 7.5, + 11.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 9.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 9.0 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 2.4499999999999993, + 6.9, + 2.0 + ], + "to": [ + 5.5, + 7.5, + 11.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 9.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 9.0 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 13.5, + 6.9, + 2.65 + ], + "to": [ + 16.55, + 7.5, + 10.88 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 8.23, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 8.23, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -0.5500000000000007, + 6.9, + 2.65 + ], + "to": [ + 2.5, + 7.5, + 10.88 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 8.23, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 8.23, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 8.23 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 16.5, + 6.9, + 3.3 + ], + "to": [ + 19.55, + 7.5, + 10.76 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -3.5500000000000007, + 6.9, + 3.3 + ], + "to": [ + -0.5, + 7.5, + 10.76 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 7.46, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 7.46 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 19.5, + 6.9, + 3.95 + ], + "to": [ + 22.55, + 7.5, + 10.64 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -6.550000000000001, + 6.9, + 3.95 + ], + "to": [ + -3.5, + 7.5, + 10.64 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 6.69, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 6.69 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 22.5, + 6.9, + 4.6 + ], + "to": [ + 25.55, + 7.5, + 10.52 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -9.55, + 6.9, + 4.6 + ], + "to": [ + -6.5, + 7.5, + 10.52 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.92, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0500000000000007, + 5.92 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 25.5, + 6.9, + 5.25 + ], + "to": [ + 28.5, + 7.5, + 10.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.15, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.15, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 5.15 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 5.15 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -12.5, + 6.9, + 5.25 + ], + "to": [ + -9.5, + 7.5, + 10.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 5.15, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 5.15, + 0.5999999999999996 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 5.15 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 5.15 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 7.6, + 10.0, + 20.0 + ], + "to": [ + 8.4, + 16.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.8000000000000007, + 6.0 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 0.8000000000000007, + 6.0 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 6.0 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 6.0 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 0.8000000000000007, + 8.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 0.8000000000000007, + 8.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 9.5, + 6.9, + 22.0 + ], + "to": [ + 16.0, + 7.4, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.5, + 0.5 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 6.5, + 0.5 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.5 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.5 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 6.5, + 6.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 6.5, + 6.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 0.0, + 6.9, + 22.0 + ], + "to": [ + 6.5, + 7.4, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.5, + 0.5 + ], + "texture": "#kh101" + }, + "south": { + "uv": [ + 0, + 0, + 6.5, + 0.5 + ], + "texture": "#kh101" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.5 + ], + "texture": "#kh101" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.5 + ], + "texture": "#kh101" + }, + "up": { + "uv": [ + 0, + 0, + 6.5, + 6.0 + ], + "texture": "#kh101" + }, + "down": { + "uv": [ + 0, + 0, + 6.5, + 6.0 + ], + "texture": "#kh101" + } + } + }, + { + "from": [ + 6.1, + 3.2, + 12.0 + ], + "to": [ + 9.9, + 5.4, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.8000000000000007, + 2.2 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.8000000000000007, + 2.2 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 2.2 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 2.2 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.8000000000000007, + 14.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.8000000000000007, + 14.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 6.4, + 3.5, + 11.4 + ], + "to": [ + 9.6, + 5.1, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.1999999999999993, + 1.5999999999999996 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 3.1999999999999993, + 1.5999999999999996 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 0.5999999999999996, + 1.5999999999999996 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 0.5999999999999996, + 1.5999999999999996 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 3.1999999999999993, + 0.5999999999999996 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 3.1999999999999993, + 0.5999999999999996 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + 6.5, + 3.6, + 26.0 + ], + "to": [ + 9.5, + 5.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 1.4 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 1.4 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + 6.9, + 3.9, + 28.0 + ], + "to": [ + 9.1, + 4.7, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 0.8000000000000003 + ], + "texture": "#glow_ember" + }, + "south": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 0.8000000000000003 + ], + "texture": "#glow_ember" + }, + "east": { + "uv": [ + 0, + 0, + 0.3999999999999986, + 0.8000000000000003 + ], + "texture": "#glow_ember" + }, + "west": { + "uv": [ + 0, + 0, + 0.3999999999999986, + 0.8000000000000003 + ], + "texture": "#glow_ember" + }, + "up": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 0.3999999999999986 + ], + "texture": "#glow_ember" + }, + "down": { + "uv": [ + 0, + 0, + 2.1999999999999993, + 0.3999999999999986 + ], + "texture": "#glow_ember" + } + } + }, + { + "from": [ + 7.0, + 10.0, + -2.0 + ], + "to": [ + 9.0, + 10.6, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 12.0 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/lancet.json b/airdef-mod/source-assets/assets/airdef/models/block/lancet.json new file mode 100644 index 0000000..88351de --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/lancet.json @@ -0,0 +1,3686 @@ +{ + "ambientocclusion": false, + "textures": { + "lancet": "airdef:block/lancet", + "bare_lamp": "airdef:block/bare_lamp", + "dark_metal": "airdef:block/dark_metal", + "prop": "airdef:block/prop", + "particle": "airdef:block/lancet" + }, + "elements": [ + { + "from": [ + 7.291522781930396, + 7.291522781930396, + -16.0 + ], + "to": [ + 8.708477218069604, + 8.708477218069604, + -14.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.5, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.5, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.5 + ], + "texture": "#lancet" + } + } + }, + { + "from": [ + 7.291522781930396, + 7.291522781930396, + -15.97 + ], + "to": [ + 8.708477218069604, + 8.708477218069604, + -14.53 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 1.416954436139208 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.4400000000000013 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 1.416954436139208, + 1.4400000000000013 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -15.25 + ] + } + }, + { + "from": [ + 6.845832411295283, + 6.845832411295283, + -14.5 + ], + "to": [ + 9.154167588704718, + 9.154167588704718, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.308335177409435, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.308335177409435, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.5, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.5, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.308335177409435, + 1.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.308335177409435, + 1.5 + ], + "texture": "#lancet" + } + } + }, + { + "from": [ + 6.845832411295283, + 6.845832411295283, + -14.47 + ], + "to": [ + 9.154167588704718, + 9.154167588704718, + -13.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.308335177409435, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.308335177409435, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 2.308335177409435 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.308335177409435, + 1.4400000000000013 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.308335177409435, + 1.4400000000000013 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -13.75 + ] + } + }, + { + "from": [ + 6.511420486194703, + 6.511420486194703, + -13.0 + ], + "to": [ + 9.488579513805297, + 9.488579513805297, + -11.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.5, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.5, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.5 + ], + "texture": "#lancet" + } + } + }, + { + "from": [ + 6.511420486194703, + 6.511420486194703, + -12.97 + ], + "to": [ + 9.488579513805297, + 9.488579513805297, + -11.53 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 2.9771590276105933 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.4400000000000013 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9771590276105933, + 1.4400000000000013 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -12.25 + ] + } + }, + { + "from": [ + 6.229756877591252, + 6.229756877591252, + -11.5 + ], + "to": [ + 9.770243122408747, + 9.770243122408747, + -10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.5, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.5, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 1.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 1.5 + ], + "texture": "#lancet" + } + } + }, + { + "from": [ + 6.229756877591252, + 6.229756877591252, + -11.47 + ], + "to": [ + 9.770243122408747, + 9.770243122408747, + -10.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 1.4400000000000013, + 3.5404862448174947 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 1.4400000000000013 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.5404862448174947, + 1.4400000000000013 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -10.75 + ] + } + }, + { + "from": [ + 6.1522, + 6.1522, + -10.0 + ], + "to": [ + 9.8478, + 9.8478, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 16 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 16 + ], + "texture": "#lancet" + } + } + }, + { + "from": [ + 6.1522, + 6.1522, + -9.97 + ], + "to": [ + 9.8478, + 9.8478, + 19.97 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.6955999999999998 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 16 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.6955999999999998, + 16 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + 4.999999999999999 + ] + } + }, + { + "from": [ + 6.8, + 5.2, + -14.0 + ], + "to": [ + 9.2, + 6.4, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.3999999999999995, + 1.2000000000000002 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 2.3999999999999995, + 1.2000000000000002 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 1.2000000000000002 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 1.2000000000000002 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 2.3999999999999995, + 5.0 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 2.3999999999999995, + 5.0 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 7.2, + 5.05, + -13.4 + ], + "to": [ + 8.8, + 5.2, + -10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.4000000000000004, + 0.25 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.4000000000000004, + 0.25 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 3.4000000000000004 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 3.4000000000000004 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 7.2, + 9.9, + -6.0 + ], + "to": [ + 8.8, + 10.1, + 5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 0.25 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 11.0, + 0.25 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 11.0, + 0.25 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 11.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.6000000000000005, + 11.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 5.14595148057186, + 6.63095148057186, + -5.0 + ], + "to": [ + 8.55595148057186, + 7.07095148057186, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 6.85095148057186, + 6.85095148057186, + -2.0 + ] + } + }, + { + "from": [ + 2.8478544417155813, + 4.332854441715582, + -4.5 + ], + "to": [ + 6.2578544417155815, + 4.772854441715581, + 0.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 4.552854441715581, + 4.552854441715581, + -1.875 + ] + } + }, + { + "from": [ + 0.549757402859302, + 2.034757402859302, + -4.0 + ], + "to": [ + 3.959757402859302, + 2.4747574028593022, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 2.254757402859302, + 2.254757402859302, + -1.75 + ] + } + }, + { + "from": [ + -1.7483396359969774, + -0.2633396359969773, + -3.5 + ], + "to": [ + 1.6616603640030227, + 0.17666036400302268, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + -0.04333963599697732, + -0.04333963599697731, + -1.625 + ] + } + }, + { + "from": [ + 5.14595148057186, + 8.92904851942814, + -5.0 + ], + "to": [ + 8.55595148057186, + 9.36904851942814, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 6.85095148057186, + 9.14904851942814, + -2.0 + ] + } + }, + { + "from": [ + 2.8478544417155813, + 11.227145558284418, + -4.5 + ], + "to": [ + 6.2578544417155815, + 11.66714555828442, + 0.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 4.552854441715581, + 11.447145558284419, + -1.875 + ] + } + }, + { + "from": [ + 0.549757402859302, + 13.525242597140698, + -4.0 + ], + "to": [ + 3.959757402859302, + 13.9652425971407, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 2.254757402859302, + 13.745242597140699, + -1.75 + ] + } + }, + { + "from": [ + -1.7483396359969774, + 15.823339635996978, + -3.5 + ], + "to": [ + 1.6616603640030227, + 16.263339635996978, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + -0.04333963599697732, + 16.04333963599698, + -1.625 + ] + } + }, + { + "from": [ + 7.44404851942814, + 6.63095148057186, + -5.0 + ], + "to": [ + 10.85404851942814, + 7.07095148057186, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 9.14904851942814, + 6.85095148057186, + -2.0 + ] + } + }, + { + "from": [ + 9.742145558284419, + 4.332854441715582, + -4.5 + ], + "to": [ + 13.152145558284419, + 4.772854441715581, + 0.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 11.447145558284419, + 4.552854441715581, + -1.875 + ] + } + }, + { + "from": [ + 12.040242597140697, + 2.034757402859302, + -4.0 + ], + "to": [ + 15.450242597140697, + 2.4747574028593022, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 13.745242597140697, + 2.254757402859302, + -1.75 + ] + } + }, + { + "from": [ + 14.338339635996977, + -0.2633396359969773, + -3.5 + ], + "to": [ + 17.748339635996977, + 0.17666036400302268, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 16.04333963599698, + -0.04333963599697731, + -1.625 + ] + } + }, + { + "from": [ + 7.44404851942814, + 8.92904851942814, + -5.0 + ], + "to": [ + 10.85404851942814, + 9.36904851942814, + 1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 9.14904851942814, + 9.14904851942814, + -2.0 + ] + } + }, + { + "from": [ + 9.742145558284419, + 11.227145558284418, + -4.5 + ], + "to": [ + 13.152145558284419, + 11.66714555828442, + 0.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 11.447145558284419, + 11.447145558284419, + -1.875 + ] + } + }, + { + "from": [ + 12.040242597140697, + 13.525242597140698, + -4.0 + ], + "to": [ + 15.450242597140697, + 13.9652425971407, + 0.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 13.745242597140697, + 13.745242597140699, + -1.75 + ] + } + }, + { + "from": [ + 14.338339635996977, + 15.823339635996978, + -3.5 + ], + "to": [ + 17.748339635996977, + 16.263339635996978, + 0.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 3.41, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 3.41, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 16.04333963599698, + 16.04333963599698, + -1.625 + ] + } + }, + { + "from": [ + 5.572728175868497, + 6.8077281758684975, + 12.0 + ], + "to": [ + 8.482728175868496, + 7.247728175868497, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 7.027728175868496, + 7.027728175868497, + 15.0 + ] + } + }, + { + "from": [ + 3.628184527605491, + 4.863184527605491, + 12.5 + ], + "to": [ + 6.538184527605491, + 5.303184527605491, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 5.083184527605491, + 5.083184527605491, + 15.125 + ] + } + }, + { + "from": [ + 1.6836408793424864, + 2.9186408793424863, + 13.0 + ], + "to": [ + 4.5936408793424865, + 3.3586408793424867, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 3.1386408793424865, + 3.1386408793424865, + 15.25 + ] + } + }, + { + "from": [ + -0.2609027689205199, + 0.9740972310794802, + 13.5 + ], + "to": [ + 2.6490972310794803, + 1.4140972310794802, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 1.1940972310794802, + 1.1940972310794802, + 15.375 + ] + } + }, + { + "from": [ + 5.572728175868497, + 8.752271824131503, + 12.0 + ], + "to": [ + 8.482728175868496, + 9.192271824131504, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 7.027728175868496, + 8.972271824131504, + 15.0 + ] + } + }, + { + "from": [ + 3.628184527605491, + 10.696815472394508, + 12.5 + ], + "to": [ + 6.538184527605491, + 11.13681547239451, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 5.083184527605491, + 10.916815472394509, + 15.125 + ] + } + }, + { + "from": [ + 1.6836408793424864, + 12.641359120657514, + 13.0 + ], + "to": [ + 4.5936408793424865, + 13.081359120657515, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 3.1386408793424865, + 12.861359120657514, + 15.25 + ] + } + }, + { + "from": [ + -0.2609027689205199, + 14.58590276892052, + 13.5 + ], + "to": [ + 2.6490972310794803, + 15.02590276892052, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 1.1940972310794802, + 14.80590276892052, + 15.375 + ] + } + }, + { + "from": [ + 7.517271824131503, + 6.8077281758684975, + 12.0 + ], + "to": [ + 10.427271824131502, + 7.247728175868497, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 8.972271824131502, + 7.027728175868497, + 15.0 + ] + } + }, + { + "from": [ + 9.461815472394509, + 4.863184527605491, + 12.5 + ], + "to": [ + 12.371815472394509, + 5.303184527605491, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4399999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 10.916815472394509, + 5.083184527605491, + 15.125 + ] + } + }, + { + "from": [ + 11.406359120657513, + 2.9186408793424863, + 13.0 + ], + "to": [ + 14.316359120657513, + 3.3586408793424867, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000004 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 12.861359120657513, + 3.1386408793424865, + 15.25 + ] + } + }, + { + "from": [ + 13.35090276892052, + 0.9740972310794802, + 13.5 + ], + "to": [ + 16.26090276892052, + 1.4140972310794802, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.43999999999999995 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": -45, + "axis": "z", + "origin": [ + 14.80590276892052, + 1.1940972310794802, + 15.375 + ] + } + }, + { + "from": [ + 7.517271824131503, + 8.752271824131503, + 12.0 + ], + "to": [ + 10.427271824131502, + 9.192271824131504, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.9099999999999993, + 6.0 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.972271824131502, + 8.972271824131504, + 15.0 + ] + } + }, + { + "from": [ + 9.461815472394509, + 10.696815472394508, + 12.5 + ], + "to": [ + 12.371815472394509, + 11.13681547239451, + 17.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 5.25, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 5.25 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 10.916815472394509, + 10.916815472394509, + 15.125 + ] + } + }, + { + "from": [ + 11.406359120657513, + 12.641359120657514, + 13.0 + ], + "to": [ + 14.316359120657513, + 13.081359120657515, + 17.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 4.5, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 4.5 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 12.861359120657513, + 12.861359120657514, + 15.25 + ] + } + }, + { + "from": [ + 13.35090276892052, + 14.58590276892052, + 13.5 + ], + "to": [ + 16.26090276892052, + 15.02590276892052, + 17.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "south": { + "uv": [ + 0, + 0, + 2.91, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "east": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "west": { + "uv": [ + 0, + 0, + 3.75, + 0.4400000000000013 + ], + "texture": "#lancet" + }, + "up": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + }, + "down": { + "uv": [ + 0, + 0, + 2.91, + 3.75 + ], + "texture": "#lancet" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 14.80590276892052, + 14.80590276892052, + 15.375 + ] + } + }, + { + "from": [ + 6.61415, + 6.61415, + 20.0 + ], + "to": [ + 9.38585, + 9.38585, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.771699999999999, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.771699999999999, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.771699999999999, + 4.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.771699999999999, + 4.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 6.61415, + 6.61415, + 20.03 + ], + "to": [ + 9.38585, + 9.38585, + 23.97 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.771699999999999, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.771699999999999, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.9399999999999977, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.9399999999999977, + 2.771699999999999 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.771699999999999, + 3.9399999999999977 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.771699999999999, + 3.9399999999999977 + ], + "texture": "#dark_metal" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + 22.0 + ] + } + }, + { + "from": [ + 3.0, + 7.85, + 24.0 + ], + "to": [ + 13.0, + 8.15, + 24.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 0.3000000000000007 + ], + "texture": "#prop" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 0.3000000000000007 + ], + "texture": "#prop" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 0.3000000000000007 + ], + "texture": "#prop" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 0.3000000000000007 + ], + "texture": "#prop" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 0.5 + ], + "texture": "#prop" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 0.5 + ], + "texture": "#prop" + } + } + }, + { + "from": [ + 7.85, + 3.0, + 24.0 + ], + "to": [ + 8.15, + 13.0, + 24.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 10.0 + ], + "texture": "#prop" + }, + "south": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 10.0 + ], + "texture": "#prop" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 10.0 + ], + "texture": "#prop" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 10.0 + ], + "texture": "#prop" + }, + "up": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 0.5 + ], + "texture": "#prop" + }, + "down": { + "uv": [ + 0, + 0, + 0.3000000000000007, + 0.5 + ], + "texture": "#prop" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/nasams_launcher.json b/airdef-mod/source-assets/assets/airdef/models/block/nasams_launcher.json new file mode 100644 index 0000000..596cdd1 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/nasams_launcher.json @@ -0,0 +1,691 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap": "airdef:block/tube_cap", + "canister": "airdef:block/canister", + "dark_metal": "airdef:block/dark_metal", + "particle": "airdef:block/tube_cap" + }, + "elements": [ + { + "from": [ + 1.0, + 0.0, + -16.0 + ], + "to": [ + 5.0, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 1.0, + 5.0, + -16.0 + ], + "to": [ + 5.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 6.0, + 0.0, + -16.0 + ], + "to": [ + 10.0, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 6.0, + 5.0, + -16.0 + ], + "to": [ + 10.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 11.0, + 0.0, + -16.0 + ], + "to": [ + 15.0, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 11.0, + 5.0, + -16.0 + ], + "to": [ + 15.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister" + } + } + }, + { + "from": [ + 0.0, + 0.0, + 26.0 + ], + "to": [ + 16.0, + 9.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.0, + 0.0, + -2.0 + ], + "to": [ + 16.0, + 9.0, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.6, + 0.0, + -2.0 + ], + "to": [ + 15.4, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.6, + 0.0, + 22.0 + ], + "to": [ + 15.4, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/nasams_launcher_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/nasams_launcher_burnt.json new file mode 100644 index 0000000..2c8ab6a --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/nasams_launcher_burnt.json @@ -0,0 +1,691 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap_b": "airdef:block/tube_cap_b", + "canister_b": "airdef:block/canister_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "particle": "airdef:block/tube_cap_b" + }, + "elements": [ + { + "from": [ + 1.0, + 0.0, + -16.0 + ], + "to": [ + 5.0, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 1.0, + 5.0, + -16.0 + ], + "to": [ + 5.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 6.0, + 0.0, + -16.0 + ], + "to": [ + 10.0, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 6.0, + 5.0, + -16.0 + ], + "to": [ + 10.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 11.0, + 0.0, + -16.0 + ], + "to": [ + 15.0, + 4.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 11.0, + 5.0, + -16.0 + ], + "to": [ + 15.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#canister_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#canister_b" + } + } + }, + { + "from": [ + 0.0, + 0.0, + 26.0 + ], + "to": [ + 16.0, + 9.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 0.0, + 0.0, + -2.0 + ], + "to": [ + 16.0, + 9.0, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 0.6, + 0.0, + -2.0 + ], + "to": [ + 15.4, + 9.0, + -1.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 0.6, + 0.0, + 22.0 + ], + "to": [ + 15.4, + 9.0, + 22.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.8, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.8, + 0.5 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/nasams_trailer.json b/airdef-mod/source-assets/assets/airdef/models/block/nasams_trailer.json new file mode 100644 index 0000000..e08e8bf --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/nasams_trailer.json @@ -0,0 +1,2189 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato": "airdef:block/pilot_nato", + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "particle": "airdef:block/pilot_nato" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -10.0 + ], + "to": [ + 21.0, + 8.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 8.0, + -8.0 + ], + "to": [ + 20.0, + 9.0, + 23.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 9.0, + 10.0 + ], + "to": [ + 20.0, + 13.0, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 14.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 14.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + 6.0, + 3.0, + -16.0 + ], + "to": [ + 10.0, + 6.0, + -10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -7.0, + 0.0, + -6.0 + ], + "to": [ + -5.0, + 5.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + -6.0 + ], + "to": [ + 23.0, + 5.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 23.0 + ], + "to": [ + -5.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ + 23.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 5.779999999999999 + ], + "to": [ + -5.5, + 1.48, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 5.779999999999999 + ], + "to": [ + 24.5, + 1.48, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 4.596 + ], + "to": [ + -5.5, + 2.96, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.48, + 4.596 + ], + "to": [ + 24.5, + 2.96, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 4.3 + ], + "to": [ + -5.5, + 4.44, + 11.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 2.96, + 4.3 + ], + "to": [ + 24.5, + 4.44, + 11.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.44, + 4.596 + ], + "to": [ + -5.5, + 5.92, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.44, + 4.596 + ], + "to": [ + 24.5, + 5.92, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 5.92, + 5.779999999999999 + ], + "to": [ + -5.5, + 7.4, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 5.92, + 5.779999999999999 + ], + "to": [ + 24.5, + 7.4, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 1.9000000000000001, + 6.2 + ], + "to": [ + -8.5, + 5.5, + 9.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 6.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 9.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 15.78 + ], + "to": [ + -5.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 15.78 + ], + "to": [ + 24.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 14.596 + ], + "to": [ + -5.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.48, + 14.596 + ], + "to": [ + 24.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 14.3 + ], + "to": [ + -5.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 2.96, + 14.3 + ], + "to": [ + 24.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.44, + 14.596 + ], + "to": [ + -5.5, + 5.92, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.44, + 14.596 + ], + "to": [ + 24.5, + 5.92, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 5.92, + 15.78 + ], + "to": [ + -5.5, + 7.4, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 5.92, + 15.78 + ], + "to": [ + 24.5, + 7.4, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 1.9000000000000001, + 16.2 + ], + "to": [ + -8.5, + 5.5, + 19.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 16.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 19.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/nasams_trailer_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/nasams_trailer_burnt.json new file mode 100644 index 0000000..aaa44bb --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/nasams_trailer_burnt.json @@ -0,0 +1,1713 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato_b": "airdef:block/pilot_nato_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "particle": "airdef:block/pilot_nato_b" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -10.0 + ], + "to": [ + 21.0, + 8.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -4.0, + 8.0, + -8.0 + ], + "to": [ + 20.0, + 9.0, + 23.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + 10.0 + ], + "to": [ + 20.0, + 13.0, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 4.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 14.0 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 14.0 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + 6.0, + 3.0, + -16.0 + ], + "to": [ + 10.0, + 6.0, + -10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -7.0, + 0.0, + -6.0 + ], + "to": [ + -5.0, + 5.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 21.0, + 0.0, + -6.0 + ], + "to": [ + 23.0, + 5.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 23.0 + ], + "to": [ + -5.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ + 23.0, + 5.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 5.779999999999999 + ], + "to": [ + -5.5, + 1.48, + 10.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.440000000000001, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.440000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 4.596 + ], + "to": [ + -5.5, + 2.96, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 4.3 + ], + "to": [ + -5.5, + 4.44, + 11.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 2.96, + 4.3 + ], + "to": [ + 24.5, + 4.44, + 11.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.3999999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.3999999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.44, + 4.596 + ], + "to": [ + -5.5, + 5.92, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.44, + 4.596 + ], + "to": [ + 24.5, + 5.92, + 11.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 1.9000000000000001, + 6.2 + ], + "to": [ + -8.5, + 5.5, + 9.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 6.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 9.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000005, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000005 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 15.78 + ], + "to": [ + -5.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 15.78 + ], + "to": [ + 24.5, + 1.48, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.48, + 14.596 + ], + "to": [ + -5.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.48, + 14.596 + ], + "to": [ + 24.5, + 2.96, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 2.96, + 14.3 + ], + "to": [ + -5.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 2.96, + 14.3 + ], + "to": [ + 24.5, + 4.44, + 21.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.44, + 14.596 + ], + "to": [ + -5.5, + 5.92, + 21.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 5.92, + 15.78 + ], + "to": [ + 24.5, + 7.4, + 20.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4399999999999995, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.4399999999999995 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 1.9000000000000001, + 16.2 + ], + "to": [ + 24.560000000000002, + 5.5, + 19.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/pantsir_pack.json b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_pack.json new file mode 100644 index 0000000..f88b325 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_pack.json @@ -0,0 +1,556 @@ +{ + "ambientocclusion": false, + "textures": { + "tubes6": "airdef:block/tubes6", + "pilot_olive": "airdef:block/pilot_olive", + "bare_barrel": "airdef:block/bare_barrel", + "dark_metal": "airdef:block/dark_metal", + "particle": "airdef:block/tubes6" + }, + "elements": [ + { + "from": [ + -11.0, + 2.0, + -16.0 + ], + "to": [ + -3.0, + 12.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#tubes6" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 19.0, + 2.0, + -16.0 + ], + "to": [ + 27.0, + 12.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#tubes6" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -9.5, + 12.5, + -16.0 + ], + "to": [ + -8.0, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + } + } + }, + { + "from": [ + 24.0, + 12.5, + -16.0 + ], + "to": [ + 25.5, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + } + } + }, + { + "from": [ + -6.5, + 12.5, + -16.0 + ], + "to": [ + -5.0, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + } + } + }, + { + "from": [ + 21.0, + 12.5, + -16.0 + ], + "to": [ + 22.5, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel" + } + } + }, + { + "from": [ + -10.0, + 12.0, + 2.0 + ], + "to": [ + -4.0, + 15.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 20.0, + 12.0, + 2.0 + ], + "to": [ + 26.0, + 15.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/pantsir_pack_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_pack_burnt.json new file mode 100644 index 0000000..8eae2ba --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_pack_burnt.json @@ -0,0 +1,556 @@ +{ + "ambientocclusion": false, + "textures": { + "tubes6_b": "airdef:block/tubes6_b", + "pilot_olive_b": "airdef:block/pilot_olive_b", + "bare_barrel_b": "airdef:block/bare_barrel_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "particle": "airdef:block/tubes6_b" + }, + "elements": [ + { + "from": [ + -11.0, + 2.0, + -16.0 + ], + "to": [ + -3.0, + 12.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#tubes6_b" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 19.0, + 2.0, + -16.0 + ], + "to": [ + 27.0, + 12.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#tubes6_b" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -9.5, + 12.5, + -16.0 + ], + "to": [ + -8.0, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + } + } + }, + { + "from": [ + 24.0, + 12.5, + -16.0 + ], + "to": [ + 25.5, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + } + } + }, + { + "from": [ + -6.5, + 12.5, + -16.0 + ], + "to": [ + -5.0, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + } + } + }, + { + "from": [ + 21.0, + 12.5, + -16.0 + ], + "to": [ + 22.5, + 14.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.5 + ], + "texture": "#bare_barrel_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#bare_barrel_b" + } + } + }, + { + "from": [ + -10.0, + 12.0, + 2.0 + ], + "to": [ + -4.0, + 15.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 20.0, + 12.0, + 2.0 + ], + "to": [ + 26.0, + 15.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/pantsir_truck.json b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_truck.json new file mode 100644 index 0000000..e65d73b --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_truck.json @@ -0,0 +1,4298 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -5.0, + 8.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.5 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 16.5, + 5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 5.0 + ], + "to": [ + 19.0, + 12.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -5.0, + 8.0, + 28.0 + ], + "to": [ + 21.0, + 11.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -7.0, + 8.0, + 6.0 + ], + "to": [ + -5.0, + 11.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 21.0, + 8.0, + 6.0 + ], + "to": [ + 23.0, + 11.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -3.9 + ], + "to": [ + -5.5, + 1.6, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -3.9 + ], + "to": [ + 24.5, + 1.6, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.18 + ], + "to": [ + -5.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.18 + ], + "to": [ + 24.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -5.5 + ], + "to": [ + -5.5, + 4.8, + 2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -5.5 + ], + "to": [ + 24.5, + 4.8, + 2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.18 + ], + "to": [ + -5.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.18 + ], + "to": [ + 24.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -3.9 + ], + "to": [ + -5.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -3.9 + ], + "to": [ + 24.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.3 + ], + "to": [ + -8.5, + 5.8, + 0.30000000000000004 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.3 + ], + "to": [ + 24.560000000000002, + 5.8, + 0.30000000000000004 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/pantsir_truck_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_truck_burnt.json new file mode 100644 index 0000000..f20bc86 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_truck_burnt.json @@ -0,0 +1,3006 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "glass_cab_b": "airdef:block/glass_cab_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -5.0, + 8.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.5 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 16.5, + 5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.5 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 5.0 + ], + "to": [ + 19.0, + 12.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -5.0, + 8.0, + 28.0 + ], + "to": [ + 21.0, + 11.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -7.0, + 8.0, + 6.0 + ], + "to": [ + -5.0, + 11.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 21.0, + 8.0, + 6.0 + ], + "to": [ + 23.0, + 11.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -3.9 + ], + "to": [ + -5.5, + 1.6, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.18 + ], + "to": [ + -5.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.18 + ], + "to": [ + 24.5, + 3.2, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -5.5 + ], + "to": [ + -5.5, + 4.8, + 2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.18 + ], + "to": [ + -5.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.18 + ], + "to": [ + 24.5, + 6.4, + 2.18 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -3.9 + ], + "to": [ + -5.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -3.9 + ], + "to": [ + 24.5, + 8.0, + 0.8999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.8, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.8 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.3 + ], + "to": [ + 24.560000000000002, + 5.8, + 0.30000000000000004 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/pantsir_turret.json b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_turret.json new file mode 100644 index 0000000..24cda16 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_turret.json @@ -0,0 +1,557 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "radar_face": "airdef:block/radar_face", + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "bare_lamp": "airdef:block/bare_lamp", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -2.0, + 0.0, + -5.0 + ], + "to": [ + 18.0, + 7.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 0.0, + 7.0, + -4.0 + ], + "to": [ + 16.0, + 10.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 1.0, + 1.0, + -11.0 + ], + "to": [ + 15.0, + 11.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#radar_face" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#radar_face" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 10.0 + ], + "texture": "#radar_face" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 10.0 + ], + "texture": "#radar_face" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 5.0 + ], + "texture": "#radar_face" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 5.0 + ], + "texture": "#radar_face" + } + } + }, + { + "from": [ + 2.0, + 1.0, + -6.0 + ], + "to": [ + 14.0, + 11.0, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 7.0, + 10.0, + 4.0 + ], + "to": [ + 9.0, + 17.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 1.0, + 10.0, + 8.0 + ], + "to": [ + 15.0, + 12.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 5.0, + 10.0, + -4.0 + ], + "to": [ + 11.0, + 13.0, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 4.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 6.0, + 10.5, + -4.4 + ], + "to": [ + 10.0, + 12.5, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 2.0 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 2.0 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.40000000000000036, + 2.0 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.40000000000000036, + 2.0 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 0.40000000000000036 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 0.40000000000000036 + ], + "texture": "#bare_lamp" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/pantsir_turret_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_turret_burnt.json new file mode 100644 index 0000000..53e4ed8 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/pantsir_turret_burnt.json @@ -0,0 +1,488 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "radar_face_b": "airdef:block/radar_face_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -2.0, + 0.0, + -5.0 + ], + "to": [ + 18.0, + 7.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 0.0, + 7.0, + -4.0 + ], + "to": [ + 16.0, + 10.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 1.0, + 1.0, + -11.0 + ], + "to": [ + 15.0, + 11.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#radar_face_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 10.0 + ], + "texture": "#radar_face_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 10.0 + ], + "texture": "#radar_face_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 10.0 + ], + "texture": "#radar_face_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 5.0 + ], + "texture": "#radar_face_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 5.0 + ], + "texture": "#radar_face_b" + } + } + }, + { + "from": [ + 2.0, + 1.0, + -6.0 + ], + "to": [ + 14.0, + 11.0, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 7.0, + 10.0, + 4.0 + ], + "to": [ + 9.0, + 17.0, + 6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 7.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 1.0, + 10.0, + 8.0 + ], + "to": [ + 15.0, + 12.0, + 16.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 8.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 5.0, + 10.0, + -4.0 + ], + "to": [ + 11.0, + 13.0, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 4.0 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/patriot_trailer.json b/airdef-mod/source-assets/assets/airdef/models/block/patriot_trailer.json new file mode 100644 index 0000000..d2d615b --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/patriot_trailer.json @@ -0,0 +1,2053 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato": "airdef:block/pilot_nato", + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "particle": "airdef:block/pilot_nato" + }, + "elements": [ + { + "from": [ + -6.0, + 6.0, + -16.0 + ], + "to": [ + 22.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -12.0 + ], + "to": [ + 20.0, + 11.0, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -1.0, + 11.0, + 14.0 + ], + "to": [ + 17.0, + 16.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -7.0, + 3.0, + -16.0 + ], + "to": [ + 23.0, + 7.0, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -8.0, + 0.0, + -6.0 + ], + "to": [ + -6.0, + 6.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 22.0, + 0.0, + -6.0 + ], + "to": [ + 24.0, + 6.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -9.0, + 0.0, + 16.78 + ], + "to": [ + -6.0, + 1.48, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 0.0, + 16.78 + ], + "to": [ + 25.0, + 1.48, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 1.48, + 15.596 + ], + "to": [ + -6.0, + 2.96, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 15.596 + ], + "to": [ + 25.0, + 2.96, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 15.3 + ], + "to": [ + -6.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 2.96, + 15.3 + ], + "to": [ + 25.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 15.596 + ], + "to": [ + -6.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 4.44, + 15.596 + ], + "to": [ + 25.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 16.78 + ], + "to": [ + -6.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 16.78 + ], + "to": [ + 25.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 17.2 + ], + "to": [ + -9.0, + 5.5, + 20.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 25.0, + 1.9000000000000001, + 17.2 + ], + "to": [ + 25.060000000000002, + 5.5, + 20.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -9.0, + 0.0, + 24.78 + ], + "to": [ + -6.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 0.0, + 24.78 + ], + "to": [ + 25.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 1.48, + 23.596 + ], + "to": [ + -6.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 23.596 + ], + "to": [ + 25.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 23.3 + ], + "to": [ + -6.0, + 4.44, + 30.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 2.96, + 23.3 + ], + "to": [ + 25.0, + 4.44, + 30.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 23.596 + ], + "to": [ + -6.0, + 5.92, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 4.44, + 23.596 + ], + "to": [ + 25.0, + 5.92, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 24.78 + ], + "to": [ + -6.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 24.78 + ], + "to": [ + 25.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 25.2 + ], + "to": [ + -9.0, + 5.5, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 25.0, + 1.9000000000000001, + 25.2 + ], + "to": [ + 25.060000000000002, + 5.5, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/patriot_trailer_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/patriot_trailer_burnt.json new file mode 100644 index 0000000..907e11f --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/patriot_trailer_burnt.json @@ -0,0 +1,1577 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato_b": "airdef:block/pilot_nato_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "particle": "airdef:block/pilot_nato_b" + }, + "elements": [ + { + "from": [ + -6.0, + 6.0, + -16.0 + ], + "to": [ + 22.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_nato_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -12.0 + ], + "to": [ + 20.0, + 11.0, + 24.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -1.0, + 11.0, + 14.0 + ], + "to": [ + 17.0, + 16.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -7.0, + 3.0, + -16.0 + ], + "to": [ + 23.0, + 7.0, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -8.0, + 0.0, + -6.0 + ], + "to": [ + -6.0, + 6.0, + -4.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + -9.0, + 0.0, + 16.78 + ], + "to": [ + -6.0, + 1.48, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 15.596 + ], + "to": [ + 25.0, + 2.96, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 15.3 + ], + "to": [ + -6.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 2.96, + 15.3 + ], + "to": [ + 25.0, + 4.44, + 22.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 15.596 + ], + "to": [ + -6.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 4.44, + 15.596 + ], + "to": [ + 25.0, + 5.92, + 22.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 16.78 + ], + "to": [ + -6.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 16.78 + ], + "to": [ + 25.0, + 7.4, + 21.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 17.2 + ], + "to": [ + -9.0, + 5.5, + 20.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -9.0, + 0.0, + 24.78 + ], + "to": [ + -6.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 0.0, + 24.78 + ], + "to": [ + 25.0, + 1.48, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 1.48, + 23.596 + ], + "to": [ + -6.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 1.48, + 23.596 + ], + "to": [ + 25.0, + 2.96, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.48 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.48 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 2.96, + 23.3 + ], + "to": [ + -6.0, + 4.44, + 30.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.399999999999999, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.399999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 4.44, + 23.596 + ], + "to": [ + -6.0, + 5.92, + 30.404 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.808, + 1.4799999999999995 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 6.808 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.0, + 5.92, + 24.78 + ], + "to": [ + -6.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 5.92, + 24.78 + ], + "to": [ + 25.0, + 7.4, + 29.22 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.439999999999998, + 1.4800000000000004 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.439999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -9.06, + 1.9000000000000001, + 25.2 + ], + "to": [ + -9.0, + 5.5, + 28.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/patriot_truck.json b/airdef-mod/source-assets/assets/airdef/models/block/patriot_truck.json new file mode 100644 index 0000000..47c3894 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/patriot_truck.json @@ -0,0 +1,4299 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_nato": "airdef:block/pilot_nato", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", + "metal": "airdef:block/metal", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "particle": "airdef:block/pilot_nato" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -5.0, + 8.0, + -16.0 + ], + "to": [ + 21.0, + 18.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.5, + 18.0, + -15.5 + ], + "to": [ + 20.5, + 22.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -4.0, + 18.0, + -15.99 + ], + "to": [ + 7.5, + 21.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 18.0, + -15.99 + ], + "to": [ + 20.0, + 21.3, + -15.82 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 18.0, + -14.5 + ], + "to": [ + -4.99, + 21.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 18.0, + -14.5 + ], + "to": [ + 21.08, + 21.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.5, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.5 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.5, + 9.0, + -16.0 + ], + "to": [ + -1.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.5, + 9.0, + -16.0 + ], + "to": [ + 20.5, + 10.2, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.1999999999999993 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 15.0, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_nato" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_nato" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 6.0 + ], + "texture": "#pilot_nato" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 6.0 + ], + "texture": "#pilot_nato" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_nato" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_nato" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 8.0 + ], + "to": [ + 19.0, + 11.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 2.0, + 11.0, + 18.0 + ], + "to": [ + 14.0, + 12.0, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 1.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 8.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 8.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + -7.0, + 9.0, + 1.0 + ], + "to": [ + -5.0, + 12.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 21.0, + 9.0, + 1.0 + ], + "to": [ + 23.0, + 12.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/radar_truck.json b/airdef-mod/source-assets/assets/airdef/models/block/radar_truck.json new file mode 100644 index 0000000..faccce7 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/radar_truck.json @@ -0,0 +1,4231 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "metal": "airdef:block/metal", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.0 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 12.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.0, + 12.0, + 4.0 + ], + "to": [ + 19.0, + 14.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -7.0, + 0.0, + 23.0 + ], + "to": [ + -5.0, + 6.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ + 23.0, + 6.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/radar_truck_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/radar_truck_burnt.json new file mode 100644 index 0000000..23c17aa --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/radar_truck_burnt.json @@ -0,0 +1,3143 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "glass_cab_b": "airdef:block/glass_cab_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -5.0 + ], + "to": [ + 20.0, + 12.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.0, + 12.0, + 4.0 + ], + "to": [ + 19.0, + 14.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 21.0, + 0.0, + 23.0 + ], + "to": [ + 23.0, + 6.0, + 25.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.0, + 2.0 + ], + "texture": "#metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/s400_chassis.json b/airdef-mod/source-assets/assets/airdef/models/block/s400_chassis.json new file mode 100644 index 0000000..8ac910b --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/s400_chassis.json @@ -0,0 +1,4502 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "bare_lamp": "airdef:block/bare_lamp", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -2.0, + 5.0, + -15.0 + ], + "to": [ + 18.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.0, + 9.0, + -16.0 + ], + "to": [ + 19.0, + 15.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.5, + 15.0, + -15.0 + ], + "to": [ + 18.5, + 19.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 15.2, + -15.95 + ], + "to": [ + 7.5, + 18.0, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 15.2, + -15.95 + ], + "to": [ + 18.0, + 18.0, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -3.06, + 14.0, + -14.0 + ], + "to": [ + -2.95, + 17.5, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 18.95, + 14.0, + -14.0 + ], + "to": [ + 19.06, + 17.5, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -2.5, + 19.0, + -15.0 + ], + "to": [ + 18.5, + 19.4, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 9.0, + -6.5 + ], + "to": [ + 18.0, + 11.0, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 0.0, + 9.1, + 7.0 + ], + "to": [ + 16.0, + 10.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 28.0 + ], + "to": [ + 19.0, + 12.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.5, + 7.0, + -16.0 + ], + "to": [ + 19.5, + 8.0, + -15.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.5999999999999996, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.5999999999999996, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5999999999999996 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -6.0, + 0.0, + -12.959999999999999 + ], + "to": [ + -3.0, + 1.6400000000000001, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 0.0, + -12.959999999999999 + ], + "to": [ + 22.0, + 1.6400000000000001, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + -14.272 + ], + "to": [ + -3.0, + 3.28, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + -14.272 + ], + "to": [ + 22.0, + 3.28, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -14.6 + ], + "to": [ + -3.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -14.6 + ], + "to": [ + 22.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -14.272 + ], + "to": [ + -3.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -14.272 + ], + "to": [ + 22.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + -3.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + 22.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + -12.7 + ], + "to": [ + -6.0, + 6.3, + -8.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + -12.7 + ], + "to": [ + 22.05, + 6.3, + -8.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -6.0, + 0.0, + -3.9599999999999995 + ], + "to": [ + -3.0, + 1.6400000000000001, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 0.0, + -3.9599999999999995 + ], + "to": [ + 22.0, + 1.6400000000000001, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + -5.272 + ], + "to": [ + -3.0, + 3.28, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + -5.272 + ], + "to": [ + 22.0, + 3.28, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -5.6 + ], + "to": [ + -3.0, + 4.92, + 2.5999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -5.6 + ], + "to": [ + 22.0, + 4.92, + 2.5999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -5.272 + ], + "to": [ + -3.0, + 6.559999999999999, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -5.272 + ], + "to": [ + 22.0, + 6.559999999999999, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -3.9599999999999995 + ], + "to": [ + -3.0, + 8.2, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -3.9599999999999995 + ], + "to": [ + 22.0, + 8.2, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + -3.7 + ], + "to": [ + -6.0, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + -3.7 + ], + "to": [ + 22.05, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -6.0, + 0.0, + 14.040000000000001 + ], + "to": [ + -3.0, + 1.6400000000000001, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 0.0, + 14.040000000000001 + ], + "to": [ + 22.0, + 1.6400000000000001, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 12.728 + ], + "to": [ + -3.0, + 3.28, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 12.728 + ], + "to": [ + 22.0, + 3.28, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 12.4 + ], + "to": [ + -3.0, + 4.92, + 20.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 12.4 + ], + "to": [ + 22.0, + 4.92, + 20.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 12.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + 12.728 + ], + "to": [ + 22.0, + 6.559999999999999, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + -3.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + 22.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + 14.3 + ], + "to": [ + -6.0, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 14.3 + ], + "to": [ + 22.05, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -6.0, + 0.0, + 23.04 + ], + "to": [ + -3.0, + 1.6400000000000001, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 0.0, + 23.04 + ], + "to": [ + 22.0, + 1.6400000000000001, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + -3.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + 22.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 21.4 + ], + "to": [ + -3.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 21.4 + ], + "to": [ + 22.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 21.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 4.92, + 21.728 + ], + "to": [ + 22.0, + 6.559999999999999, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 23.04 + ], + "to": [ + -3.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 23.04 + ], + "to": [ + 22.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + 23.3 + ], + "to": [ + -6.0, + 6.3, + 27.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 23.3 + ], + "to": [ + 22.05, + 6.3, + 27.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -5.0, + 7.0, + 4.0 + ], + "to": [ + -2.5, + 10.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 18.5, + 7.0, + 4.0 + ], + "to": [ + 21.0, + 10.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.5, + 10.0, + -14.0 + ], + "to": [ + -3.0, + 10.8, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 19.0, + 10.0, + -14.0 + ], + "to": [ + 20.5, + 10.8, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -4.0, + 12.0, + -8.2 + ], + "to": [ + -3.0, + 12.6, + -7.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.39999999999999947, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.39999999999999947, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 0.39999999999999947 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 0.39999999999999947 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 19.0, + 12.0, + -8.2 + ], + "to": [ + 20.0, + 12.6, + -7.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.39999999999999947, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.39999999999999947, + 0.5999999999999996 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 0.39999999999999947 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 0.39999999999999947 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/s400_chassis_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/s400_chassis_burnt.json new file mode 100644 index 0000000..1a514c1 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/s400_chassis_burnt.json @@ -0,0 +1,3346 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "glass_cab_b": "airdef:block/glass_cab_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -2.0, + 5.0, + -15.0 + ], + "to": [ + 18.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.0, + 9.0, + -16.0 + ], + "to": [ + 19.0, + 15.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 6.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.5, + 15.0, + -15.0 + ], + "to": [ + 18.5, + 19.0, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 8.5, + 15.2, + -15.95 + ], + "to": [ + 18.0, + 18.0, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 9.5, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 2.8000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 9.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + 18.95, + 14.0, + -14.0 + ], + "to": [ + 19.06, + 17.5, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.5 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -2.5, + 19.0, + -15.0 + ], + "to": [ + 18.5, + 19.4, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 0.3999999999999986 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.0, + 9.0, + -6.5 + ], + "to": [ + 18.0, + 11.0, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 13.5, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 13.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 0.0, + 9.1, + 7.0 + ], + "to": [ + 16.0, + 10.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.9000000000000004 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -3.0, + 9.0, + 28.0 + ], + "to": [ + 19.0, + 12.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.5, + 7.0, + -16.0 + ], + "to": [ + 19.5, + 8.0, + -15.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5999999999999996, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5999999999999996, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.5999999999999996 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.5999999999999996 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 19.0, + 0.0, + -12.959999999999999 + ], + "to": [ + 22.0, + 1.6400000000000001, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -14.6 + ], + "to": [ + -3.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -14.6 + ], + "to": [ + 22.0, + 4.92, + -6.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -14.272 + ], + "to": [ + -3.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -14.272 + ], + "to": [ + 22.0, + 6.559999999999999, + -6.728 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + -3.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -12.959999999999999 + ], + "to": [ + 22.0, + 8.2, + -8.040000000000001 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999998, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999998 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + -5.6 + ], + "to": [ + -3.0, + 4.92, + 2.5999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + -5.6 + ], + "to": [ + 22.0, + 4.92, + 2.5999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.2, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.2 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + -5.272 + ], + "to": [ + -3.0, + 6.559999999999999, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 4.92, + -5.272 + ], + "to": [ + 22.0, + 6.559999999999999, + 2.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.5440000000000005, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.5440000000000005 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + -3.9599999999999995 + ], + "to": [ + -3.0, + 8.2, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + -3.9599999999999995 + ], + "to": [ + 22.0, + 8.2, + 0.9599999999999995 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.919999999999999, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.919999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + -3.7 + ], + "to": [ + -6.0, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + -3.7 + ], + "to": [ + 22.05, + 6.3, + 0.7000000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.4, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 19.0, + 0.0, + 14.040000000000001 + ], + "to": [ + 22.0, + 1.6400000000000001, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 12.728 + ], + "to": [ + -3.0, + 3.28, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 12.728 + ], + "to": [ + 22.0, + 3.28, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 12.4 + ], + "to": [ + -3.0, + 4.92, + 20.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 12.4 + ], + "to": [ + 22.0, + 4.92, + 20.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000001, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 12.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 20.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999999, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + -3.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 14.040000000000001 + ], + "to": [ + 22.0, + 8.2, + 18.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.92, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.92 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.05, + 1.8999999999999995, + 14.3 + ], + "to": [ + -6.0, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 14.3 + ], + "to": [ + 22.05, + 6.3, + 18.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -6.0, + 0.0, + 23.04 + ], + "to": [ + -3.0, + 1.6400000000000001, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + -3.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 1.6400000000000001, + 21.728 + ], + "to": [ + 22.0, + 3.28, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999997 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 3.28, + 21.4 + ], + "to": [ + -3.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 3.28, + 21.4 + ], + "to": [ + 22.0, + 4.92, + 29.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.200000000000003, + 1.6400000000000001 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.200000000000003 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 4.92, + 21.728 + ], + "to": [ + -3.0, + 6.559999999999999, + 29.272 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.543999999999997, + 1.6399999999999988 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.543999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -6.0, + 6.559999999999999, + 23.04 + ], + "to": [ + -3.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 19.0, + 6.559999999999999, + 23.04 + ], + "to": [ + 22.0, + 8.2, + 27.96 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.920000000000002, + 1.6400000000000006 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.920000000000002 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 22.0, + 1.8999999999999995, + 23.3 + ], + "to": [ + 22.05, + 6.3, + 27.7 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 4.4 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.399999999999999, + 4.4 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 4.399999999999999 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -5.0, + 7.0, + 4.0 + ], + "to": [ + -2.5, + 10.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 18.5, + 7.0, + 4.0 + ], + "to": [ + 21.0, + 10.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 2.5, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 2.5, + 8.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -4.5, + 10.0, + -14.0 + ], + "to": [ + -3.0, + 10.8, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 19.0, + 10.0, + -14.0 + ], + "to": [ + 20.5, + 10.8, + -12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 0.8000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 2.0 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 19.0, + 12.0, + -8.2 + ], + "to": [ + 20.0, + 12.6, + -7.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 0.5999999999999996 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 0.5999999999999996 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.39999999999999947, + 0.5999999999999996 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.39999999999999947, + 0.5999999999999996 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 0.39999999999999947 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 0.39999999999999947 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/s400_tubes.json b/airdef-mod/source-assets/assets/airdef/models/block/s400_tubes.json new file mode 100644 index 0000000..8c1a389 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/s400_tubes.json @@ -0,0 +1,1803 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap": "airdef:block/tube_cap", + "tube": "airdef:block/tube", + "dark_metal": "airdef:block/dark_metal", + "particle": "airdef:block/tube_cap" + }, + "elements": [ + { + "from": [ + 1.9, + 0.0, + -16.0 + ], + "to": [ + 6.1, + 1.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.7799999999999998, + 1.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 2.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.5, + 2.8, + -16.0 + ], + "to": [ + 7.5, + 4.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.7799999999999998, + 4.2, + -16.0 + ], + "to": [ + 7.220000000000001, + 5.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 1.9, + 5.6, + -16.0 + ], + "to": [ + 6.1, + 7.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.35, + 2.6, + -1.0 + ], + "to": [ + 7.65, + 4.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.35, + 2.6, + 26.0 + ], + "to": [ + 7.65, + 4.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 1.9, + 8.0, + -16.0 + ], + "to": [ + 6.1, + 9.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.7799999999999998, + 9.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 10.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.5, + 10.8, + -16.0 + ], + "to": [ + 7.5, + 12.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.7799999999999998, + 12.2, + -16.0 + ], + "to": [ + 7.220000000000001, + 13.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 1.9, + 13.6, + -16.0 + ], + "to": [ + 6.1, + 15.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 0.35, + 10.6, + -1.0 + ], + "to": [ + 7.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 0.35, + 10.6, + 26.0 + ], + "to": [ + 7.65, + 12.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 9.9, + 0.0, + -16.0 + ], + "to": [ + 14.1, + 1.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 1.4, + -16.0 + ], + "to": [ + 15.22, + 2.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.5, + 2.8, + -16.0 + ], + "to": [ + 15.5, + 4.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 4.2, + -16.0 + ], + "to": [ + 15.22, + 5.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 9.9, + 5.6, + -16.0 + ], + "to": [ + 14.1, + 7.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.35, + 2.6, + -1.0 + ], + "to": [ + 15.65, + 4.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 8.35, + 2.6, + 26.0 + ], + "to": [ + 15.65, + 4.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 9.9, + 8.0, + -16.0 + ], + "to": [ + 14.1, + 9.4, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 9.4, + -16.0 + ], + "to": [ + 15.22, + 10.8, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.5, + 10.8, + -16.0 + ], + "to": [ + 15.5, + 12.2, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.78, + 12.2, + -16.0 + ], + "to": [ + 15.22, + 13.6, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 9.9, + 13.6, + -16.0 + ], + "to": [ + 14.1, + 15.0, + 32.0 + ], + "faces": { + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 8.35, + 10.6, + -1.0 + ], + "to": [ + 15.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 8.35, + 10.6, + 26.0 + ], + "to": [ + 15.65, + 12.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -0.5, + 0.0, + 26.0 + ], + "to": [ + 16.5, + 1.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/s400_tubes_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/s400_tubes_burnt.json new file mode 100644 index 0000000..f7b62d7 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/s400_tubes_burnt.json @@ -0,0 +1,1779 @@ +{ + "ambientocclusion": false, + "textures": { + "tube_cap_b": "airdef:block/tube_cap_b", + "tube_b": "airdef:block/tube_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "particle": "airdef:block/tube_cap_b" + }, + "elements": [ + { + "from": [ + 1.9, + 0.0, + -16.0 + ], + "to": [ + 6.1, + 1.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.7799999999999998, + 1.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 2.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.5, + 2.8, + -16.0 + ], + "to": [ + 7.5, + 4.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.7799999999999998, + 4.2, + -16.0 + ], + "to": [ + 7.220000000000001, + 5.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 1.9, + 5.6, + -16.0 + ], + "to": [ + 6.1, + 7.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.35, + 2.6, + 26.0 + ], + "to": [ + 7.65, + 4.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 1.9, + 8.0, + -16.0 + ], + "to": [ + 6.1, + 9.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.7799999999999998, + 9.4, + -16.0 + ], + "to": [ + 7.220000000000001, + 10.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.5, + 10.8, + -16.0 + ], + "to": [ + 7.5, + 12.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.7799999999999998, + 12.2, + -16.0 + ], + "to": [ + 7.220000000000001, + 13.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 1.9, + 13.6, + -16.0 + ], + "to": [ + 6.1, + 15.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 0.35, + 10.6, + -1.0 + ], + "to": [ + 7.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 0.35, + 10.6, + 26.0 + ], + "to": [ + 7.65, + 12.4, + 27.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 9.9, + 0.0, + -16.0 + ], + "to": [ + 14.1, + 1.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 1.4, + -16.0 + ], + "to": [ + 15.22, + 2.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.5, + 2.8, + -16.0 + ], + "to": [ + 15.5, + 4.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 4.2, + -16.0 + ], + "to": [ + 15.22, + 5.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.3999999999999995 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999995 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 9.9, + 5.6, + -16.0 + ], + "to": [ + 14.1, + 7.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.35, + 2.6, + -1.0 + ], + "to": [ + 15.65, + 4.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000003 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 9.9, + 8.0, + -16.0 + ], + "to": [ + 14.1, + 9.4, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 9.4, + -16.0 + ], + "to": [ + 15.22, + 10.8, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.5, + 10.8, + -16.0 + ], + "to": [ + 15.5, + 12.2, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.3999999999999986 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.3999999999999986 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.78, + 12.2, + -16.0 + ], + "to": [ + 15.22, + 13.6, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.440000000000001, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.440000000000001, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 9.9, + 13.6, + -16.0 + ], + "to": [ + 14.1, + 15.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.199999999999999, + 1.4000000000000004 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.4000000000000004 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.199999999999999, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 8.35, + 10.6, + -1.0 + ], + "to": [ + 15.65, + 12.4, + 0.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 1.8000000000000007 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.300000000000001, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -0.5, + 0.0, + 26.0 + ], + "to": [ + 16.5, + 1.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/searchlight.json b/airdef-mod/source-assets/assets/airdef/models/block/searchlight.json new file mode 100644 index 0000000..6988a0d --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/searchlight.json @@ -0,0 +1,350 @@ +{ + "ambientocclusion": false, + "textures": { + "dark_metal": "airdef:block/dark_metal", + "metal": "airdef:block/metal", + "particle": "airdef:block/dark_metal" + }, + "elements": [ + { + "from": [ + 1.0, + 0.0, + 1.0 + ], + "to": [ + 15.0, + 2.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 3.0, + 2.0, + 3.0 + ], + "to": [ + 13.0, + 4.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 6.0, + 4.0, + 6.0 + ], + "to": [ + 10.0, + 12.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 2.0, + 8.0, + 7.0 + ], + "to": [ + 3.0, + 14.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 13.0, + 8.0, + 7.0 + ], + "to": [ + 14.0, + 14.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/searchlight_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/searchlight_burnt.json new file mode 100644 index 0000000..fb66c2f --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/searchlight_burnt.json @@ -0,0 +1,282 @@ +{ + "ambientocclusion": false, + "textures": { + "dark_metal_b": "airdef:block/dark_metal_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/dark_metal_b" + }, + "elements": [ + { + "from": [ + 1.0, + 0.0, + 1.0 + ], + "to": [ + 15.0, + 2.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 14.0, + 2.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 14.0, + 14.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 3.0, + 2.0, + 3.0 + ], + "to": [ + 13.0, + 4.0, + 13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 6.0, + 4.0, + 6.0 + ], + "to": [ + 10.0, + 12.0, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 8.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 4.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 2.0, + 8.0, + 7.0 + ], + "to": [ + 3.0, + 14.0, + 9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 2.0, + 6.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 2.0 + ], + "texture": "#metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/searchlight_head.json b/airdef-mod/source-assets/assets/airdef/models/block/searchlight_head.json new file mode 100644 index 0000000..80f8e2a --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/searchlight_head.json @@ -0,0 +1,283 @@ +{ + "ambientocclusion": false, + "textures": { + "dark_metal": "airdef:block/dark_metal", + "lamp": "airdef:block/lamp", + "metal": "airdef:block/metal", + "particle": "airdef:block/dark_metal" + }, + "elements": [ + { + "from": [ + 3.0, + 3.0, + -2.0 + ], + "to": [ + 13.0, + 13.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 4.0, + 2.0, + 0.0 + ], + "to": [ + 12.0, + 14.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 3.5, + 3.5, + -2.5 + ], + "to": [ + 12.5, + 12.5, + -2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 9.0 + ], + "texture": "#lamp" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 9.0 + ], + "texture": "#lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#lamp" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 0.5 + ], + "texture": "#lamp" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 0.5 + ], + "texture": "#lamp" + } + } + }, + { + "from": [ + 5.0, + 5.0, + 14.0 + ], + "to": [ + 11.0, + 11.0, + 15.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 6.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/searchlight_head_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/searchlight_head_burnt.json new file mode 100644 index 0000000..2a6fcef --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/searchlight_head_burnt.json @@ -0,0 +1,214 @@ +{ + "ambientocclusion": false, + "textures": { + "dark_metal_b": "airdef:block/dark_metal_b", + "lamp_b": "airdef:block/lamp_b", + "particle": "airdef:block/dark_metal_b" + }, + "elements": [ + { + "from": [ + 3.0, + 3.0, + -2.0 + ], + "to": [ + 13.0, + 13.0, + 14.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 10.0, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 10.0, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 10.0, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 4.0, + 2.0, + 0.0 + ], + "to": [ + 12.0, + 14.0, + 12.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 12.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 8.0, + 12.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 3.5, + 3.5, + -2.5 + ], + "to": [ + 12.5, + 12.5, + -2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 9.0 + ], + "texture": "#lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 9.0 + ], + "texture": "#lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.5, + 9.0 + ], + "texture": "#lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 0.5 + ], + "texture": "#lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 0.5 + ], + "texture": "#lamp_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/shahed.json b/airdef-mod/source-assets/assets/airdef/models/block/shahed.json new file mode 100644 index 0000000..caeeef5 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/shahed.json @@ -0,0 +1,3127 @@ +{ + "ambientocclusion": false, + "textures": { + "shahed_nose": "airdef:block/shahed_nose", + "shahed": "airdef:block/shahed", + "kh101_belly": "airdef:block/kh101_belly", + "dark_metal": "airdef:block/dark_metal", + "bare_nozzle": "airdef:block/bare_nozzle", + "particle": "airdef:block/shahed_nose" + }, + "elements": [ + { + "from": [ + 7.114403477412995, + 7.114403477412995, + -16.0 + ], + "to": [ + 8.885596522587004, + 8.885596522587004, + -14.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.75, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.75, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.75 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.75 + ], + "texture": "#shahed_nose" + } + } + }, + { + "from": [ + 7.114403477412995, + 7.114403477412995, + -15.97 + ], + "to": [ + 8.885596522587004, + 8.885596522587004, + -14.28 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 1.7711930451740097 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 1.7711930451740097, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -15.125 + ] + } + }, + { + "from": [ + 6.557290514119103, + 6.557290514119103, + -14.25 + ], + "to": [ + 9.442709485880897, + 9.442709485880897, + -12.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.885418971761794, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 2.885418971761794, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.75, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.75, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 2.885418971761794, + 1.75 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 2.885418971761794, + 1.75 + ], + "texture": "#shahed_nose" + } + } + }, + { + "from": [ + 6.557290514119103, + 6.557290514119103, + -14.22 + ], + "to": [ + 9.442709485880897, + 9.442709485880897, + -12.53 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.885418971761794, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 2.885418971761794, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 2.885418971761794 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 2.885418971761794, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 2.885418971761794, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -13.375 + ] + } + }, + { + "from": [ + 6.139275607743379, + 6.139275607743379, + -12.5 + ], + "to": [ + 9.860724392256621, + 9.860724392256621, + -10.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.75, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.75, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.75 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.75 + ], + "texture": "#shahed_nose" + } + } + }, + { + "from": [ + 6.139275607743379, + 6.139275607743379, + -12.47 + ], + "to": [ + 9.860724392256621, + 9.860724392256621, + -10.78 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 3.7214487845132425 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 3.7214487845132425, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -11.625 + ] + } + }, + { + "from": [ + 5.7871960969890655, + 5.7871960969890655, + -10.75 + ], + "to": [ + 10.212803903010935, + 10.212803903010935, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.425607806021869, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 4.425607806021869, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.75, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.75, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 4.425607806021869, + 1.75 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 4.425607806021869, + 1.75 + ], + "texture": "#shahed_nose" + } + } + }, + { + "from": [ + 5.7871960969890655, + 5.7871960969890655, + -10.72 + ], + "to": [ + 10.212803903010935, + 10.212803903010935, + -9.03 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.425607806021869, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "south": { + "uv": [ + 0, + 0, + 4.425607806021869, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "east": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "west": { + "uv": [ + 0, + 0, + 1.6900000000000013, + 4.425607806021869 + ], + "texture": "#shahed_nose" + }, + "up": { + "uv": [ + 0, + 0, + 4.425607806021869, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + }, + "down": { + "uv": [ + 0, + 0, + 4.425607806021869, + 1.6900000000000013 + ], + "texture": "#shahed_nose" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + -9.875 + ] + } + }, + { + "from": [ + 5.69025, + 5.69025, + -9.0 + ], + "to": [ + 10.309750000000001, + 10.309750000000001, + 22.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.619500000000001, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 4.619500000000001, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 4.619500000000001, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 4.619500000000001, + 16 + ], + "texture": "#shahed" + } + } + }, + { + "from": [ + 5.69025, + 5.69025, + -8.97 + ], + "to": [ + 10.309750000000001, + 10.309750000000001, + 21.97 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.619500000000001, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 4.619500000000001, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.619500000000001 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 4.619500000000001, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 4.619500000000001, + 16 + ], + "texture": "#shahed" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + 6.499999999999999 + ] + } + }, + { + "from": [ + 7.25, + 7.5, + -2.0 + ], + "to": [ + 8.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 1.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 1.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 6.5, + 7.5, + -1.1785714285714286 + ], + "to": [ + 9.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 5.75, + 7.5, + -0.3571428571428572 + ], + "to": [ + 10.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 4.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 4.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 5.0, + 7.5, + 0.4642857142857144 + ], + "to": [ + 11.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 4.25, + 7.5, + 1.2857142857142856 + ], + "to": [ + 11.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 7.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 7.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 7.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 3.5, + 7.5, + 2.1071428571428568 + ], + "to": [ + 12.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 2.75, + 7.5, + 2.928571428571429 + ], + "to": [ + 13.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 10.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 10.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 10.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 10.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 2.0, + 7.5, + 3.75 + ], + "to": [ + 14.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 12.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 12.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 12.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 12.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 1.25, + 7.5, + 4.571428571428571 + ], + "to": [ + 14.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 13.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 13.5, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 13.5, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 13.5, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + 0.5, + 7.5, + 5.392857142857143 + ], + "to": [ + 15.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 15.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 15.0, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 15.0, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 15.0, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -0.25, + 7.5, + 6.2142857142857135 + ], + "to": [ + 16.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -1.0, + 7.5, + 7.0357142857142865 + ], + "to": [ + 17.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -1.75, + 7.5, + 7.857142857142858 + ], + "to": [ + 17.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -2.5, + 7.5, + 8.678571428571429 + ], + "to": [ + 18.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -3.25, + 7.5, + 9.5 + ], + "to": [ + 19.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -4.0, + 7.5, + 10.321428571428571 + ], + "to": [ + 20.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 15.678571428571429, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 15.678571428571429, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 15.678571428571429 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 15.678571428571429 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -4.75, + 7.5, + 11.142857142857142 + ], + "to": [ + 20.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 14.857142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 14.857142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 14.857142857142858 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 14.857142857142858 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -5.5, + 7.5, + 11.964285714285714 + ], + "to": [ + 21.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 14.035714285714286, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 14.035714285714286, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 14.035714285714286 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 14.035714285714286 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -6.25, + 7.5, + 12.785714285714286 + ], + "to": [ + 22.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 13.214285714285714, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 13.214285714285714, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 13.214285714285714 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 13.214285714285714 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -7.0, + 7.5, + 13.607142857142858 + ], + "to": [ + 23.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 12.392857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 12.392857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 12.392857142857142 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 12.392857142857142 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -7.75, + 7.5, + 14.428571428571427 + ], + "to": [ + 23.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 11.571428571428573, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 11.571428571428573, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 11.571428571428573 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 11.571428571428573 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -8.5, + 7.5, + 15.25 + ], + "to": [ + 24.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 10.75, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 10.75, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.75 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.75 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -9.25, + 7.5, + 16.071428571428573 + ], + "to": [ + 25.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 9.928571428571427, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 9.928571428571427, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.928571428571427 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.928571428571427 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -10.0, + 7.5, + 16.892857142857142 + ], + "to": [ + 26.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 9.107142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 9.107142857142858, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.107142857142858 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.107142857142858 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -10.75, + 7.5, + 17.714285714285715 + ], + "to": [ + 26.75, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 8.285714285714285, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 8.285714285714285, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 8.285714285714285 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 8.285714285714285 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -11.5, + 7.5, + 18.535714285714285 + ], + "to": [ + 27.5, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 7.464285714285715, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 7.464285714285715, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 7.464285714285715 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 7.464285714285715 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -12.25, + 7.5, + 19.357142857142858 + ], + "to": [ + 28.25, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 6.642857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 6.642857142857142, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 6.642857142857142 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 6.642857142857142 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -13.0, + 7.5, + 20.178571428571427 + ], + "to": [ + 29.0, + 8.3, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 5.821428571428573, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 5.821428571428573, + 0.8000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.821428571428573 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.821428571428573 + ], + "texture": "#kh101_belly" + } + } + }, + { + "from": [ + -12.8, + 7.6, + 18.0 + ], + "to": [ + -12.100000000000001, + 13.2, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 5.6 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 5.6 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 5.6 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 5.6 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#shahed" + } + } + }, + { + "from": [ + 28.1, + 7.6, + 18.0 + ], + "to": [ + 28.8, + 13.2, + 26.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 5.6 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 5.6 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 5.6 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 5.6 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 8.0 + ], + "texture": "#shahed" + } + } + }, + { + "from": [ + 6.33698, + 6.33698, + 22.0 + ], + "to": [ + 9.66302, + 9.66302, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.32604, + 3.32604 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.32604, + 3.32604 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.32604 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.32604 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.32604, + 6.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.32604, + 6.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 6.33698, + 6.33698, + 22.03 + ], + "to": [ + 9.66302, + 9.66302, + 27.97 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.32604, + 3.32604 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.32604, + 3.32604 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 5.939999999999998, + 3.32604 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 5.939999999999998, + 3.32604 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.32604, + 5.939999999999998 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.32604, + 5.939999999999998 + ], + "texture": "#dark_metal" + } + }, + "rotation": { + "angle": 45, + "axis": "z", + "origin": [ + 8.0, + 8.0, + 25.0 + ] + } + }, + { + "from": [ + 8.8, + 5.5, + 24.0 + ], + "to": [ + 9.5, + 6.6, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + 6.5, + 5.5, + 24.0 + ], + "to": [ + 7.199999999999999, + 6.6, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "south": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "east": { + "uv": [ + 0, + 0, + 4.0, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "west": { + "uv": [ + 0, + 0, + 4.0, + 1.0999999999999996 + ], + "texture": "#bare_nozzle" + }, + "up": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#bare_nozzle" + }, + "down": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 4.0 + ], + "texture": "#bare_nozzle" + } + } + }, + { + "from": [ + 6.7, + 10.5, + -3.0 + ], + "to": [ + 9.3, + 10.8, + 7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 2.6000000000000005, + 0.3000000000000007 + ], + "texture": "#shahed" + }, + "south": { + "uv": [ + 0, + 0, + 2.6000000000000005, + 0.3000000000000007 + ], + "texture": "#shahed" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 0.3000000000000007 + ], + "texture": "#shahed" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 0.3000000000000007 + ], + "texture": "#shahed" + }, + "up": { + "uv": [ + 0, + 0, + 2.6000000000000005, + 10.0 + ], + "texture": "#shahed" + }, + "down": { + "uv": [ + 0, + 0, + 2.6000000000000005, + 10.0 + ], + "texture": "#shahed" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/tor_chassis.json b/airdef-mod/source-assets/assets/airdef/models/block/tor_chassis.json new file mode 100644 index 0000000..07c4625 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/tor_chassis.json @@ -0,0 +1,3626 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "dark_metal": "airdef:block/dark_metal", + "track": "airdef:block/track", + "hub": "airdef:block/hub", + "metal": "airdef:block/metal", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -9.0, + 4.0, + -13.0 + ], + "to": [ + 25.0, + 13.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -9.0, + 4.0, + -16.0 + ], + "to": [ + 25.0, + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -11.5 + ] + } + }, + { + "from": [ + -8.0, + 13.0, + -7.0 + ], + "to": [ + 24.0, + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -13.0, + 0.0, + -15.0 + ], + "to": [ + -9.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -12.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -13.5 + ], + "to": [ + -13.02, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -13.5 + ], + "to": [ + 29.08, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -12.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -6.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -0.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -0.75 + ], + "to": [ + -13.02, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -0.75 + ], + "to": [ + 29.08, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 5.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 4.5 + ], + "to": [ + 29.08, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 5.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 11.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 11.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 10.5 + ], + "to": [ + -13.02, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 10.5 + ], + "to": [ + 29.08, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 11.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 11.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 17.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 17.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 16.5 + ], + "to": [ + 29.08, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 17.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 17.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 23.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -7.0, + 15.0, + -10.0 + ], + "to": [ + 2.0, + 15.7, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 15.0, + 14.0, + 21.0 + ], + "to": [ + 22.0, + 15.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#metal" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/tor_chassis_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/tor_chassis_burnt.json new file mode 100644 index 0000000..b417f39 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/tor_chassis_burnt.json @@ -0,0 +1,2878 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "track_b": "airdef:block/track_b", + "hub_b": "airdef:block/hub_b", + "metal_b": "airdef:block/metal_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -9.0, + 4.0, + -13.0 + ], + "to": [ + 25.0, + 13.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -9.0, + 4.0, + -16.0 + ], + "to": [ + 25.0, + 9.0, + -13.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -8.0, + 9.0, + -14.0 + ], + "to": [ + 24.0, + 13.0, + -9.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#pilot_olive_b" + } + }, + "rotation": { + "angle": -22.5, + "axis": "x", + "origin": [ + 8.0, + 11.0, + -11.5 + ] + } + }, + { + "from": [ + -8.0, + 13.0, + -7.0 + ], + "to": [ + 24.0, + 15.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 2.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -7.0, + 13.0, + 21.0 + ], + "to": [ + 23.0, + 14.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -13.0, + 0.0, + -15.0 + ], + "to": [ + -9.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + 25.0, + 0.0, + -15.0 + ], + "to": [ + 29.0, + 8.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 8.5 + ], + "texture": "#track_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 8.5 + ], + "texture": "#track_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 16 + ], + "texture": "#track_b" + } + } + }, + { + "from": [ + -13.3, + 8.5, + -14.0 + ], + "to": [ + -9.0, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 25.0, + 8.5, + -14.0 + ], + "to": [ + 29.3, + 9.5, + 31.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.300000000000001, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.300000000000001, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -12.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -13.5 + ], + "to": [ + -13.02, + 5.75, + -8.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -12.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -9.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + -6.75 + ], + "to": [ + -13.02, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -6.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -7.5 + ], + "to": [ + -13.02, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -7.5 + ], + "to": [ + 29.08, + 5.75, + -2.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -6.75 + ], + "to": [ + -13.02, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -6.75 + ], + "to": [ + 29.08, + 7.300000000000001, + -3.25 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + -0.75 + ], + "to": [ + 29.08, + 2.6500000000000004, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + -1.5 + ], + "to": [ + -13.02, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + -1.5 + ], + "to": [ + 29.08, + 5.75, + 3.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + -0.75 + ], + "to": [ + -13.02, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + -0.75 + ], + "to": [ + 29.08, + 7.300000000000001, + 2.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 5.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 5.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 4.5 + ], + "to": [ + -13.02, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 4.5 + ], + "to": [ + 29.08, + 5.75, + 9.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 5.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 11.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 10.5 + ], + "to": [ + -13.02, + 5.75, + 15.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 11.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 14.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 17.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 16.5 + ], + "to": [ + -13.02, + 5.75, + 21.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 17.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 20.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 1.1, + 23.25 + ], + "to": [ + -13.02, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 1.1, + 23.25 + ], + "to": [ + 29.08, + 2.6500000000000004, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000003 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 2.6500000000000004, + 22.5 + ], + "to": [ + -13.02, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 2.6500000000000004, + 22.5 + ], + "to": [ + 29.08, + 5.75, + 27.5 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 3.0999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 5.0 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -13.08, + 5.75, + 23.25 + ], + "to": [ + -13.02, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 29.02, + 5.75, + 23.25 + ], + "to": [ + 29.08, + 7.300000000000001, + 26.75 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.5, + 1.5500000000000007 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + -7.0, + 15.0, + -10.0 + ], + "to": [ + 2.0, + 15.7, + -5.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 9.0, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 9.0, + 5.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 15.0, + 14.0, + 21.0 + ], + "to": [ + 22.0, + 15.0, + 28.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.0, + 1.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 7.0, + 7.0 + ], + "texture": "#metal_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/tor_turret.json b/airdef-mod/source-assets/assets/airdef/models/block/tor_turret.json new file mode 100644 index 0000000..437aeaf --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/tor_turret.json @@ -0,0 +1,828 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "radar_face": "airdef:block/radar_face", + "dark_metal": "airdef:block/dark_metal", + "tube_cap": "airdef:block/tube_cap", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -4.0, + 0.0, + -6.0 + ], + "to": [ + 20.0, + 12.0, + 22.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -3.0, + 12.0, + -3.0 + ], + "to": [ + 19.0, + 16.0, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -2.0, + 2.0, + -11.0 + ], + "to": [ + 18.0, + 15.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#radar_face" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#radar_face" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 13.0 + ], + "texture": "#radar_face" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 13.0 + ], + "texture": "#radar_face" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#radar_face" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#radar_face" + } + } + }, + { + "from": [ + -3.0, + 16.0, + 2.0 + ], + "to": [ + 19.0, + 17.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -1.0, + 16.5, + 4.0 + ], + "to": [ + 3.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + -1.0, + 16.5, + 12.0 + ], + "to": [ + 3.0, + 17.5, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 4.0, + 16.5, + 4.0 + ], + "to": [ + 8.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 4.0, + 16.5, + 12.0 + ], + "to": [ + 8.0, + 17.5, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 9.0, + 16.5, + 4.0 + ], + "to": [ + 13.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 9.0, + 16.5, + 12.0 + ], + "to": [ + 13.0, + 17.5, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 14.0, + 16.5, + 4.0 + ], + "to": [ + 18.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + }, + { + "from": [ + 14.0, + 16.5, + 12.0 + ], + "to": [ + 18.0, + 17.5, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/tor_turret_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/tor_turret_burnt.json new file mode 100644 index 0000000..bb8e41a --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/tor_turret_burnt.json @@ -0,0 +1,692 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "radar_face_b": "airdef:block/radar_face_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "tube_cap_b": "airdef:block/tube_cap_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -4.0, + 0.0, + -6.0 + ], + "to": [ + 20.0, + 12.0, + 22.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 12.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -3.0, + 12.0, + -3.0 + ], + "to": [ + 19.0, + 16.0, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -2.0, + 2.0, + -11.0 + ], + "to": [ + 18.0, + 15.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#radar_face_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 13.0 + ], + "texture": "#radar_face_b" + }, + "east": { + "uv": [ + 0, + 0, + 5.0, + 13.0 + ], + "texture": "#radar_face_b" + }, + "west": { + "uv": [ + 0, + 0, + 5.0, + 13.0 + ], + "texture": "#radar_face_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#radar_face_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 5.0 + ], + "texture": "#radar_face_b" + } + } + }, + { + "from": [ + -3.0, + 16.0, + 2.0 + ], + "to": [ + 19.0, + 17.0, + 20.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -1.0, + 16.5, + 4.0 + ], + "to": [ + 3.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + -1.0, + 16.5, + 12.0 + ], + "to": [ + 3.0, + 17.5, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 4.0, + 16.5, + 4.0 + ], + "to": [ + 8.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 4.0, + 16.5, + 12.0 + ], + "to": [ + 8.0, + 17.5, + 18.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 9.0, + 16.5, + 4.0 + ], + "to": [ + 13.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + } + } + }, + { + "from": [ + 14.0, + 16.5, + 4.0 + ], + "to": [ + 18.0, + 17.5, + 10.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 4.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 1.0 + ], + "texture": "#tube_cap_b" + }, + "up": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "down": { + "uv": [ + 0, + 0, + 4.0, + 6.0 + ], + "texture": "#tube_cap_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/tzm_truck.json b/airdef-mod/source-assets/assets/airdef/models/block/tzm_truck.json new file mode 100644 index 0000000..4090a6f --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/tzm_truck.json @@ -0,0 +1,4437 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive": "airdef:block/pilot_olive", + "glass_cab": "airdef:block/glass_cab", + "dark_metal": "airdef:block/dark_metal", + "bare_lamp": "airdef:block/bare_lamp", + "tire": "airdef:block/tire", + "hub": "airdef:block/hub", + "metal": "airdef:block/metal", + "tube_cap": "airdef:block/tube_cap", + "tube": "airdef:block/tube", + "particle": "airdef:block/pilot_olive" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 8.5, + 16.0, + -15.99 + ], + "to": [ + 20.0, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + 20.990000000000002, + 16.0, + -14.0 + ], + "to": [ + 21.08, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -16.0 + ], + "to": [ + -1.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -13.4 + ], + "to": [ + -5.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -13.4 + ], + "to": [ + 24.5, + 1.6, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -14.68 + ], + "to": [ + -5.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -13.4 + ], + "to": [ + 24.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -12.8 + ], + "to": [ + -8.5, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -12.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -9.2 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -5.68 + ], + "to": [ + 24.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -5.68 + ], + "to": [ + -5.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -5.68 + ], + "to": [ + 24.5, + 6.4, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + -3.8 + ], + "to": [ + -8.5, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + -3.8 + ], + "to": [ + 24.560000000000002, + 5.8, + -0.19999999999999996 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.5999999999999996, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 14.6 + ], + "to": [ + 24.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 13.32 + ], + "to": [ + -5.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 13.32 + ], + "to": [ + 24.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 14.6 + ], + "to": [ + 24.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 15.2 + ], + "to": [ + -8.5, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 23.6 + ], + "to": [ + -5.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 4.8, + 22.32 + ], + "to": [ + 24.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire" + } + } + }, + { + "from": [ + -8.56, + 2.2, + 24.2 + ], + "to": [ + -8.5, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 24.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 27.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -3.0 + ], + "to": [ + 20.0, + 12.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + 18.0, + 12.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 6.0, + 24.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal" + }, + "south": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal" + }, + "up": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal" + }, + "down": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal" + } + } + }, + { + "from": [ + 6.0, + 20.0, + -4.0 + ], + "to": [ + 7.0, + 24.0, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal" + } + } + }, + { + "from": [ + -3.0, + 12.0, + -1.0 + ], + "to": [ + 3.0, + 18.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 5.0, + 12.0, + -1.0 + ], + "to": [ + 11.0, + 18.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube" + } + } + }, + { + "from": [ + 13.0, + 12.0, + -1.0 + ], + "to": [ + 19.0, + 18.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/models/block/tzm_truck_burnt.json b/airdef-mod/source-assets/assets/airdef/models/block/tzm_truck_burnt.json new file mode 100644 index 0000000..7c79455 --- /dev/null +++ b/airdef-mod/source-assets/assets/airdef/models/block/tzm_truck_burnt.json @@ -0,0 +1,2873 @@ +{ + "ambientocclusion": false, + "textures": { + "pilot_olive_b": "airdef:block/pilot_olive_b", + "glass_cab_b": "airdef:block/glass_cab_b", + "dark_metal_b": "airdef:block/dark_metal_b", + "bare_lamp_b": "airdef:block/bare_lamp_b", + "tire_b": "airdef:block/tire_b", + "hub_b": "airdef:block/hub_b", + "metal_b": "airdef:block/metal_b", + "tube_cap_b": "airdef:block/tube_cap_b", + "tube_b": "airdef:block/tube_b", + "particle": "airdef:block/pilot_olive_b" + }, + "elements": [ + { + "from": [ + -5.0, + 5.0, + -15.0 + ], + "to": [ + 21.0, + 9.0, + 32.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -5.0, + 9.0, + -16.0 + ], + "to": [ + 21.0, + 16.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 10.0, + 7.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 10.0 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.5, + 16.0, + -15.5 + ], + "to": [ + 20.5, + 20.0, + -6.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "east": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "west": { + "uv": [ + 0, + 0, + 9.5, + 4.0 + ], + "texture": "#pilot_olive_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 9.5 + ], + "texture": "#pilot_olive_b" + } + } + }, + { + "from": [ + -4.0, + 16.0, + -15.99 + ], + "to": [ + 7.5, + 19.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 11.5, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 3.3000000000000007 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 11.5, + 0.25 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.08, + 16.0, + -14.0 + ], + "to": [ + -4.99, + 19.0, + -8.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "east": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "west": { + "uv": [ + 0, + 0, + 6.0, + 3.0 + ], + "texture": "#glass_cab_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 6.0 + ], + "texture": "#glass_cab_b" + } + } + }, + { + "from": [ + -5.5, + 7.0, + -16.0 + ], + "to": [ + 21.5, + 8.0, + -15.3 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.6999999999999993, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 0.6999999999999993 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 17.0, + 9.0, + -16.0 + ], + "to": [ + 20.0, + 10.3, + -15.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "east": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "west": { + "uv": [ + 0, + 0, + 0.25, + 1.3000000000000007 + ], + "texture": "#bare_lamp_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 0.25 + ], + "texture": "#bare_lamp_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + -14.68 + ], + "to": [ + 24.5, + 3.2, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -15.0 + ], + "to": [ + -5.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -15.0 + ], + "to": [ + 24.5, + 4.8, + -7.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + -14.68 + ], + "to": [ + -5.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 4.8, + -14.68 + ], + "to": [ + 24.5, + 6.4, + -7.32 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -13.4 + ], + "to": [ + -5.5, + 8.0, + -8.6 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + -4.4 + ], + "to": [ + -5.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + -4.4 + ], + "to": [ + 24.5, + 1.6, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + -5.68 + ], + "to": [ + -5.5, + 3.2, + 1.6800000000000002 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + -6.0 + ], + "to": [ + -5.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + -6.0 + ], + "to": [ + 24.5, + 4.8, + 2.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + -4.4 + ], + "to": [ + -5.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + -4.4 + ], + "to": [ + 24.5, + 8.0, + 0.3999999999999999 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.800000000000001, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.800000000000001 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 0.0, + 14.6 + ], + "to": [ + -5.5, + 1.6, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 13.32 + ], + "to": [ + 24.5, + 3.2, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 13.0 + ], + "to": [ + -5.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 13.0 + ], + "to": [ + 24.5, + 4.8, + 21.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 13.32 + ], + "to": [ + -5.5, + 6.4, + 20.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 14.6 + ], + "to": [ + -5.5, + 8.0, + 19.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999999, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 24.5, + 2.2, + 15.2 + ], + "to": [ + 24.560000000000002, + 5.8, + 18.8 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "south": { + "uv": [ + 0, + 0, + 0.25, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.6000000000000014, + 3.5999999999999996 + ], + "texture": "#hub_b" + }, + "up": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + }, + "down": { + "uv": [ + 0, + 0, + 0.25, + 3.6000000000000014 + ], + "texture": "#hub_b" + } + } + }, + { + "from": [ + 21.5, + 0.0, + 23.6 + ], + "to": [ + 24.5, + 1.6, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 1.6, + 22.32 + ], + "to": [ + -5.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 1.6, + 22.32 + ], + "to": [ + 24.5, + 3.2, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 3.2, + 22.0 + ], + "to": [ + -5.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 3.2, + 22.0 + ], + "to": [ + 24.5, + 4.8, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 8.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 8.0 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 4.8, + 22.32 + ], + "to": [ + -5.5, + 6.4, + 29.68 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 7.359999999999999, + 1.6000000000000005 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 7.359999999999999 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -8.5, + 6.4, + 23.6 + ], + "to": [ + -5.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + 21.5, + 6.4, + 23.6 + ], + "to": [ + 24.5, + 8.0, + 28.4 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "east": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "west": { + "uv": [ + 0, + 0, + 4.799999999999997, + 1.5999999999999996 + ], + "texture": "#tire_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 4.799999999999997 + ], + "texture": "#tire_b" + } + } + }, + { + "from": [ + -4.0, + 9.0, + -3.0 + ], + "to": [ + 20.0, + 12.0, + 30.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 3.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + 18.0, + 12.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 14.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 3.0, + 3.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 6.0, + 24.0, + -4.0 + ], + "to": [ + 21.0, + 26.0, + -1.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 15.0, + 2.0 + ], + "texture": "#metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 3.0, + 2.0 + ], + "texture": "#metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 15.0, + 3.0 + ], + "texture": "#metal_b" + } + } + }, + { + "from": [ + 6.0, + 20.0, + -4.0 + ], + "to": [ + 7.0, + 24.0, + -3.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "south": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "east": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "west": { + "uv": [ + 0, + 0, + 1.0, + 4.0 + ], + "texture": "#dark_metal_b" + }, + "up": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal_b" + }, + "down": { + "uv": [ + 0, + 0, + 1.0, + 1.0 + ], + "texture": "#dark_metal_b" + } + } + }, + { + "from": [ + -3.0, + 12.0, + -1.0 + ], + "to": [ + 3.0, + 18.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 5.0, + 12.0, + -1.0 + ], + "to": [ + 11.0, + 18.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube_b" + } + } + }, + { + "from": [ + 13.0, + 12.0, + -1.0 + ], + "to": [ + 19.0, + 18.0, + 29.0 + ], + "faces": { + "north": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "south": { + "uv": [ + 0, + 0, + 6.0, + 6.0 + ], + "texture": "#tube_cap_b" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube_b" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6.0 + ], + "texture": "#tube_b" + }, + "up": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube_b" + }, + "down": { + "uv": [ + 0, + 0, + 6.0, + 16 + ], + "texture": "#tube_b" + } + } + } + ] +} diff --git a/airdef-mod/source-assets/assets/airdef/textures/block/pilot_nato.png b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_nato.png new file mode 100644 index 0000000..53549cb Binary files /dev/null and b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_nato.png differ diff --git a/airdef-mod/source-assets/assets/airdef/textures/block/pilot_nato_b.png b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_nato_b.png new file mode 100644 index 0000000..005a924 Binary files /dev/null and b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_nato_b.png differ diff --git a/airdef-mod/source-assets/assets/airdef/textures/block/pilot_olive.png b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_olive.png new file mode 100644 index 0000000..875dd0e Binary files /dev/null and b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_olive.png differ diff --git a/airdef-mod/source-assets/assets/airdef/textures/block/pilot_olive_b.png b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_olive_b.png new file mode 100644 index 0000000..005a924 Binary files /dev/null and b/airdef-mod/source-assets/assets/airdef/textures/block/pilot_olive_b.png differ diff --git a/airdef-mod/src/battery_view.rs b/airdef-mod/src/battery_view.rs new file mode 100644 index 0000000..580df9e --- /dev/null +++ b/airdef-mod/src/battery_view.rs @@ -0,0 +1,42 @@ +//! Player-facing labels for battery configuration snapshots. +use crate::sim::battery::{BatteryStatus, RadarRole, SamMode}; + +pub fn radar_role_name(role: RadarRole) -> &'static str { + match role { + RadarRole::Multifunction => "многофункциональная", + RadarRole::Search => "обзорная", + RadarRole::Engagement => "сопровождения", + RadarRole::Height => "мачтовая", + } +} + +pub fn status(status: BatteryStatus) -> String { + match status { + BatteryStatus::Radar { index, role, emitting, operational } => format!( + "№{} РЛС: {}, излучение {}, {}", index + 1, radar_role_name(role), + if emitting { "вкл" } else { "выкл" }, + if operational { "работает" } else { "нет связи или РЛС не работает" } + ), + BatteryStatus::Launcher { index, kind, mode, linked } => format!( + "№{} {}: {}{}", index + 1, kind.name(), mode.key(), + if mode == SamMode::LauncherOnly { + if linked { ", РЛС связана" } else { ", нет связанной РЛС сопровождения" } + } else { "" } + ), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sim::SamKind; + + #[test] + fn renders_battery_status_in_existing_format() { + assert_eq!(status(BatteryStatus::Radar { index: 0, role: RadarRole::Search, emitting: false, operational: false }), "№1 РЛС: обзорная, излучение выкл, нет связи или РЛС не работает"); + assert_eq!(status(BatteryStatus::Radar { index: 1, role: RadarRole::Engagement, emitting: true, operational: true }), "№2 РЛС: сопровождения, излучение вкл, работает"); + assert_eq!(status(BatteryStatus::Launcher { index: 2, kind: SamKind::S400, mode: SamMode::LauncherOnly, linked: true }), "№3 С-400: launcher, РЛС связана"); + assert_eq!(status(BatteryStatus::Launcher { index: 3, kind: SamKind::S500, mode: SamMode::LauncherOnly, linked: false }), "№4 С-500: launcher, нет связанной РЛС сопровождения"); + assert_eq!(status(BatteryStatus::Launcher { index: 4, kind: SamKind::S400, mode: SamMode::Integrated, linked: false }), "№5 С-400: integrated"); + } +} diff --git a/airdef-mod/src/capabilities.rs b/airdef-mod/src/capabilities.rs new file mode 100644 index 0000000..949292d --- /dev/null +++ b/airdef-mod/src/capabilities.rs @@ -0,0 +1,240 @@ +//! Host milestones, not promises of audible output or completed GPU/file work. +use horizon_mod_sdk::render::{self, CapabilityReason, CapabilityState}; + +pub fn ready(s: &CapabilityState) -> bool { + s.generation != 0 + && s.available + && s.enabled + && s.request_accepted + && s.prepared + && s.reason == CapabilityReason::Ready +} + +pub fn capture_problem(s: &CapabilityState) -> Option<&'static str> { + if !s.available { + Some("Движок не поддерживает подтверждение захвата") + } else if !s.enabled || s.generation == 0 { + Some("Захват недоступен без текущего мира") + } else if !s.request_accepted { + Some("Захват больше не принадлежит этой сессии мода") + } else if s.reason == CapabilityReason::Failed { + Some("Ошибка пути захвата движка") + } else { + None + } +} + +/// Adopt the existing global preference once in this world, without switching it. +/// A configured request and actual backend preparation are still separate milestones. +pub fn acoustics() -> CapabilityState { + let s = render::acoustics_status(); + if s.available && s.generation != 0 && !s.request_accepted { + render::set_acoustics(s.enabled); + render::acoustics_status() + } else { + s + } +} + +fn reason(r: CapabilityReason) -> &'static str { + match r { + CapabilityReason::AwaitingWorld => "awaiting-world", + CapabilityReason::Disabled => "disabled", + CapabilityReason::NotRequested => "not-requested", + CapabilityReason::AwaitingBackend => "awaiting-backend", + CapabilityReason::Failed => "failed", + CapabilityReason::Ready => "ready", + } +} + +fn line( + name: &str, + generation: u64, + available: bool, + enabled: bool, + accepted: bool, + prepared: bool, + why: &str, +) -> String { + format!("{name}: API={available}, включено={enabled}, запрос={accepted}, подготовлено={prepared}, поколение={generation}, {why}") +} + +fn camera_line(s: &render::CameraState) -> String { + let cameras: Vec = s + .cameras + .iter() + .map(|c| { + let why = match c.reason { + render::CameraReason::AwaitingFrame => "awaiting-frame", + render::CameraReason::Ready => "ready", + }; + format!("{}:{}", c.id, why) + }) + .collect(); + let main = match s.main_view { + Some(id) => id.to_string(), + None => "нет".into(), + }; + format!( + "Камеры: API={}, включено={}, запрос={}, подготовлено={}, поколение={}, {}, main={}, [{}]", + s.available, + s.enabled, + s.request_accepted, + s.prepared, + s.generation, + reason(s.reason), + main, + cameras.join("; ") + ) +} + +pub fn diagnostics() -> String { + let smoke = render::smoke_status(); + let acoustic = acoustics(); + let capture = render::capture_owner_status(); + let lights = render::point_lights_status(); + let sources = render::sound_sources_status(); + let cameras = render::camera_status(); + let smoke_reason = match smoke.reason { + render::SmokeReason::AwaitingFrame => "awaiting-frame", + render::SmokeReason::Disabled => "disabled", + render::SmokeReason::PathUnavailable => "path-unavailable", + render::SmokeReason::Ready => "ready", + }; + [ + "Возможности движка (API 30; подготовка не означает готовый GPU-кадр, слышимый звук или файл):".into(), + line( + "Дым", + smoke.generation, + smoke.available, + smoke.enabled, + smoke.request_accepted, + smoke.backend_ready, + smoke_reason, + ), + line( + "Акустика", + acoustic.generation, + acoustic.available, + acoustic.enabled, + acoustic.request_accepted, + acoustic.prepared, + reason(acoustic.reason), + ), + line( + "Точечный свет", + lights.generation, + lights.available, + lights.enabled, + lights.request_accepted, + lights.prepared, + reason(lights.reason), + ), + line( + "Постоянные звуки", + sources.generation, + sources.available, + sources.enabled, + sources.request_accepted, + sources.prepared, + reason(sources.reason), + ), + camera_line(&cameras), + line( + "Захват этого мода", + capture.generation, + capture.available, + capture.enabled, + capture.request_accepted, + capture.prepared, + reason(capture.reason), + ), + ] + .join("\n") +} + +#[cfg(test)] +mod tests { + use super::*; + fn state() -> CapabilityState { + CapabilityState { + generation: 5, + available: true, + enabled: true, + request_accepted: true, + prepared: true, + reason: CapabilityReason::Ready, + } + } + #[test] + fn acceptance_alone_never_disables_the_working_fallback() { + assert!(ready(&state())); + for missing in 0..6 { + let mut s = state(); + match missing { + 0 => s.generation = 0, + 1 => s.available = false, + 2 => s.enabled = false, + 3 => s.request_accepted = false, + 4 => s.prepared = false, + _ => s.reason = CapabilityReason::AwaitingBackend, + } + assert!(!ready(&s)); + } + } + #[test] + fn a_foreign_capture_is_not_success_and_preparing_is_not_failure() { + let mut s = state(); + s.request_accepted = false; + assert!(capture_problem(&s).is_some()); + s = state(); + s.prepared = false; + s.reason = CapabilityReason::AwaitingBackend; + assert!(capture_problem(&s).is_none()); + assert!(!ready(&s)); + s.reason = CapabilityReason::Failed; + assert!(capture_problem(&s).is_some()); + } + fn camera(id: u32, prepared: bool) -> render::CameraReadiness { + render::CameraReadiness { + id, + request_accepted: true, + prepared, + reason: if prepared { + render::CameraReason::Ready + } else { + render::CameraReason::AwaitingFrame + }, + } + } + #[test] + fn the_camera_line_separates_readiness_from_the_screens_selection() { + let line = camera_line(&render::CameraState { + generation: 5, + available: true, + enabled: true, + request_accepted: true, + prepared: true, + reason: CapabilityReason::Ready, + cameras: vec![camera(7, true), camera(8, false)], + main_view: Some(7), + }); + assert_eq!( + line, + "Камеры: API=true, включено=true, запрос=true, подготовлено=true, поколение=5, \ + ready, main=7, [7:ready; 8:awaiting-frame]" + ); + // No accepted submission: no cameras, no main view, and the reason says so. + let line = camera_line(&render::CameraState { + generation: 5, + available: true, + enabled: false, + request_accepted: false, + prepared: false, + reason: CapabilityReason::NotRequested, + cameras: Vec::new(), + main_view: None, + }); + assert!(line.ends_with("not-requested, main=нет, []"), "{line}"); + } +} diff --git a/airdef-mod/src/cinema.rs b/airdef-mod/src/cinema.rs index 7c731ed..bdc7eec 100644 --- a/airdef-mod/src/cinema.rs +++ b/airdef-mod/src/cinema.rs @@ -2,15 +2,17 @@ //! tail; from afar the muffled report and a late boom), and the camera's reaction (a brief //! exposure and bloom punch). Everything is a function of the blast, its distance and its age, so //! a replay hears and sees the same as the live raid did, from the replay's camera. +use crate::sim::catalog::sound; + /// The power (0 air burst .. 4 ballistic) of a blast sound event, if it is one. pub fn blast_power(event: &str) -> Option { Some(match event { - "airdef:blast.air" => 0, - "airdef:blast.medium" => 1, - "airdef:blast.big" => 2, - "airdef:blast.heavy" => 3, - "airdef:blast.huge" => 4, + sound::BLAST_AIR => 0, + sound::BLAST_MEDIUM => 1, + sound::BLAST_BIG => 2, + sound::BLAST_HEAVY => 3, + sound::BLAST_HUGE => 4, _ => return None, }) } @@ -34,7 +36,7 @@ pub const FULL: usize = 3; /// nearest of its tick. pub fn layers(power: usize, dist: f64, far: f64, gain: f32, pitch: f32, rank: usize) -> Vec { let g = gain.clamp(0.3, 2.0); - let own = ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][power.min(4)]; + let own = [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][power.min(4)]; let layer = |name: &str, volume: f32, pitch: f32, delay: u64| Layer { name: name.to_string(), volume, pitch, delay }; let far_away = dist > far; let suffix = if far_away { "_far" } else { "" }; @@ -45,22 +47,22 @@ pub fn layers(power: usize, dist: f64, far: f64, gain: f32, pitch: f32, rank: us // The muffled report, then a deep boom that arrives late. return vec![ layer(&format!("{own}_far"), g, pitch, 0), - layer("airdef:blast.big_far", 0.7 * g, pitch * 0.6, 10 + 2 * power as u64), + layer(sound::BLAST_BIG_FAR, 0.7 * g, pitch * 0.6, 10 + 2 * power as u64), ]; } let tail = (0.45 + 0.08 * power as f32) * g; if power == 0 { // An air burst has no body of its own: the crack, the thump, the roll. vec![ - layer("airdef:blast.air", g, pitch * 1.12, 0), - layer("airdef:blast.big", 0.85 * g, pitch * 0.66, 2), - layer("airdef:blast.huge", tail, pitch * 0.5, 7), + layer(sound::BLAST_AIR, g, pitch * 1.12, 0), + layer(sound::BLAST_BIG, 0.85 * g, pitch * 0.66, 2), + layer(sound::BLAST_HUGE, tail, pitch * 0.5, 7), ] } else { vec![ layer(own, g, pitch, 0), - layer("airdef:blast.air", 0.7 * g, pitch * 1.15, 0), - layer("airdef:blast.huge", tail, pitch * 0.5, 7), + layer(sound::BLAST_AIR, 0.7 * g, pitch * 1.15, 0), + layer(sound::BLAST_HUGE, tail, pitch * 0.5, 7), ] } } diff --git a/airdef-mod/src/commands.rs b/airdef-mod/src/commands.rs index 2370855..e8c7aaf 100644 --- a/airdef-mod/src/commands.rs +++ b/airdef-mod/src/commands.rs @@ -14,11 +14,12 @@ fn on_off() -> Value { /// `/airdef ...` as `lib.rs` accepts it. pub fn tree() -> Tree { let wave_kinds: Vec<&str> = DroneKind::ALL.iter().map(|k| k.spec().key).chain(["mix"]).collect(); - let deploy_kinds: Vec<&str> = SamKind::ALL.iter().map(|k| k.key()).collect(); + let deploy_kinds: Vec<&str> = SamKind::ALL.iter().map(|k| k.key()).chain(["s400_launcher", "s500_launcher", "radar_search", "radar_track", "radar_height"]).collect(); let armed: Vec<&str> = SamKind::ARMED.iter().map(|k| k.key()).chain(["all"]).collect(); let toggle = |word: &str| Node::lit(word).then(Node::arg("on/off", on_off()).runs()); Tree::new("airdef").runs().with([ Node::lit("menu").runs(), + Node::lit("capabilities").runs(), Node::lit("monitor").runs(), Node::lit("grid").runs(), Node::lit("replay").runs().with([ @@ -59,6 +60,10 @@ pub fn tree() -> Tree { Node::lit("why").runs().then(Node::arg("номер", Value::int_from(1)).runs()), Node::lit("raid").runs(), Node::lit("sites").runs(), + Node::lit("ground-stock").then(Node::arg("склад", Value::int_from(1)).then(Node::arg("количество", Value::int_from(0)).runs())), + Node::lit("ground-cancel").then(Node::arg("комплекс", Value::int_from(1)).runs()), + Node::lit("ground-unload").then(Node::arg("комплекс", Value::int_from(1)).runs()), + Node::lit("ground-load").then(Node::arg("комплекс", Value::int_from(1)).runs()), Node::lit("strike").then(Node::arg("номер", Value::int_from(1)).runs()), Node::lit("repair").runs(), Node::lit("import").runs(), @@ -67,6 +72,8 @@ pub fn tree() -> Tree { Node::lit("move").then(Node::arg("номер", Value::int_from(1)).runs()), Node::lit("target").then(Node::lit("here").runs()), Node::lit("give").runs(), + Node::lit("battery").runs().then(Node::arg("номер", Value::int_from(1)).then(Node::arg("режим", Value::choice(["integrated", "launcher"])).runs())), + Node::lit("radar").then(Node::arg("номер", Value::int_from(1)).then(Node::arg("роль", Value::choice(["on", "off", "multi", "search", "engagement", "height"])).runs())), Node::lit("deploy").then(Node::arg("комплекс", Value::choice(deploy_kinds)).runs()), Node::lit("wave").then( Node::arg("тип", Value::choice(wave_kinds)).runs().then( @@ -155,13 +162,13 @@ mod tests { #[test] fn every_branch_of_the_handler_parses() { for line in [ - "", "menu", "monitor", "grid", "give", "raid", "repair", "import", "flare", "clear", "stats", "roles", + "", "menu", "capabilities", "monitor", "grid", "give", "raid", "repair", "import", "flare", "clear", "stats", "roles", "replay", "replay map", "replay full", "replay reel", "replay film", "replay film best", "replay film all", "replay film 3", "replay export", "replay export mine", "replay export best", "replay export all", "replay export 3", "replay export mine all fps=30 size=1280x720 crf=18 sec=12.5 noaudio", "replay export fps=60", "replay stop", "scenario save a", "scenario save a fresh", "scenario load a", "scenario load a force", "scenario list", "scenario delete a", "why", "why 2", "ew on", "ew off", "move 4", "target here", "targets clear", - "sites", "strike 3", "deploy patriot", "deploy light", "deploy s400", "wave mix", "wave shahed", "wave shahed 20", "wave kh101 80 800", "wave shahed 20 4000", + "sites", "strike 3", "battery", "battery 1 launcher", "battery 1 integrated", "radar 2 on", "radar 2 off", "radar 2 engagement", "radar 2 search", "deploy s400_launcher", "deploy s500_launcher", "deploy radar_search", "deploy radar_track", "deploy radar_height", "deploy patriot", "deploy light", "deploy s400", "wave mix", "wave shahed", "wave shahed 20", "wave kh101 80 800", "wave shahed 20 4000", "wave oreshnik 1 60", "damage on", "damage off", "truth on", "shader hd", "shader off", "shader on", "hold on", "lastchance on", "lastchance off", "roles all default", "doctrine", "doctrine 1 reserve 4", "doctrine all salvo eco", "doctrine 2 sector 90 45", "doctrine 2 sector off", "doctrine 1 guard 1,2", "doctrine 1 reset", "roles patriot drone,jet", "roles pantsir all", "roles tor ballistic", ] { @@ -234,10 +241,10 @@ mod tests { fn completion_offers_the_next_word_only() { let top = complete("", &[]); for w in ["menu", "monitor", "grid", "replay", "scenario", "why", "raid", "repair", "import", "ew", "flare", "move", - "target", "give", "deploy", "wave", "clear", "targets", "damage", "truth", "shader", "hold", "lastchance", "roles", "doctrine", "stats", "sites", "strike"] { + "target", "give", "deploy", "wave", "clear", "targets", "damage", "truth", "shader", "hold", "lastchance", "roles", "doctrine", "stats", "sites", "strike", "ground-stock", "ground-load", "ground-unload", "ground-cancel"] { assert!(top.contains(&w.to_string()), "{w} in {top:?}"); } - assert_eq!(top.len(), 28); + assert_eq!(top.len(), 35); assert_eq!(complete("rep", &[]), ["repair", "replay"]); assert_eq!(complete("replay ", &[]), ["export", "film", "full", "map", "reel", "stop"]); assert_eq!(complete("replay film ", &[]), ["all", "best"]); @@ -273,7 +280,7 @@ mod tests { #[test] fn the_help_line_is_the_trees() { let help = help(); - assert!(help.starts_with("/airdef menu | monitor | grid | replay "), "{help}"); + assert!(help.starts_with("/airdef menu | capabilities | monitor | grid | replay "), "{help}"); assert!(help.contains("scenario save <имя> [fresh]"), "{help}"); assert!(help.contains("wave <тип> [<кол-во> [<дальность>]]"), "{help}"); assert!(help.ends_with("| stats"), "{help}"); diff --git a/airdef-mod/src/engines.rs b/airdef-mod/src/engines.rs index 31bdc6d..4a5121b 100644 --- a/airdef-mod/src/engines.rs +++ b/airdef-mod/src/engines.rs @@ -9,6 +9,8 @@ //! for a tick or two (a step the simulation skipped) does not stop the sound; a *cut* (a replay //! seek, a new shot) stops every engine and starts them again under new ids, so nothing glides //! across the jump. +use crate::sim::catalog::sound; + use glam::DVec3; use std::collections::{BTreeMap, BTreeSet}; @@ -22,20 +24,7 @@ const MAX: usize = 128; /// change what is heard, so it stays off; the game bounds it to 12% anyway. pub const DOPPLER: f32 = 0.0; -/// An engine that sounds this tick. -#[derive(Clone, Debug, PartialEq)] -pub struct Decl { - /// The entity: a drone's id, a missile's with a high bit, a replay track's with another. - pub key: u32, - pub event: &'static str, - pub at: DVec3, - /// Blocks a second. - pub vel: DVec3, - pub gain: f32, - pub pitch: f32, - /// Shot down: the engine dies away. - pub damaged: bool, -} +pub use crate::sim::EngineDecl as Decl; /// What a source is asked to be. #[derive(Clone, Debug, PartialEq)] @@ -65,12 +54,12 @@ pub enum Op { /// engine_sounds_has_no_hole` finds them (after the fade-in, before the sound begins to die). pub fn steady_part(event: &str) -> (f32, f32) { match event { - "airdef:drone.shahed" => (0.2, 2.2), - "airdef:drone.geran3" => (0.2, 2.0), - "airdef:drone.lancet" => (0.2, 1.4), - "airdef:drone.cruise" => (0.2, 2.6), - "airdef:drone.rocket" => (0.2, 2.6), - "airdef:missile.flight" => (0.2, 2.2), + sound::DRONE_SHAHED => (0.2, 2.2), + sound::DRONE_GERAN3 => (0.2, 2.0), + sound::DRONE_LANCET => (0.2, 1.4), + sound::DRONE_CRUISE => (0.2, 2.6), + sound::DRONE_ROCKET => (0.2, 2.6), + sound::MISSILE_FLIGHT => (0.2, 2.2), _ => (0.0, 0.0), } } @@ -95,6 +84,12 @@ impl Engines { self.live.is_empty() } + /// An explicitly unpowered entity releases its source without waiting for the missing-frame grace. + pub fn stop(&mut self, key: u32) -> Option { + self.dead.remove(&key); + self.live.remove(&key).map(|live| Op::Stop(live.id)) + } + /// The game released every source: forget them (nothing to stop). pub fn reset(&mut self) { self.live.clear(); @@ -210,6 +205,15 @@ mod tests { damaged: false, } } + #[test] + fn explicit_burnout_releases_the_source_immediately() { + let mut engines = Engines::default(); + let ops = engines.step(vec![d(42, 0.0)], false, DVec3::ZERO); + assert!(matches!(ops.as_slice(), [Op::Start(_, _)])); + assert!(matches!(engines.stop(42), Some(Op::Stop(_)))); + assert!(engines.is_empty()); + assert!(engines.stop(42).is_none()); + } fn starts(ops: &[Op]) -> usize { ops.iter().filter(|o| matches!(o, Op::Start(..))).count() } diff --git a/airdef-mod/src/film.rs b/airdef-mod/src/film.rs index 88c8644..e97d7ff 100644 --- a/airdef-mod/src/film.rs +++ b/airdef-mod/src/film.rs @@ -7,8 +7,8 @@ //! raid, so the same film comes out every time. The land is asked for before the film starts (the //! planner needs the heights under every camera and along every line of sight); each shot has a //! few candidate placements, and the one the land hides least is kept; the camera is lifted over -//! the ground wherever it would dip into it. Only what the recording knows is shown: a launch is -//! the interceptor's first point, a kill the recorded event; no lock-on or detection is invented. +//! the ground wherever it would dip into it. Only what the recording knows is shown: launches use the recorded transition where available (older tracks use their first point); +//! no lock-on or detection is invented. use crate::replay::{interest, title}; use crate::sim::record::{EventKind, Recording, STEP}; use crate::sim::Ground; @@ -309,7 +309,8 @@ fn plan_event(rec: &Recording, i: usize, floor: f64) -> Vec { }; match e.kind { EventKind::Kill { drone, kind, sam, sam_kind, sam_index, missile, outcome } => { - let sp = rec.sam(sam).map_or(pos - DVec3::new(150.0, 0.0, 0.0), |s| s.1); + let launch_tick = missile.and_then(|m| rec.missile_launch(m).map(|e| e.at as f64)).unwrap_or(te); + let sp = rec.sam_at(sam, launch_tick).map_or(pos - DVec3::new(150.0, 0.0, 0.0), |s| s.1); let name = format!("{} №{}", sam_kind.name(), sam_index + 1); let how = match outcome { crate::sim::outcome::Outcome::DisabledFalling => "", @@ -317,7 +318,7 @@ fn plan_event(rec: &Recording, i: usize, floor: f64) -> Vec { crate::sim::outcome::Outcome::AirDetonation => ": детонация в воздухе", }; let sub = format!("Перехват «{}»{how}", kind.spec().name); - let mt = missile.and_then(|m| rec.track(m).map(|t| (m, t.first as f64))); + let mt = missile.and_then(|m| rec.track(m).map(|t| (m, rec.missile_launch(m).map_or(t.first as f64, |e| e.at as f64)))); match mt { Some((m, tl)) => { // The facts, in order: the launcher waits, the interceptor leaves it at `tl`, it diff --git a/airdef-mod/src/layout.rs b/airdef-mod/src/layout.rs index e2c04e8..8ceac51 100644 --- a/airdef-mod/src/layout.rs +++ b/airdef-mod/src/layout.rs @@ -23,6 +23,16 @@ pub struct SamRec { pub dest: Option, /// The parts, from the sensor on; older saves have fewer or none. pub comps: Vec, + pub ground_rounds: u32, + pub ground_request: bool, + pub ground_return: bool, + pub return_cargo: bool, + pub ground_stock: u32, + pub air_48n6_stock: u32, + pub mode: sim::battery::SamMode, + pub radar_role: sim::battery::RadarRole, + pub spent_cells: u8, + pub radar_on: bool, } #[derive(Default)] @@ -117,7 +127,40 @@ impl Layout { None => break, } } - out.sams.push(SamRec { kind, pos, heading, roles, health, cargo, ammo, dest, comps }); + let ground_rounds = l.num(22, "заряжено RM48U", 0.0, 4.0)?.unwrap_or(0.0) as u32; + let ground_stock = l.num(23, "запас RM48U", 0.0, COUNT)?.unwrap_or(0.0) as u32; + let air_48n6_stock = l.num(24, "снятый боезапас ПВО", 0.0, COUNT)?.unwrap_or(0.0) as u32; + let mode = match l.w.get(25) { + None => sim::battery::SamMode::Integrated, + Some(v) => sim::battery::SamMode::from_key(v).ok_or_else(|| l.bad("режим", "integrated или launcher"))?, + }; + let radar_role = match l.w.get(26) { + None => sim::battery::RadarRole::Multifunction, + Some(v) => sim::battery::RadarRole::from_key(v).ok_or_else(|| l.bad("роль РЛС", "multi, search, engagement или height"))?, + }; + let spent_cells = match l.w.get(27) { + None => 0, + Some(v) => v.parse::().map_err(|_| l.bad("пустые контейнеры", "нужно целое число 0..255"))?, + }; + let ground_request = l.num(28, "заявка RM48U", 0.0, 1.0)?.unwrap_or(0.0) == 1.0; + if ground_request && kind != SamKind::S400 { return Err(l.bad("заявка RM48U", "нужен С-400")); } + let radar_on = l.flag(29, "излучение РЛС")?.unwrap_or(true); + let ground_return = l.flag(30, "заявка на вывоз RM48U")?.unwrap_or(false); + let return_cargo = l.flag(31, "возвращаемый груз RM48U")?.unwrap_or(false); + if (ground_return && (kind != SamKind::S400 || ground_request || ground_rounds == 0)) || (return_cargo && (kind != SamKind::Tzm || ground_stock == 0)) { return Err(l.bad("возврат RM48U", "несовместимое состояние")); } + if radar_role != sim::battery::RadarRole::Multifunction && kind != SamKind::Radar { + return Err(l.bad("роль РЛС", "роль поддерживается только отдельной РЛС")); + } + if mode == sim::battery::SamMode::LauncherOnly && !matches!(kind, SamKind::S400 | SamKind::S500) { + return Err(l.bad("режим", "пусковая поддерживается только С-400 и С-500")); + } + if (kind != SamKind::S400 && ground_rounds != 0) || (!matches!(kind, SamKind::Depot | SamKind::Tzm) && (ground_stock != 0 || air_48n6_stock != 0)) { + return Err(l.bad("боезапас", "несовместимый тип техники")); + } + if let Some((m, _, _, _)) = ammo && m + ground_rounds > 4 && kind == SamKind::S400 { + return Err(l.bad("боезапас", "больше четырёх ячеек")); + } + out.sams.push(SamRec { kind, pos, heading, roles, health, cargo, ammo, dest, comps, ground_rounds, ground_stock, air_48n6_stock, mode, radar_role, spent_cells, ground_request, radar_on, ground_return, return_cargo }); } "target" => { if out.targets.len() >= MAX_UNITS { @@ -166,6 +209,23 @@ impl Layout { mod tests { use super::*; + #[test] + fn battery_modes_load_and_old_layouts_default() { + let old = Layout::parse("sam s400 0 70 0 0").unwrap(); + assert_eq!(old.sams[0].mode, sim::battery::SamMode::Integrated); + assert!(old.sams[0].radar_on); + assert_eq!(old.sams[0].radar_role, sim::battery::RadarRole::Multifunction); + let data = "sam s400 0 70 0 0 30 1 0 4 0 0 0 - - - 1 1 1 1 1 1 0 0 0 launcher multi 5\nsam radar 1 70 0 0 0 1 0 0 0 0 0 - - - 1 1 1 1 1 1 0 0 0 integrated engagement"; + let l = Layout::parse(data).unwrap(); + assert_eq!(l.sams[0].mode, sim::battery::SamMode::LauncherOnly); + assert_eq!(l.sams[0].spent_cells, 5); + assert_eq!(l.sams[1].radar_role, sim::battery::RadarRole::Engagement); + assert!(!Layout::parse(&data.replace("launcher multi 5", "launcher multi 5 0 false")).unwrap().sams[0].radar_on); + assert!(Layout::parse(&data.replace("launcher multi 5", "launcher multi 5 0 nope")).is_err()); + assert!(Layout::parse(&data.replace("engagement", "invalid")).is_err()); + assert!(Layout::parse(&data.replace("launcher multi 5", "launcher multi 256")).is_err()); + } + fn key() -> &'static str { SamKind::Patriot.key() } @@ -221,4 +281,36 @@ mod tests { let w = crate::wave::WaveConfig::default().text(); assert!(Layout::parse(&format!("wave {w}\n")).is_ok(), "{w}"); } + #[test] + fn typed_ground_ammo_is_persistent_and_incompatible_layout_is_refused() { + let text = "sam s400 0 70 0 0 30 1 0 2 0 0 0 - - - 1 1 1 1 1 1 2 0 0\nsam depot 0 70 10 0 0 1 30 0 0 0 0 - - - 1 1 1 1 1 1 0 7 2\n"; + let layout = Layout::parse(text).unwrap(); + assert_eq!((layout.sams[0].ground_rounds, layout.sams[0].ammo.unwrap().0), (2, 2)); + assert_eq!((layout.sams[1].ground_stock, layout.sams[1].air_48n6_stock), (7, 2)); + assert!(Layout::parse(&text.replace("s400", "patriot")).is_err()); + assert!(Layout::parse(&text.replace("0 2 0 0 0 -", "0 4 0 0 0 -")).is_err()); + } + + #[test] + fn typed_truck_cargo_and_delivery_request_load_from_save() { + let text = "sam s400 200 70 0 0 30 1 0 4 0 0 0 - - - 1 1 1 1 1 1 0 0 0 integrated multi 0 1\nsam tzm 0 70 0 0 0 1 0 0 0 0 0 - - - 1 1 1 1 1 1 0 1 0 integrated multi 0 0\n"; + let l = Layout::parse(text).unwrap(); + assert!(l.sams[0].ground_request); + assert_eq!(l.sams[1].ground_stock, 1); + assert!(Layout::parse(&text.replace("sam s400", "sam patriot")).is_err()); + } + + #[test] + fn the_save_line_is_what_the_layout_reads() { + let mut d = crate::sim::doctrine::Doctrine { salvo: crate::sim::doctrine::Salvo::Economy, reserve: 3, ..Default::default() }; + d.sector = Some((1.5, 0.5)); + d.guards = vec![DVec3::new(1.0, 2.0, 3.0)]; + let text = format!("sam {} 0 70 0 0\ndoctrine 0 {}\n", SamKind::Patriot.key(), d.save_text()); + let l = crate::layout::Layout::parse(&text).unwrap(); + assert_eq!(l.doctrines, vec![(0, d)]); + assert!(crate::layout::Layout::parse("doctrine 0 loud 1 - - 0\n").is_err()); + assert!(crate::layout::Layout::parse("doctrine 0 eco 1 - - 2 1 2 3\n").is_err(), "guards cut short"); + } + + } diff --git a/airdef-mod/src/lib.rs b/airdef-mod/src/lib.rs index 833d99c..7067329 100644 --- a/airdef-mod/src/lib.rs +++ b/airdef-mod/src/lib.rs @@ -12,7 +12,9 @@ use horizon_mod_sdk::{ ui::{self, ButtonWidget, ItemWidget, ListWidget, Screen, Widget}, }; +mod battery_view; mod cinema; +mod capabilities; mod engines; mod commands; mod layout; @@ -20,45 +22,19 @@ mod map; mod film; mod monitor; mod panel; +mod presentation; mod radar; mod replay; +mod scene; mod smoke; -pub mod sim; +pub use airdef_core::sim; mod scenario; mod wave; +mod world_view; +mod why_view; use sim::{Class, DroneKind, Ground, Out, Sam, SamKind, Sim}; -const ITEMS: [&str; 29] = [ - "airdef:designator", - "airdef:launch_shahed", - "airdef:launch_geran3", - "airdef:launch_lancet", - "airdef:launch_kh101", - "airdef:launch_kalibr", - "airdef:launch_kh22", - "airdef:launch_zircon", - "airdef:launch_iskander", - "airdef:launch_kinzhal", - "airdef:launch_oreshnik", - "airdef:deploy_patriot", - "airdef:deploy_gepard", - "airdef:deploy_pantsir", - "airdef:deploy_s400", - "airdef:deploy_s500", - "airdef:deploy_irist", - "airdef:deploy_nasams", - "airdef:deploy_buk", - "airdef:deploy_tor", - "airdef:deploy_p1sun", - "airdef:deploy_radar", - "airdef:deploy_depot", - "airdef:deploy_tzm", - "airdef:deploy_ew", - "airdef:deploy_light", - "airdef:mover", - "airdef:flare_gun", - "airdef:remover", -]; +const ITEMS: [&str; sim::catalog::INVENTORY_COUNT] = sim::catalog::INVENTORY; struct AirDef { smoke: smoke::Smoke, @@ -78,14 +54,10 @@ struct AirDef { grid_ids: Vec, /// The raid the wave menu orders. wave: wave::WaveConfig, - /// A replay playing (it takes the screen and the mouse wheel). - replay: Option, - /// An MP4 export in progress (`/airdef replay export`). - export: Option, + /// Playback lifecycle. Export owns the exact replay player it is capturing. + presentation: presentation::Session, /// The effects preset a film replaced: (shader, hd). film_prev: Option<(bool, bool)>, - /// Counts the replay/film sessions started; an export belongs to the one it began with. - session: u64, /// The world drawn beyond the render distance (replays and cameras see far off). far: bool, /// The acoustics asked for, until the game has them (its answer comes a tick later). @@ -101,7 +73,7 @@ struct AirDef { hd: bool, /// The world whose state is loaded (`game.world-id`), once one is. world: Option>, - /// The systems' and objects' health as last saved, to save again when it changes. + /// The durable systems, objects and strike stock as last saved, to save again when they change. health_mark: u64, /// The menu's page while it is open. menu: Option, @@ -144,8 +116,8 @@ enum Stage { Finishing, } struct Export { - /// The session (`AirDef::session`) whose film this export records. - session: u64, + /// The presentation generation whose film this export records. + generation: u64, opts: ExportOpts, stage: Stage, ticks: u32, @@ -186,13 +158,23 @@ fn source_op(kind: render::SourceOpKind, id: u32, v: &engines::Voice) -> render: /// A sound heard later: when, what, where, and how loud and high it was. struct PendingSound { - due: u64, + due: f64, + generation: u64, + created_at: presentation::Clock, name: String, at: DVec3, volume: f32, pitch: f32, } +impl PendingSound { + fn invalidated_by(&self, generation: u64, clock: presentation::Clock) -> bool { + self.generation != generation + || !self.created_at.same_domain(clock) + || clock.value() < self.created_at.value() + } +} + /// Blocks a tick the sound travels (343 m/s). const SOUND_SPEED: f64 = 17.15; /// Past this the far variant plays: muffled, more rolling echo. @@ -209,6 +191,7 @@ fn travels(event: &str) -> bool { #[derive(Clone, Copy, PartialEq)] enum Page { Main, + BatteryDeploy, Roles(SamKind), /// The raid's make-up, then its parameters and the launch. Wave, @@ -223,6 +206,27 @@ enum Page { } const MENU_KINDS: u32 = 1; +const BATTERY_ROWS: u32 = 5; +const BTN_BATTERY: u32 = 22; +const BATTERY_DEPLOY: [(&str, &str); 5] = [ + ("s400_launcher", "С-400 — отдельная пусковая"), + ("s500_launcher", "С-500 — отдельная пусковая"), + ("radar_search", "РЛС обзора"), + ("radar_track", "РЛС сопровождения"), + ("radar_height", "Мачтовая РЛС"), +]; + +fn deploy_configuration(key: &str) -> Option<(SamKind, sim::battery::SamMode, sim::battery::RadarRole)> { + use sim::battery::{RadarRole as R, SamMode as M}; + Some(match key { + "s400_launcher" => (SamKind::S400, M::LauncherOnly, R::Multifunction), + "s500_launcher" => (SamKind::S500, M::LauncherOnly, R::Multifunction), + "radar_search" => (SamKind::Radar, M::Integrated, R::Search), + "radar_track" => (SamKind::Radar, M::Integrated, R::Engagement), + "radar_height" => (SamKind::Radar, M::Integrated, R::Height), + _ => (SamKind::from_key(key)?, M::Integrated, R::Multifunction), + }) +} const BTN_HOLD: u32 = 10; const BTN_SHADER: u32 = 11; const BTN_DAMAGE: u32 = 12; @@ -273,6 +277,18 @@ struct Shown { replay: bool, } +impl Shown { + fn from_context(ctx: &presentation::Context, scene: &scene::Snapshot<'_>) -> Self { + let recording = ctx.is_playback() && ctx.filming; + Self { + blasts: scene.blasts().into_owned(), + now: scene.time() as f32, + hold: if recording { 1.0 - ctx.rate.clamp(0.0, 1.0) } else { 0.0 }, + replay: recording, + } + } +} + fn player() -> Option { game::get_player() } @@ -314,8 +330,8 @@ impl AirDef { } None => say("Нет блока под прицелом"), }, - _ if id.starts_with("airdef:launch_") => { - let Some(kind) = DroneKind::from_key(&id["airdef:launch_".len()..]) else { return }; + _ if sim::catalog::launch_item(id).is_some() => { + let Some(kind) = sim::catalog::launch_item(id) else { return }; // Where you look, else the nearest marked target. let target = aim().or_else(|| self.sim.nearest_target(eye(&p))); match target { @@ -326,8 +342,8 @@ impl AirDef { None => say("Наведитесь на блок или отметьте цель"), } } - _ if id.starts_with("airdef:deploy_") => { - let Some(kind) = SamKind::from_key(&id["airdef:deploy_".len()..]) else { return }; + _ if sim::catalog::deploy_item(id).is_some() => { + let Some(kind) = sim::catalog::deploy_item(id) else { return }; match aim() { Some(at) => { let d = view(&p); @@ -412,8 +428,8 @@ impl AirDef { /// The HD lighting's lights: flames, glowing warheads, flares, muzzle flashes, tracers, /// blasts (a flash, then the fireball's glow dying down), burning wrecks and the /// searchlights' spots. - fn lights(&self, out: &Out) -> Vec { - let shown = self.shown(); + fn lights(&self, out: &Out, ctx: &presentation::Context) -> Vec { + let shown = self.shown(ctx); let mut lights: Vec = Vec::new(); let mut add = |id: u32, at: DVec3, rgb: (f32, f32, f32), intensity: f32, radius: f32| { if lights.len() < 500 && intensity > 0.05 { @@ -423,7 +439,7 @@ impl AirDef { let tick = shown.now as u64; let flicker = |id: u32| 0.85 + 0.15 * (((tick as u32).wrapping_mul(2654435761) ^ id.wrapping_mul(97)) % 100) as f32 / 100.0; // Blasts first, the nearest first: a crowded sky never costs the key intercept its light. - let viewer = self.viewer(&shown); + let viewer = self.viewer(ctx); let mut near: Vec<&sim::Blast> = shown.blasts.iter().collect(); if let Some(e) = viewer { near.sort_by(|a, b| a.at.distance_squared(e).total_cmp(&b.at.distance_squared(e))); @@ -491,11 +507,11 @@ impl AirDef { /// The searchlights' spot lights (HD): a cone from the head along its real direction, as far /// as the beam runs free; at most 16, the renderer draws the nearest. - fn spots(&self) -> Vec { - let shown = self.shown(); + fn spots(&self, ctx: &presentation::Context) -> Vec { + let shown = self.shown(ctx); // A replay lights with the recording's searchlights, where and as they pointed. let beams: Vec = if shown.replay { - match self.replay.as_ref().and_then(|r| self.sim.replay(r.rec).map(|rec| rec.beams(f64::from(shown.now)))) { + match self.presentation.playback().and_then(|r| self.sim.replay(r.rec).map(|rec| rec.beams(f64::from(shown.now)))) { Some(b) => b, None => Vec::new(), } @@ -556,8 +572,8 @@ impl AirDef { /// Hands the simulation's output to the game. /// The post pass's parameters: blasts and glowing parts (shaders/effects.wgsl). - fn shader_params(&self, out: &Out) -> Vec { - let shown = self.shown(); + fn shader_params(&self, out: &Out, ctx: &presentation::Context) -> Vec { + let shown = self.shown(ctx); let mut p = vec![0.0f32; 4]; p[0] = shown.now; p[3] = if self.smoke.hd { 2.0 } else if self.hd { 1.0 } else { 0.0 }; @@ -565,7 +581,7 @@ impl AirDef { // The shader composites the volumes front to back with one shared transmittance, so // the nearest to the player goes first. This is exact for volumes that do not // overlap; where two intersect, no order by centre is right along every ray. - if let Some(e) = self.viewer(&shown) { + if let Some(e) = self.viewer(ctx) { blasts.sort_by(|a, b| a.at.distance_squared(e).total_cmp(&b.at.distance_squared(e))); } p[1] = blasts.len() as f32; @@ -605,39 +621,58 @@ impl AirDef { /// The smoke volumes the renderer draws and the clock they are aged by: the live ones, or (in a /// replay) the blasts' billows at the replay's moment, never the live raid's trails and fires. - /// The billows are the live ones' own function (`Smoke::segments`), run on the recording's - /// blasts and time with the live state set aside for the call. - fn shown_smoke(&mut self) -> (f64, Vec) { - let shown = self.shown(); - if !shown.replay { - return (self.sim.tick as f64, self.smoke.segments(&self.sim)); - } - // The recording alone, as a function of its time: the live Sim is not touched. - let segments = match self.sim.replay(self.replay.as_ref().map_or(0, |r| r.rec)) { - Some(rec) => smoke::replay_smoke(rec, f64::from(shown.now)), - None => Vec::new(), - }; - (f64::from(shown.now), segments) + /// The recorded scene is read directly; live simulation state is never replaced. + fn shown_smoke(&self, ctx: &presentation::Context) -> (f64, Vec) { + let scene = self.scene_snapshot(ctx); + (scene.time(), self.smoke.segments(&scene)) } /// Where the picture is seen from: the replay's camera, or the player's eyes. - fn viewer(&self, shown: &Shown) -> Option { - match &self.replay { - Some(r) if shown.replay => Some(r.eye()), - _ => player().map(|pl| eye(&pl)), + fn viewer(&self, ctx: &presentation::Context) -> Option { + if ctx.is_playback() && ctx.filming { + self.presentation.playback().map(|r| r.eye()) + } else { + Some(ctx.listener) } } /// The blasts the effects show and the clock they run on: the live ones, or (in a replay) the /// recording's at the replay's moment, never the live world's. - fn shown(&self) -> Shown { - if let Some(r) = &self.replay - && r.filming() - && let Some(rec) = self.sim.replay(r.rec) - { - return Shown { blasts: sim::record::replay_blasts(rec, r.time()), now: r.time() as f32, hold: 1.0 - r.rate().clamp(0.0, 1.0), replay: true }; + fn presentation_context(&self) -> presentation::Context { + let world = self.world.clone(); + if let Some(r) = self.presentation.playback() { + presentation::Context::playback( + if self.presentation.is_exporting() { presentation::Mode::Export } else { presentation::Mode::Replay }, + self.presentation.generation(), + world, + self.sim.engine_tick, + self.sim.tick, + r.time(), + r.day_time(&self.sim), + r.rate(), + self.listener, + r.filming(), + ) + } else { + presentation::Context::live(self.presentation.generation(), world, self.sim.engine_tick, self.sim.tick, self.sim.day_time, self.listener) } - Shown { blasts: self.sim.blasts.clone(), now: self.sim.tick as f32, hold: 0.0, replay: false } + } + + fn scene_snapshot(&self, ctx: &presentation::Context) -> scene::Snapshot<'_> { + if ctx.is_playback() && ctx.filming { + let source = self.presentation.playback() + .and_then(|r| self.sim.replay(r.rec)) + .map(scene::Source::Recorded) + .unwrap_or(scene::Source::Empty); + scene::Snapshot::at_context(ctx, source) + } else { + scene::Snapshot::at_context(ctx, scene::Source::Live(&self.sim)) + } + } + + fn shown(&self, ctx: &presentation::Context) -> Shown { + let scene = self.scene_snapshot(ctx); + Shown::from_context(ctx, &scene) } /// The models to the game by handle: each name crosses once, the buffer stays allocated. @@ -674,8 +709,8 @@ impl AirDef { /// The engines the simulation or the replay declared become persistent sound sources (#95): /// started, followed, stopped. The one-shots below stay as they were. - fn step_engines(&mut self, out: &mut Out) { - let replay = self.replay.as_ref().is_some_and(|r| r.filming()); + fn step_engines(&mut self, ctx: &presentation::Context, out: &mut Out) { + let replay = ctx.is_playback() && ctx.filming; let cut = std::mem::take(&mut out.engine_cut) || replay != self.engines_replay; self.engines_replay = replay; let generation = render::sound_source_generation(); @@ -684,13 +719,14 @@ impl AirDef { self.engines.reset(); self.engine_gen = generation; } - if self.engines.is_empty() && out.engines.is_empty() && !cut { + if self.engines.is_empty() && out.engines.is_empty() && out.engine_stops.is_empty() && !cut { return; } - let ops: Vec = self - .engines - .step(std::mem::take(&mut out.engines), cut, self.listener) - .into_iter() + let mut changes = self.engines.step(std::mem::take(&mut out.engines), cut, ctx.listener); + for key in std::mem::take(&mut out.engine_stops) { + if let Some(op) = self.engines.stop(key) { changes.push(op); } + } + let ops: Vec = changes.into_iter() .map(|op| match op { engines::Op::Start(id, v) => source_op(render::SourceOpKind::Start, id, &v), engines::Op::Update(id, v) => source_op(render::SourceOpKind::Update, id, &v), @@ -724,24 +760,26 @@ impl AirDef { } fn emit(&mut self, mut out: Out) { - self.step_engines(&mut out); + let ctx = self.presentation_context(); + self.step_engines(&ctx, &mut out); if self.sim.shader { - render::set_params(&self.shader_params(&out)); + render::set_params(&self.shader_params(&out, &ctx)); } if self.hd { // The camera's reaction to a near blast: a brief exposure and bloom punch. - let shown = self.shown(); - let p = self.viewer(&shown).map_or(0.0, |e| { + let shown = self.shown(&ctx); + let p = self.viewer(&ctx).map_or(0.0, |e| { shown.blasts.iter().map(|b| cinema::punch(b.size, shown.now - b.start as f32, b.at.distance(e))).fold(0.0, f32::max) }); if (p - self.punch).abs() > 0.01 || (p == 0.0 && self.punch != 0.0) { self.punch = p; render::set_lighting(Lighting { hd: true, exposure: 1.0 + 0.4 * p, bloom: 0.55 + 0.9 * p }); } - render::set_lights(&self.lights(&out)); - self.sim.spot_hd = render::set_spot_lights(&self.spots()); - let (clock, segments) = self.shown_smoke(); - self.smoke.hd = render::set_smoke(clock, &segments); + render::set_lights(&self.lights(&out, &ctx)); + self.sim.spot_hd = render::set_spot_lights(&self.spots(&ctx)); + let (clock, segments) = self.shown_smoke(&ctx); + render::set_smoke(clock, &segments); + self.smoke.hd = horizon_mod_sdk::smoke_can_replace_fallback(&render::smoke_status()); } else { self.smoke.hd = false; } @@ -749,7 +787,7 @@ impl AirDef { self.send_models(&out.models); } if !out.particles.is_empty() { - let hd_smoke = self.smoke.hd && !self.replay.as_ref().is_some_and(|r| r.filming()); + let hd_smoke = self.smoke.hd && !(ctx.is_playback() && ctx.filming); let spawns: Vec = out .particles .into_iter() @@ -771,14 +809,14 @@ impl AirDef { .collect(); render::spawn_particles(&spawns); } - let now = self.sim.tick; + let sound_clock = ctx.sound_clock(); // Blasts play layered (cinema.rs), the nearest ones in full. let mut booms: Vec<(f64, usize, DVec3, f32, f32)> = Vec::new(); // The game muffles by walls and distance itself (#96): the near variants play out to the // reach of its tracing, so nothing is muffled twice; past it the far ones still do. - let far = if render::acoustics() { ACOUSTIC_FAR } else { FAR }; + let far = if capabilities::ready(&capabilities::acoustics()) { ACOUSTIC_FAR } else { FAR }; for (event, at, volume, pitch) in out.sounds { - let dist = at.distance(self.listener); + let dist = at.distance(ctx.listener); if let Some(power) = cinema::blast_power(event) { booms.push((dist, power, at, volume, pitch)); } else if travels(event) { @@ -788,7 +826,7 @@ impl AirDef { if delay == 0 { render::play_sound(&name, at.x, at.y, at.z, volume, pitch); } else if self.pending.len() < 256 { - self.pending.push(PendingSound { due: now + delay, name, at, volume, pitch }); + self.pending.push(PendingSound { due: sound_clock.deadline_after(delay), generation: ctx.generation, created_at: sound_clock, name, at, volume, pitch }); } } else { render::play_sound(event, at.x, at.y, at.z, volume, pitch); @@ -802,13 +840,15 @@ impl AirDef { if delay == 0 { render::play_sound(&l.name, at.x, at.y, at.z, l.volume, l.pitch); } else if self.pending.len() < 256 { - self.pending.push(PendingSound { due: now + delay, name: l.name, at, volume: l.volume, pitch: l.pitch }); + self.pending.push(PendingSound { due: sound_clock.deadline_after(delay), generation: ctx.generation, created_at: sound_clock, name: l.name, at, volume: l.volume, pitch: l.pitch }); } } } let mut i = 0; while i < self.pending.len() { - if self.pending[i].due <= now { + if self.pending[i].invalidated_by(ctx.generation, sound_clock) { + self.pending.swap_remove(i); + } else if sound_clock.has_reached(self.pending[i].due) { let s = self.pending.swap_remove(i); render::play_sound(&s.name, s.at.x, s.at.y, s.at.z, s.volume, s.pitch); } else { @@ -829,7 +869,7 @@ impl AirDef { fn draw_hud(&mut self, player: DVec3, yaw: f32) { let pic = self.sim.air_picture(); // The command post's grid and replays cover the screen: the other panels wait under them. - if self.grid || self.replay.is_some() || (pic.tracks.is_empty() && self.sim.sams.is_empty() && self.sim.missiles.is_empty()) { + if self.grid || self.presentation.playback().is_some() || (pic.tracks.is_empty() && self.sim.sams.is_empty() && self.sim.missiles.is_empty()) { if self.hud_shown { hud::clear(); render::clear_corner(); @@ -846,7 +886,13 @@ impl AirDef { /// The state as text, lines of words: the autosave's format, and the body of a scenario. /// `fresh`: every system and object whole, magazines full (a scenario to replay from the start). + /// The air state (the raid's drones, launch queue, retarget jobs and the picture behind them, + /// #127) belongs to the autosave only: a scenario is a start state, not a raid frozen mid-flight. fn state_text(&self, fresh: bool, view: bool) -> String { + self.state_text_with(fresh, view, !fresh) + } + + fn state_text_with(&self, fresh: bool, view: bool, air: bool) -> String { let mut text = String::new(); for s in &self.sim.sams { // Then its ammunition (missiles, bursts, out and waiting for a truck, reload ticks @@ -854,7 +900,7 @@ impl AirDef { let dest = s.dest.filter(|_| !fresh).map_or("- - -".to_string(), |d| format!("{} {} {}", d.x, d.y, d.z)); let new = Sam::new(0, s.kind, s.pos, s.heading); let (health, cargo, missiles, bursts, empty, reloading) = if fresh { - (new.health, new.cargo, new.missiles, new.bursts, false, 0) + (new.health, new.cargo, new.missiles.saturating_sub(s.ground_rounds), new.bursts, false, 0) } else { (s.health, s.cargo, s.missiles, s.bursts, s.empty, s.reloading) }; @@ -862,7 +908,7 @@ impl AirDef { let comps = if fresh { new.comps } else { s.comps }; let parts = comps.0.iter().map(|v| format!("{v:.3}")).collect::>().join(" "); text += &format!( - "sam {} {} {} {} {} {} {} {} {} {} {} {} {dest} {parts}\n", + "sam {} {} {} {} {} {} {} {} {} {} {} {} {dest} {parts} {} {} {} {} {} {} {} {} {} {}\n", s.kind.key(), s.pos.x, s.pos.y, @@ -875,6 +921,7 @@ impl AirDef { bursts, u8::from(empty), reloading, + s.ground_rounds, s.ground_stock, s.air_48n6_stock, s.mode.key(), s.radar_role.key(), if fresh { 0 } else { s.spent_cells }, u8::from(!fresh && s.ground_request), s.radar_on, !fresh && s.ground_return, !fresh && s.return_cargo, ); } for (i, t) in self.sim.targets.iter().enumerate() { @@ -898,11 +945,27 @@ impl AirDef { for (i, s) in self.sim.sams.iter().enumerate().filter(|(_, s)| !s.doctrine.is_default()) { text += &format!("doctrine {i} {}\n", s.doctrine.save_text()); } + // The enemy's air state, last: it names its own format version, so an older build (or an + // older save) reads the layout and ignores it (#127). + if air && let Some(a) = self.sim.air_text() { + text += &a; + } text } fn save(&mut self) { - let text = self.state_text(false, true); + self.save_with(true); + } + + /// Writes the state. `air`: include the enemy's air state (#127). A raid still being *planned* has + /// no durable form — its items, sites and routes are half made — so a periodic save waits for it + /// to be launched (at most `sites::SEARCH_TICKS`), while a world change takes the layout alone. + fn save_with(&mut self, air: bool) { + if air && self.sim.planning() { + self.next_save = self.sim.tick + 20; + return; + } + let text = if air { self.state_text(false, true) } else { self.state_text_with(false, true, false) }; // Clean only once written: a failed write is tried again after a pause. match storage::set(&self.state_key(), text.as_bytes()) { Ok(()) => { @@ -952,7 +1015,15 @@ impl AirDef { /// naming the line and field, and the scene stays as it was (#86). fn apply_state(&mut self, text: &str) -> Result<(), String> { let layout = layout::Layout::parse(text)?; + // The air section is checked before anything is touched, like the layout (#127). + let air = sim::Sim::parse_air(text)?; self.commit_layout(layout); + if let Some(air) = air { + let cancelled = self.sim.restore_air(air); + if cancelled > 0 { + say(&format!("Воздушная обстановка восстановлена: отменено устаревших заданий {cancelled}")); + } + } Ok(()) } @@ -982,7 +1053,18 @@ impl AirDef { for (v, c) in sam.comps.0.iter_mut().zip(&r.comps) { *v = *c; } + sam.ground_rounds = r.ground_rounds; + sam.ground_request = r.ground_request; + sam.ground_return = r.ground_return; + sam.return_cargo = r.return_cargo; + sam.ground_stock = r.ground_stock; + sam.air_48n6_stock = r.air_48n6_stock; sam.fit_cells(); + sam.set_mode(r.mode); + sam.set_radar_role(r.radar_role); + sam.radar_on = r.radar_on; + sam.spent_cells = r.spent_cells; + sam.sync_tube_cells(); self.sim.sams.push(sam); if let Some(to) = r.dest { let i = self.sim.sams.len() - 1; @@ -1060,6 +1142,34 @@ fn world_identity() -> Option { }) } +/// What an autosave must not miss (#7, #113): the systems' places, ammunition and damage, the objects' health, and +/// the strike missiles' stock and refill. A change here marks the durable state dirty; the write itself is still +/// paced by `next_save`, and a failed write is retried. +fn durable_mark(sim: &Sim) -> u64 { + let st = &sim.strikes; + // The stock exactly; the refill at checkpoints, so that a tick-by-tick progress doesn't ask for a save (and a + // full state dump) every tick. A restart is at most `progress_save` ticks (5 s) behind on the next missile. + let strikes = u64::from(st.stock) | u64::from(st.progress / sim::strike::TUNE.progress_save.max(1)) << 8; + sim.sams + .iter() + .map(|s| { + let at = s.dest.unwrap_or(s.pos); + let place = (at.x * 10.0) as i64 as u64 ^ ((at.z * 10.0) as i64 as u64).rotate_left(21) ^ (if s.dest.is_some() { 0 } else { (s.heading * 100.0) as i64 as u64 }).rotate_left(42); + let ammo = u64::from(s.missiles) | u64::from(s.bursts) << 16 | u64::from(s.cargo) << 32 | u64::from(s.empty) << 48 | u64::from(s.reloading > 0) << 49; + let typed = u64::from(s.ground_rounds) ^ u64::from(s.ground_stock).rotate_left(17) ^ u64::from(s.air_48n6_stock).rotate_left(39) ^ u64::from(s.ground_request).rotate_left(55) ^ u64::from(s.ground_return).rotate_left(56) ^ u64::from(s.return_cargo).rotate_left(57); + let parts = s.comps.0.iter().fold(0u64, |a, v| a.wrapping_mul(131).wrapping_add((v * 100.0) as u64)); + ((s.health * 1000.0) as u64).wrapping_mul(1_000_003) ^ place ^ ammo.rotate_left(7) ^ parts.rotate_left(13) ^ typed.rotate_left(29) ^ (s.mode as u64).rotate_left(3) ^ (s.radar_role as u64).rotate_left(11) ^ u64::from(s.spent_cells).rotate_left(19) ^ u64::from(s.radar_on).rotate_left(23) + }) + .chain(sim.target_health.iter().map(|h| (h * 10.0) as u64)) + .fold(sim.sams.len() as u64, |a, h| a.wrapping_mul(31).wrapping_add(h)) + .wrapping_mul(31) + .wrapping_add(strikes) + // The air state's shape (#127): a launch, a loss, a new or finished route job makes the + // autosave take the raid as it stands, so a reload resumes it rather than an empty sky. + .wrapping_mul(31) + .wrapping_add(sim.air_mark()) +} + impl AirDef { /// The EW stations: on if none is, else off. fn toggle_ew(&mut self) { @@ -1082,11 +1192,11 @@ impl AirDef { /// Another world opened: the last one's state saved, a clean simulation with this one's loaded. fn open_world(&mut self, world: Option) { if self.world.is_some() && self.dirty { - self.save(); - } - if self.replay.is_some() { - self.stop_replay(); + // Leaving the world: the layout is kept, a raid still being planned is not (it would have + // no drones to resume and the world it belonged to is going). + self.save_with(false); } + self.end_session(presentation::ExitCause::WorldChange); let shader = self.sim.shader; let seed = game::get_clock().map_or(0x5eed, |c| c.game_time.wrapping_mul(2654435761) ^ 0x9e37); self.sim = Sim::new(seed); @@ -1148,7 +1258,7 @@ impl AirDef { /// Plays the last recorded raid: "map", "full", "reel" or (with `event`) a clip. fn start_replay(&mut self, what: &str, event: Option) -> Result<(), String> { - switch_allowed(self.export.is_some())?; + switch_allowed(self.presentation.is_exporting())?; let n = self.sim.replays.len(); if n == 0 { return Err("Повторов пока нет: запишется первый же налёт (когда небо очистится)".into()); @@ -1169,43 +1279,37 @@ impl AirDef { self.say_block_log(rec); let player = replay::Player::new(rec.number, mode, &self.sim); // A film (its quality and effects) or another replay ends properly before this one. - self.end_session(); - self.session += 1; - self.replay = Some(player); + self.end_session(presentation::ExitCause::Replaced); + self.presentation.replay(player); self.grid = false; input::claim_wheel(true); Ok(()) } fn stop_replay(&mut self) { - if self.export.take().is_some() { - render::capture_stop(true); + let was_export = self.presentation.is_exporting(); + self.end_session(presentation::ExitCause::UserStop); + if was_export { say("Экспорт отменён"); } - self.end_session(); } /// Ends whatever replay or film shows, on every path: the film quality, the effects, the /// clean HUD, the views and the time and camera overrides go back to the live game's. - fn end_session(&mut self) { - render::set_film_quality(false); - if let Some((shader, hd)) = self.film_prev.take() { - self.set_effects(shader, hd); - } - self.replay = None; - self.show_world_at(0, 0.0, false); - render::set_time_override(None); - render::set_main_view(None); - input::claim_wheel(false); - render::set_hud(&[]); - render::set_views(&[]); - self.monitor_shown = false; + fn end_session(&mut self, cause: presentation::ExitCause) { + let cleanup = if cause == presentation::ExitCause::WorldChange { + self.presentation.world_changed() + } else { + self.presentation.end(cause) + }; + let Some(cleanup) = cleanup else { return }; + cleanup.apply(self); } /// Plays the director's film of the last raid at the best quality: HD effects on (restored after). fn start_film(&mut self, which: &str, export: Option<&ExportOpts>) -> Result<(), String> { if export.is_none() { - switch_allowed(self.export.is_some())?; + switch_allowed(self.presentation.is_exporting())?; } let n = self.sim.replays.len(); if n == 0 { @@ -1226,9 +1330,8 @@ impl AirDef { player.limit = o.seconds; } // What was shown ends first (its effects are remembered below, not lost). - self.end_session(); - self.session += 1; - self.replay = Some(player); + self.end_session(presentation::ExitCause::Replaced); + self.presentation.replay(player); self.grid = false; input::claim_wheel(true); // The best picture: HD on, the renderer's film quality (full-size world, more cloud and fog steps). @@ -1261,7 +1364,7 @@ impl AirDef { o.name = a.chars().filter(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_')).collect(); } } - if self.export.is_some() { + if self.presentation.is_exporting() { return Err("Экспорт уже идёт (/airdef replay stop — отмена)".into()); } if o.name.is_empty() { @@ -1270,23 +1373,44 @@ impl AirDef { } let which = o.which.clone(); self.start_film(&which, Some(&o))?; + let state = self.presentation.take(); + let playback = match state { + presentation::State::Replay(player) => player, + other => { + self.presentation.restore(other); + self.end_session(presentation::ExitCause::Failed); + return Err("Не удалось передать фильм в захват".into()); + } + }; say(&format!("Экспорт «{}»: {} fps, {}x{}; планирую кадры…", o.name, o.fps, o.size.0, o.size.1)); - self.export = Some(Export { session: self.session, opts: o, stage: Stage::Planning, ticks: 0 }); + self.presentation.export(playback, Export { generation: self.presentation.generation(), opts: o, stage: Stage::Planning, ticks: 0 }); Ok(()) } /// Drives an export: plan, start the recorder, wait for its first frame, report, stop at the end. - fn export_step(&mut self, r: &mut replay::Player) { - let Some(ex) = &mut self.export else { return }; - if ex.session != self.session { + fn export_step(&mut self, r: &mut replay::Player, ex: &mut Export) -> Option> { + if ex.generation != self.presentation.generation() { // The film this export recorded was replaced: the recorder stops, nothing else drives it. - render::capture_stop(true); - self.export = None; say("Экспорт отменён: показ заменён другим"); - return; + return Some(Err("показ заменён другим".into())); } ex.ticks += 1; let st = render::capture_status(); + let owner = render::capture_owner_status(); + if ex.stage != Stage::Planning { + if let Some(problem) = capabilities::capture_problem(&owner) { + let message = if st.message.is_empty() { problem.to_string() } else { format!("{problem}: {}", st.message) }; + say(&format!("Экспорт не удался: {message}")); + r.done = true; + return Some(Err(message)); + } + if ex.stage == Stage::Arming && ex.ticks > 600 { + let message = "Движок не подтвердил подготовку захвата за 600 тиков".to_string(); + say(&message); + r.done = true; + return Some(Err(message)); + } + } let failed = |s: &render::CaptureState| s.phase == render::CapturePhase::Failed; let mut finish: Option> = None; match ex.stage { @@ -1306,12 +1430,13 @@ impl AirDef { Ok(()) => { say(&format!("Кадры готовы ({}); запись…", r.film_summary())); ex.stage = Stage::Arming; + ex.ticks = 0; } Err(e) => finish = Some(Err(e)), } } Stage::Arming => { - if st.phase == render::CapturePhase::Recording { + if st.phase == render::CapturePhase::Recording && capabilities::ready(&owner) { r.wait = false; ex.stage = Stage::Recording; ex.ticks = 0; @@ -1331,7 +1456,7 @@ impl AirDef { } } Stage::Finishing => match st.phase { - render::CapturePhase::Done => { + render::CapturePhase::Done if capabilities::ready(&owner) => { let note = if st.message.is_empty() { String::new() } else { format!(" ({})", st.message) }; finish = Some(Ok(format!("{}x{}, {} кадров: {}{note}", st.width, st.height, st.frames, st.file))); } @@ -1341,13 +1466,14 @@ impl AirDef { _ => {} } if let Some(res) = finish { - self.export = None; - match res { + match &res { Ok(m) => say(&format!("Экспорт готов: {m}")), Err(e) => say(&format!("Экспорт не удался: {e}")), } r.done = true; + return Some(res.map(|_| ())); } + None } /// What a raid strikes: the marked objects, else where the player stands. @@ -1419,6 +1545,23 @@ impl AirDef { }; } + fn deploy_configured(&mut self, key: &str) -> Result { + let (kind, mode, role) = deploy_configuration(key).ok_or("Неизвестный вид техники")?; + let p = player().ok_or("Нет игрока")?; + let d = view(&p); + let flat = DVec3::new(d.x, 0.0, d.z).normalize_or(DVec3::NEG_Z); + let at = DVec3::new(p.x, p.y, p.z) + flat * 8.0; + let id = self.sim.id(); + let mut sam = Sam::new(id, kind, at, (-flat.x).atan2(-flat.z)); + sam.set_mode(mode); + sam.set_radar_role(role); + if let Some(r) = self.sim.roles.get(&kind) { sam.roles = *r; } + self.sim.sams.push(sam); + self.sim.settle(id); + self.dirty = true; + Ok(format!("{} развёрнут ({}, {})", kind.name(), mode.key(), role.key())) + } + fn open_menu(&mut self, page: Page) { self.menu = Some(page); let onoff = |b: bool| if b { "вкл" } else { "выкл" }; @@ -1437,6 +1580,7 @@ impl AirDef { .collect(); widgets.push(Widget::List(ListWidget { id: MENU_KINDS, rows, selected: None })); let _ = &self.monitor; + widgets.push(Widget::Button(ButtonWidget { id: BTN_BATTERY, label: "Разместить элементы батареи…".into() })); widgets.push(Widget::Button(ButtonWidget { id: BTN_GRID, label: "Командный пункт: все комплексы (J)".into() })); let custom = self.sim.sams.iter().filter(|s| !s.doctrine.is_default()).count(); widgets.push(Widget::Button(ButtonWidget { id: BTN_DOCTRINE, label: format!("Доктрины: охрана, сектор, резерв (особых: {custom})…") })); @@ -1444,6 +1588,11 @@ impl AirDef { widgets.push(Widget::Button(ButtonWidget { id: BTN_REPLAYS, label: "Повторы…".into() })); widgets.push(Widget::Button(ButtonWidget { id: BTN_MORE, label: "Огонь, ремонт, эффекты…".into() })); } + Page::BatteryDeploy => { + title = "Размещение батареи — клик создаёт технику перед игроком".into(); + widgets.push(Widget::List(ListWidget { id: BATTERY_ROWS, rows: BATTERY_DEPLOY.iter().map(|(_, name)| (*name).to_string()).collect(), selected: None })); + widgets.push(Widget::Button(ButtonWidget { id: BTN_BACK, label: "← Назад".into() })); + } Page::Replays => { match self.sim.replays.last() { Some(rec) => { @@ -1641,6 +1790,28 @@ impl AirDef { } } +impl presentation::CleanupHost for AirDef { + fn apply_cleanup_action(&mut self, action: presentation::CleanupAction) { + match action { + presentation::CleanupAction::CancelCapture => render::capture_stop(true), + presentation::CleanupAction::ClearPendingSounds => self.pending.clear(), + presentation::CleanupAction::RestoreFilmQuality => render::set_film_quality(false), + presentation::CleanupAction::RestoreEffects => { + if let Some((shader, hd)) = self.film_prev.take() { + self.set_effects(shader, hd); + } + } + presentation::CleanupAction::RestoreWorldView => self.show_world_at(0, 0.0, false), + presentation::CleanupAction::ClearTimeOverride => render::set_time_override(None), + presentation::CleanupAction::ClearMainView => render::set_main_view(None), + presentation::CleanupAction::ReleaseWheel => input::claim_wheel(false), + presentation::CleanupAction::ClearHud => render::set_hud(&[]), + presentation::CleanupAction::ClearViews => render::set_views(&[]), + presentation::CleanupAction::HideMonitor => self.monitor_shown = false, + } + } +} + /// The help line: generated from the command tree (`commands.rs`), which the game checks lines with. fn help() -> String { commands::help() @@ -1660,12 +1831,10 @@ impl Mod for AirDef { grid: false, grid_ids: Vec::new(), wave: wave::WaveConfig::default(), - replay: None, + presentation: presentation::Session::default(), block_log: None, time_view: false, - export: None, film_prev: None, - session: 0, smoke: Default::default(), far: true, acoustics: None, @@ -1706,28 +1875,30 @@ impl Mod for AirDef { me.apply_far(); // Subcommands, typed arguments and the saved scenarios' names for the chat box (#98). commands::register(&scenario::saved_names()); - Ok((me, Registration::new().command("airdef").on_tick())) + Ok((me, Registration::new().command("airdef").on_tick().on_frame())) } fn tick(&mut self, tick: u64) { self.sim.engine_tick = tick; - let Some(p) = player() else { return }; // Each world keeps its own systems, objects and settings: loaded when it opens. let world = world_identity(); if self.world.as_ref() != Some(&world) { self.open_world(world); + // `open_world` replaces Sim and resets its host clock; restore this callback's tick. + self.sim.engine_tick = tick; } + let Some(p) = player() else { return }; for event in input::poll() { match event { Event::UseStart(u) => self.use_item(&u.item.id), Event::KeyDown(k) if k == "map" => self.map_toggle(), Event::KeyDown(k) if k == "menu" => { - if self.replay.is_some() { + if self.presentation.playback().is_some() { self.stop_replay(); } self.open_menu(Page::Main) } - Event::KeyDown(k) if k == "rp_exit" && self.replay.is_some() => self.stop_replay(), + Event::KeyDown(k) if k == "rp_exit" && self.presentation.playback().is_some() => self.stop_replay(), Event::KeyDown(k) if k.starts_with("rp_") => { let c = match k.as_str() { "rp_back" => replay::Control::Back, @@ -1736,7 +1907,7 @@ impl Mod for AirDef { "rp_slower" => replay::Control::Slower, _ => replay::Control::Pause, }; - if let Some(r) = &mut self.replay + if let Some(r) = self.presentation.playback_mut() && let Some(rec) = self.sim.replay(r.rec) { r.control(c, rec); @@ -1746,14 +1917,14 @@ impl Mod for AirDef { Event::KeyDown(k) if k == "grid" => self.grid = !self.grid, Event::KeyDown(k) if k == "ew" => self.toggle_ew(), Event::KeyDown(k) if k == "replay" => { - if self.replay.is_some() { + if self.presentation.playback().is_some() { self.stop_replay(); } else if let Err(e) = self.start_replay("map", None) { say(&e); } } Event::Wheel(w) => { - if let Some(r) = &mut self.replay + if let Some(r) = self.presentation.playback_mut() && let Some(rec) = self.sim.replay(r.rec) { r.wheel(w, rec); @@ -1802,7 +1973,9 @@ impl Mod for AirDef { self.sim.night = game::get_clock().is_some_and(|c| (13000..23000).contains(&(c.day_time % 24000))); self.sim.day_time = game::get_clock().map(|c| c.day_time); self.sim.step(&mut self.ground, Some(DVec3::new(p.x, p.y, p.z)), &mut out); - self.smoke.update(&self.sim); + let context = presentation::Context::live(self.presentation.generation(), self.world.clone(), self.sim.engine_tick, self.sim.tick, self.sim.day_time, self.listener); + let scene = scene::Snapshot::at_context(&context, scene::Source::Live(&self.sim)); + self.smoke.update(&scene); let wanted = self.ground.take_wanted(); if !wanted.is_empty() { render::query_ground(&wanted); @@ -1826,22 +1999,51 @@ impl Mod for AirDef { if !sections.is_empty() { render::query_sections(render::Policy::Collision, §ions); } - if let Some(mut r) = self.replay.take() { - // A replay shows the recording alone: not the live raid's models, particles, sounds or jolts. - if r.filming() { - out.models.clear(); - out.particles.clear(); - out.sounds.clear(); - out.engines.clear(); - out.shake = None; + let tick_context = self.presentation_context(); + match self.presentation.take() { + presentation::State::Live => {} + presentation::State::Replay(mut playback) => { + if playback.filming() { + out.models.clear(); + out.particles.clear(); + out.sounds.clear(); + out.engines.clear(); + out.engine_stops.clear(); + out.shake = None; + } + playback.tick(&self.sim, &mut self.ground, tick_context.listener, &mut out); + if playback.done { + self.presentation.restore(presentation::State::Replay(playback)); + self.end_session(presentation::ExitCause::Finished); + } else { + self.presentation.restore(presentation::State::Replay(playback)); + } } - r.tick(&self.sim, &mut self.ground, self.listener, &mut out); - self.export_step(&mut r); - if r.done { - self.replay = Some(r); - self.stop_replay(); - } else { - self.replay = Some(r); + presentation::State::Export { mut playback, mut export } => { + let progress = if export.generation != self.presentation.generation() { + Some(Err("показ заменён другим".into())) + } else { + if playback.filming() { + out.models.clear(); + out.particles.clear(); + out.sounds.clear(); + out.engines.clear(); + out.engine_stops.clear(); + out.shake = None; + } + playback.tick(&self.sim, &mut self.ground, tick_context.listener, &mut out); + self.export_step(&mut playback, &mut export) + }; + let cause = match progress { + Some(Ok(())) => Some(presentation::ExitCause::Finished), + Some(Err(_)) => Some(presentation::ExitCause::Failed), + None if playback.done => Some(presentation::ExitCause::Failed), + None => None, + }; + self.presentation.restore(presentation::State::Export { playback, export }); + if let Some(cause) = cause { + self.end_session(cause); + } } } self.emit(out); @@ -1850,14 +2052,15 @@ impl Mod for AirDef { self.map_frame(); self.draw_hud(DVec3::new(p.x, p.y, p.z), p.yaw); // The replay takes the screen; else the monitors and the cameras they show. - if let Some(r) = &self.replay { + let ctx = self.presentation_context(); + if let Some(r) = self.presentation.playback() { let size = render::gui_size(); render::set_hud(&r.hud(&self.sim, size)); render::set_views(&r.views(size)); // The world through the director's camera, with the full pipeline (HD, the post pass). render::set_main_view(r.filming().then(|| r.view_id())); // The sky of the raid, not of now (None for a raid whose clock was unknown). - render::set_time_override(r.day_time(&self.sim)); + render::set_time_override(ctx.day_time); self.show_world_at(r.rec, r.time(), r.filming()); self.monitor_shown = true; } else if self.grid { @@ -1889,19 +2092,7 @@ impl Mod for AirDef { } // Damage is saved too: a destroyed system stays destroyed. So are where the units stand // (or drive to: not every step of the way), their ammunition and the depots' stock. - let mark = self - .sim - .sams - .iter() - .map(|s| { - let at = s.dest.unwrap_or(s.pos); - let place = (at.x * 10.0) as i64 as u64 ^ ((at.z * 10.0) as i64 as u64).rotate_left(21) ^ (if s.dest.is_some() { 0 } else { (s.heading * 100.0) as i64 as u64 }).rotate_left(42); - let ammo = u64::from(s.missiles) | u64::from(s.bursts) << 16 | u64::from(s.cargo) << 32 | u64::from(s.empty) << 48 | u64::from(s.reloading > 0) << 49; - let parts = s.comps.0.iter().fold(0u64, |a, v| a.wrapping_mul(131).wrapping_add((v * 100.0) as u64)); - ((s.health * 1000.0) as u64).wrapping_mul(1_000_003) ^ place ^ ammo.rotate_left(7) ^ parts.rotate_left(13) - }) - .chain(self.sim.target_health.iter().map(|h| (h * 10.0) as u64)) - .fold(self.sim.sams.len() as u64, |a, h| a.wrapping_mul(31).wrapping_add(h)); + let mark = durable_mark(&self.sim); if mark != self.health_mark { self.health_mark = mark; self.dirty = true; @@ -1920,6 +2111,23 @@ impl Mod for AirDef { } return; } + (Page::Main, BTN_BATTERY) => { + self.open_menu(Page::BatteryDeploy); + return; + } + (Page::BatteryDeploy, BATTERY_ROWS) => { + if let Some((key, _)) = BATTERY_DEPLOY.get(row as usize) { + match self.deploy_configured(key) { + Ok(message) => say(&message), + Err(error) => say(&error), + } + } + return; + } + (Page::BatteryDeploy, BTN_BACK) => { + self.open_menu(Page::Main); + return; + } (Page::Main, BTN_DOCTRINE) => { self.open_menu(Page::Fleet); return; @@ -2108,6 +2316,17 @@ impl Mod for AirDef { self.menu = None; } + /// A frame runs outside a world too (the title screen), where no tick does: a film or an + /// export still active there is ended, so its capture cannot go on recording the title. + fn frame(&mut self, _seconds: f32) { + if self.presentation.is_live() { + return; + } + if let Some(cleanup) = self.presentation.world_left(world_identity().as_deref()) { + cleanup.apply(self); + } + } + fn command(&mut self, _name: &str, args: &str) -> Result, String> { let w: Vec<&str> = args.split_whitespace().collect(); let mut out = Out::default(); @@ -2116,6 +2335,7 @@ impl Mod for AirDef { self.open_menu(Page::Main); None } + ["capabilities"] => Some(capabilities::diagnostics()), ["monitor"] => { self.cycle_monitor(); None @@ -2128,24 +2348,35 @@ impl Mod for AirDef { self.give(); None } - ["deploy", kind] => { - // A system where the player stands, a few blocks ahead. - let kind = SamKind::from_key(kind) - .ok_or("Техника: patriot, gepard, pantsir, s400, s500, irist, nasams, buk, tor, p1sun, radar, depot, tzm, ew, light")?; - let p = player().ok_or("Нет игрока")?; - let d = view(&p); - let flat = DVec3::new(d.x, 0.0, d.z).normalize_or(DVec3::NEG_Z); - let at = DVec3::new(p.x, p.y, p.z) + flat * 8.0; - let sid = self.sim.id(); - let mut sam = Sam::new(sid, kind, at, (-flat.x).atan2(-flat.z)); - if let Some(r) = self.sim.roles.get(&kind) { - sam.roles = *r; - } - self.sim.sams.push(sam); - // Standing in the ground (a chunk that had not loaded as the player fell): it comes up to the surface. - self.sim.settle(sid); + ["deploy", kind] => Some(self.deploy_configured(kind)?), + ["battery"] => { + let status = self.sim.battery_status(); + for entry in status { say(&battery_view::status(entry)); } + Some("/airdef battery <номер> integrated|launcher; /airdef radar <номер> on|off|multi|search|engagement|height".into()) + } + ["battery", n, mode] => { + let i = n.parse::().ok().and_then(|n| n.checked_sub(1)).ok_or("Нужен номер комплекса")?; + let mode = sim::battery::SamMode::from_key(mode).ok_or("Режим: integrated или launcher")?; + let sam = self.sim.sams.get_mut(i).ok_or("Нет такого комплекса")?; + if !sam.set_mode(mode) { return Err("Отдельная пусковая доступна для С-400 и С-500".into()); } self.dirty = true; - Some(format!("{} развёрнут", kind.name())) + Some(format!("№{} {}: режим {}", i + 1, sam.kind.name(), mode.key())) + } + ["radar", n, state @ ("on" | "off")] => { + let i = n.parse::().ok().and_then(|n| n.checked_sub(1)).ok_or("Нужен номер комплекса")?; + let sam = self.sim.sams.get_mut(i).ok_or("Нет такого комплекса")?; + if sam.kind.spec().sensor <= 0.0 || sam.mode == sim::battery::SamMode::LauncherOnly { return Err("У комплекса нет активной РЛС".into()); } + sam.radar_on = *state == "on"; + self.dirty = true; + Some(format!("№{} {}: излучение РЛС {}", i + 1, sam.kind.name(), state)) + } + ["radar", n, role] => { + let i = n.parse::().ok().and_then(|n| n.checked_sub(1)).ok_or("Нужен номер РЛС")?; + let role = sim::battery::RadarRole::from_key(role).ok_or("Роль: multi, search, engagement или height")?; + let sam = self.sim.sams.get_mut(i).ok_or("Нет такого комплекса")?; + if !sam.set_radar_role(role) { return Err("Нужна отдельная РЛС".into()); } + self.dirty = true; + Some(format!("№{} РЛС: роль {}", i + 1, role.key())) } ["wave", kind, rest @ ..] => { let count = rest.first().and_then(|v| v.parse().ok()).unwrap_or(5u32).clamp(1, 80); @@ -2188,7 +2419,7 @@ impl Mod for AirDef { return Err("Нет такой техники".into()); } for c in self.sim.why(i) { - say(&format!("{} {}", if c.ok { "+" } else { "-" }, c.text)); + say(&format!("{} {}", if c.ok { "+" } else { "-" }, why_view::reason(&c.reason))); } None } @@ -2196,6 +2427,12 @@ impl Mod for AirDef { self.launch_wave(&mut out)?; None } + ["preset", n] => { + // The wave screen's preset button for scripts: the same apply() the UI cycles. + let n = n.parse::().map_err(|_| "Номер готового состава: число".to_string())?; + self.wave.apply(n); + Some(format!("Готовый: {}", wave::PRESETS[self.wave.preset].name)) + } ["sites"] => { // The launch sites the radars have found, and the strike missiles (#101). let p = player().ok_or("Нет игрока")?; @@ -2204,11 +2441,41 @@ impl Mod for AirDef { } None } + ["ground-stock", n, count] => { + let i = n.parse::().ok().and_then(|v| v.checked_sub(1)).ok_or("/airdef ground-stock <номер склада> <количество>")?; + let count = count.parse::().ok().filter(|v| *v <= 10000).ok_or("Количество: 0..10000")?; + let depot = self.sim.sams.get_mut(i).ok_or("Нет такого склада")?; + if depot.kind != SamKind::Depot || !depot.alive() { return Err("Нужен действующий склад".into()); } + depot.ground_stock = count; + self.dirty = true; + Some(format!("Sandbox: задан начальный запас склада №{}: RM48U {}", i + 1, count)) + } + ["ground-load", n] => { + let i = n.parse::().ok().and_then(|v| v.checked_sub(1)).ok_or("/airdef ground-load <номер комплекса>")?; + let message = self.sim.load_ground_round(i)?; + self.dirty = true; + Some(message) + } + ["ground-unload", n] => { + let i = n.parse::().ok().and_then(|v| v.checked_sub(1)).ok_or("/airdef ground-unload <номер комплекса>")?; + let message = self.sim.unload_ground_round(i)?; + self.dirty = true; + Some(message) + } + ["ground-cancel", n] => { + let i = n.parse::().ok().and_then(|v| v.checked_sub(1)).ok_or("/airdef ground-cancel <номер комплекса>")?; + let message = self.sim.cancel_ground_request(i)?; + self.dirty = true; + Some(message) + } ["strike", n] => { - // A strike missile at known site n, from where the player stands. + // A manual order: the simulation validates availability; player position is no launcher. let p = player().ok_or("Нет игрока")?; let id = n.parse::().map_err(|_| "/airdef strike <номер ПУ из /airdef sites>")?; - Some(self.sim.order_strike(id, DVec3::new(p.x, p.y, p.z), &mut out)?) + let said = self.sim.order_strike(id, DVec3::new(p.x, p.y, p.z), &mut out)?; + // The stock went down: the durable state must not forget it (#113). + self.dirty = true; + Some(said) } ["move", n] => { // Unit n (as the summary numbers them) drives to where the player stands. @@ -2334,7 +2601,7 @@ impl Mod for AirDef { self.sim.strikes.stunned, self.sim.strikes.missed, self.sim.strikes.shot_down, - self.ground.world.text() + world_view::text(&self.ground.world) )), _ => return Err(help()), }; @@ -2347,12 +2614,430 @@ horizon_mod_sdk::export_mod!(AirDef); #[cfg(test)] mod tests { + #[test] + fn battery_menu_presets_leave_existing_catalog_indices_intact() { + use super::*; + assert_eq!(sim::catalog::INVENTORY[0], "airdef:designator"); + assert_eq!(sim::catalog::UNITS[3].kind, SamKind::S400); + assert_eq!(sim::catalog::UNITS[4].kind, SamKind::S500); + assert_eq!(sim::catalog::UNITS[10].kind, SamKind::Radar); + for (key, _) in BATTERY_DEPLOY { + assert!(deploy_configuration(key).is_some()); + } + for kind in SamKind::ALL { + let (actual, mode, role) = deploy_configuration(kind.key()).unwrap(); + assert_eq!(actual, kind); + assert_eq!(mode, sim::battery::SamMode::Integrated); + assert_eq!(role, sim::battery::RadarRole::Multifunction); + } + } + use super::*; + fn test_mod() -> AirDef { + AirDef { + sim: Sim::new(1), + ground: Ground::default(), + dirty: false, + next_save: 0, + hud_shown: false, + monitor: None, + monitor_shown: false, + grid: false, + grid_ids: Vec::new(), + wave: wave::WaveConfig::default(), + presentation: presentation::Session::default(), + block_log: None, + time_view: false, + film_prev: None, + smoke: Default::default(), + far: true, + acoustics: None, + moving: None, + hd: false, + world: None, + health_mark: 0, + menu: None, + listener: DVec3::ZERO, + punch: 0.0, + pending: Vec::new(), + engines: engines::Engines::default(), + engine_gen: u32::MAX, + engines_replay: false, + model_ids: std::collections::HashMap::new(), + model_generation: u32::MAX, + by_id: Vec::new(), + map: map::MapUi::default(), + } + } + + #[test] + fn a_return_reservation_lost_with_the_launcher_keeps_a_loadable_save() { + let mut game = test_mod(); + let mut sam = Sam::new(100, SamKind::S400, DVec3::ZERO, 0.0); + sam.ground_rounds = 1; + sam.missiles = 3; + sam.ground_return = true; + sam.comps.0[sim::Part::Launcher as usize] = 0.0; + sam.fit_cells(); + game.sim.sams.push(sam); + let mut restored = test_mod(); + restored.apply_state(&game.state_text(false, false)).unwrap(); + assert_eq!(restored.sim.sams[0].ground_rounds, 0); + assert!(!restored.sim.sams[0].ground_return); + } + + #[test] + fn return_round_drives_and_survives_both_state_round_trips() { + struct Flat; + impl sim::world::WorldSource for Flat { + fn solid(&self, _: i32, y: i32, _: i32) -> Option { Some(y < 64) } + } + let mut game = test_mod(); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = game.sim.id(); + game.sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + game.sim.sams[1].ground_rounds = 1; + game.sim.sams[1].missiles = 3; + game.sim.unload_ground_round(1).unwrap(); + assert!(game.sim.sams[1].ground_return); + assert!(game.sim.load_ground_round(1).is_err()); + let saved = game.state_text(false, false); + let mut restored = test_mod(); + restored.apply_state(&saved).unwrap(); + game = restored; + assert!(game.sim.sams[1].ground_return); + let mut out = Out::default(); + let mut picked = false; + let mut far: f64 = 0.0; + for _ in 0..12000 { + let now = game.sim.tick; + let cols = game.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect(); + game.ground.answer(cols, now); + let sections = game.ground.world.take_wanted(now); + game.ground.world.serve(&Flat, sections, now); + game.sim.step(&mut game.ground, None, &mut out); + far = far.max(game.sim.sams[2].pos.x); + if !picked && game.sim.sams[2].return_cargo { + assert_eq!((game.sim.sams[1].ground_rounds, game.sim.sams[0].ground_stock), (0, 0)); + let saved = game.state_text(false, false); + let mut restored = test_mod(); + restored.apply_state(&saved).unwrap(); + game = restored; + assert!(game.sim.sams[2].return_cargo); + assert_eq!(game.sim.sams[2].ground_stock, 1); + picked = true; + } + if picked && game.sim.sams[0].ground_stock == 1 { break; } + out = Out::default(); + } + assert!(picked && far > 140.0); + assert_eq!((game.sim.sams[0].ground_stock, game.sim.sams[1].ground_rounds, game.sim.sams[2].ground_stock), (1, 0, 0)); + assert!(!game.sim.sams[2].return_cargo); + } + + #[test] + fn delivery_request_and_cargo_survive_full_state_round_trip() { + struct Flat; + impl sim::world::WorldSource for Flat { + fn solid(&self, _: i32, y: i32, _: i32) -> Option { Some(y < 64) } + } + let mut before = test_mod(); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 30.0)] { + let id = before.sim.id(); + before.sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + before.sim.sams[1].ground_request = true; + before.sim.sams[1].radar_on = false; + before.sim.sams[1].ground_rounds = 1; + before.sim.sams[1].missiles = 3; + before.sim.sams[2].ground_stock = 1; + before.sim.order_move(2, DVec3::new(100.0, 64.0, 0.0)); + let text = before.state_text(false, false); + let mut after = test_mod(); + after.apply_state(&text).unwrap(); + assert_eq!((after.sim.sams[0].ground_stock, after.sim.sams[1].ground_rounds, after.sim.sams[1].missiles), (0, 1, 3)); + assert!(after.sim.sams[1].ground_request); + assert!(!after.sim.sams[1].radar_on); + assert_eq!(after.sim.sams[2].ground_stock, 1); + assert!(after.sim.sams[2].moving()); + let mut out = Out::default(); + for _ in 0..6000 { + let now = after.sim.tick; + let cols = after.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect(); + after.ground.answer(cols, now); + let sections = after.ground.world.take_wanted(now); + after.ground.world.serve(&Flat, sections, now); + after.sim.step(&mut after.ground, None, &mut out); + if after.sim.sams[0].air_48n6_stock == 1 { break; } + out = Out::default(); + } + assert_eq!((after.sim.sams[1].ground_rounds, after.sim.sams[1].missiles), (2, 2)); + assert!(!after.sim.sams[1].ground_request); + assert_eq!((after.sim.sams[0].air_48n6_stock, after.sim.sams[2].ground_stock, after.sim.sams[2].air_48n6_stock), (1, 0, 0)); + } + + /// #128 end to end: A declares a return and drives off, B waits for a round; the truck must + /// serve B anyway (a save/load mid-flight included), and once A is back the actual return + /// completes. Rounds are conserved throughout. + #[test] + fn a_moving_return_claim_drives_and_supplies_and_survives_a_state_round_trip() { + struct Flat; + impl sim::world::WorldSource for Flat { + fn solid(&self, _: i32, y: i32, _: i32) -> Option { Some(y < 64) } + } + let mut game = test_mod(); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 120.0), (SamKind::S400, 240.0), (SamKind::Tzm, 0.0)] { + let id = game.sim.id(); + game.sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + game.sim.sams[0].ground_stock = 2; + game.sim.sams[1].ground_rounds = 1; + game.sim.sams[1].missiles = 3; + game.sim.unload_ground_round(1).unwrap(); + assert!(game.sim.sams[1].ground_return); + game.sim.sams[2].missiles = 3; + // B is out of the depot's reach: the load becomes a pending delivery request. + game.sim.load_ground_round(2).unwrap(); + assert!(game.sim.sams[2].ground_request); + // A drives away: its return claim is impossible for a while. + assert!(game.sim.order_move(1, DVec3::new(420.0, 64.0, 0.0))); + let mut out = Out::default(); + let mut restored_mid_flight = false; + let mut supplied = false; + for _ in 0..12000 { + let now = game.sim.tick; + let cols = game.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect(); + game.ground.answer(cols, now); + let sections = game.ground.world.take_wanted(now); + game.ground.world.serve(&Flat, sections, now); + game.sim.step(&mut game.ground, None, &mut out); + if !restored_mid_flight && game.sim.sams[3].ground_stock == 1 { + // The truck took a round for B: save, reload, continue from the restored state. + let saved = game.state_text(false, false); + let mut restored = test_mod(); + restored.apply_state(&saved).unwrap(); + game = restored; + restored_mid_flight = true; + } + if game.sim.sams[2].ground_rounds == 1 { supplied = true; break; } + out = Out::default(); + } + assert!(restored_mid_flight, "truck never picked up a round for B"); + assert!(supplied, "B starved while A's return was impossible"); + assert!(game.sim.sams[1].ground_return, "claim must survive the supply run"); + assert_eq!(game.sim.sams[0].ground_stock + game.sim.sams[3].ground_stock + game.sim.sams[1].ground_rounds + game.sim.sams[2].ground_rounds, 3); + // A returns to its place; the waiting claim is then collected and driven to the depot. + assert!(game.sim.order_move(1, DVec3::new(120.0, 64.0, 0.0))); + let mut returned = false; + for _ in 0..12000 { + let now = game.sim.tick; + let cols = game.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect(); + game.ground.answer(cols, now); + let sections = game.ground.world.take_wanted(now); + game.ground.world.serve(&Flat, sections, now); + game.sim.step(&mut game.ground, None, &mut out); + if game.sim.sams[0].ground_stock == 2 && game.sim.sams[1].ground_rounds == 0 { returned = true; break; } + out = Out::default(); + } + assert!(returned); + assert!(!game.sim.sams[1].ground_return); + assert!(!game.sim.sams[3].return_cargo); + assert_eq!(game.sim.sams[0].ground_stock + game.sim.sams[3].ground_stock + game.sim.sams[1].ground_rounds + game.sim.sams[2].ground_rounds, 3); + } + /// #83: while an export records no other replay or film replaces it; after it, they may. + #[test] + fn typed_ammunition_changes_mark_the_state_dirty() { + let mut sim = Sim::new(1); + sim.sams.push(Sam::new(1, SamKind::S400, DVec3::ZERO, 0.0)); + let before = durable_mark(&sim); + sim.sams[0].ground_rounds = 1; + assert_ne!(durable_mark(&sim), before); + let before = durable_mark(&sim); + sim.sams[0].ground_stock = 1; + assert_ne!(durable_mark(&sim), before); + } + + #[test] + fn battery_configuration_and_spent_cells_change_durable_mark() { + let mut sim = Sim::new(1); + sim.sams.push(Sam::new(1, SamKind::S400, DVec3::ZERO, 0.0)); + let before = durable_mark(&sim); + sim.sams[0].set_mode(sim::battery::SamMode::LauncherOnly); + assert_ne!(durable_mark(&sim), before); + let before = durable_mark(&sim); + sim.sams[0].spent_cells = 1; + assert_ne!(durable_mark(&sim), before); + sim.sams.push(Sam::new(2, SamKind::Radar, DVec3::ZERO, 0.0)); + let before = durable_mark(&sim); + sim.sams[1].set_radar_role(sim::battery::RadarRole::Search); + assert_ne!(durable_mark(&sim), before); + } + #[test] fn nothing_replaces_a_running_export() { assert!(switch_allowed(true).is_err()); assert!(switch_allowed(false).is_ok()); } + + #[test] + fn delayed_replay_audio_uses_recording_time_and_is_dropped_after_seek_back() { + let ctx = presentation::Context::playback( + presentation::Mode::Replay, + 7, + None, + 90_000, + 900, + 20.5, + None, + 0.25, + DVec3::ZERO, + true, + ); + let clock = ctx.sound_clock(); + let due = clock.deadline_after(5); + let pending = PendingSound { + due, + generation: ctx.generation, + created_at: clock, + name: "test".into(), + at: DVec3::ZERO, + volume: 1.0, + pitch: 1.0, + }; + + assert_eq!(due, 25.5); + assert!(!presentation::Clock::Recording(25.25).has_reached(due)); + assert!(!clock.has_reached(due), "slow playback has not advanced the delay yet"); + assert!(pending.invalidated_by(ctx.generation, presentation::Clock::Recording(19.0))); + assert!(pending.invalidated_by(ctx.generation + 1, clock)); + assert!(!pending.invalidated_by(ctx.generation, presentation::Clock::Recording(20.5))); + } + + #[test] + fn shown_effects_follow_the_recording_clock_not_the_combat_clock() { + let live_blast = sim::Blast { id: 1, at: DVec3::ZERO, size: 4.0, start: 900, life: 60 }; + let mut rec = sim::record::Recording::blank(100); + rec.events.push(sim::record::Event { + at: 12, + pos: DVec3::X, + kind: sim::record::EventKind::Hit { drone: 1, kind: DroneKind::Geran3, power: 8, object: None, sam: None }, + }); + let ctx = presentation::Context::playback( + presentation::Mode::Replay, + 5, + Some(Some("w".into())), + 10_000, + 10_001, + 12.5, + Some(13000), + 0.3, + DVec3::Z, + true, + ); + let record_scene = scene::Snapshot::at_context(&ctx, scene::Source::Recorded(&rec)); + let shown = Shown::from_context(&ctx, &record_scene); + assert!(shown.replay); + assert_eq!(shown.now, 12.5); + assert_eq!(shown.blasts.len(), 1); + assert_eq!(shown.blasts[0].id, 0x4000_0000); + assert!((shown.hold - 0.7).abs() < 1e-6); + + let map = presentation::Context::playback( + presentation::Mode::Replay, + 5, + Some(Some("w".into())), + 10_020, + 10_002, + 20.0, + Some(13000), + 1.0, + DVec3::Z, + false, + ); + let mut live = Sim::new(1); + live.tick = 10_002; + live.blasts.push(live_blast); + let map_scene = scene::Snapshot::at_context(&map, scene::Source::Live(&live)); + let shown_map = Shown::from_context(&map, &map_scene); + assert!(!shown_map.replay); + assert_eq!(shown_map.now, 10_002.0); + assert_eq!(shown_map.blasts[0].id, live_blast.id); + } + + #[test] + fn strike_stock_and_refill_change_the_durable_mark() { + let mut sim = Sim::new(7); + let full = durable_mark(&sim); + sim.strikes.stock -= 1; + assert_ne!(durable_mark(&sim), full, "spending a missile is not saved"); + + sim.strikes.progress = sim::strike::TUNE.resupply - 1; + let before = durable_mark(&sim); + let (mut ground, mut out) = (sim::Ground::default(), sim::Out::default()); + sim.step(&mut ground, None, &mut out); + assert_eq!(sim.strikes.stock, 3); + assert_ne!(durable_mark(&sim), before, "refilling a missile is not saved"); + + let checkpoint = durable_mark(&sim); + sim.strikes.progress = 1; + assert_eq!(durable_mark(&sim), checkpoint, "progress inside one checkpoint saves every tick"); + sim.strikes.progress = sim::strike::TUNE.progress_save; + assert_ne!(durable_mark(&sim), checkpoint, "a refill checkpoint is not saved"); + } + + /// #127: the air section of a save is written and read by the adapter, and a save without it (an + /// older one) still loads as before. The section itself is checked in the core's own tests; here + /// the contract is that the text the game writes is the text the next load restores, and that a + /// state with a sky replaces a state without one rather than merging into it. + #[test] + fn the_air_section_of_a_save_is_written_and_read_by_the_adapter() { + let zeros = "0 ".repeat(24); + let air = format!( + "air 1 120 2 0 1 1 1 1\n\ + airintel {zeros}\n\ + airglobal -\n\ + airrelief 0 0 0 16 8 u64\n\ + aircontact 32 s400 0 70 0 100 emission - - 0\n\ + airqueue 200 48 shahed 0 90 -600 0 1 0 0 70 0 cruise react 32 120 0 confirmed 0 0 0 0 0 0 false false true 0 - 0 - -\n\ + airjob 1 48 0 70 0 32 120 1 react 0\n" + ); + let saved = format!("sam {} 0 70 300 0\n{air}", SamKind::S400.key()); + let mut game = test_mod(); + game.apply_state(&saved).expect("a saved raid must load"); + assert_eq!(game.sim.tick, 120); + assert_eq!(game.sim.queue.len(), 1, "the launch queue is not restored"); + assert_eq!(game.sim.queue[0].0, 200); + assert_eq!(game.sim.drones.len(), 0); + let report = game.sim.enemy_picture(); + assert_eq!(report.pending.len(), 1, "the pending route job is not restored"); + assert_eq!(report.pending[0].intent, "react"); + assert_eq!(report.pending[0].unit, 32); + assert_eq!(report.contacts.len(), 1); + + // The game writes the same section back: what it read is what it saves. + let back = game.state_text(false, false); + assert!(back.contains("\nair 1 120 2 0 1 1 1 1\n"), "{back}"); + assert!(back.contains("\nairjob 1 48 0.00 70.00 0.00 32 120 1 react 0\n"), "{back}"); + assert!(back.contains("\nairqueue 200 48 "), "{back}"); + + // The air state is replaced, not merged: a save of a quiet world clears the sky. + let quiet = format!("sam {} 0 70 300 0\n", SamKind::S400.key()); + let quiet = format!("{quiet}air 1 300 2 0 0 0 0 0\nairintel {zeros}\nairglobal -\n"); + game.apply_state(&quiet).unwrap(); + assert!(game.sim.queue.is_empty() && game.sim.enemy_picture().pending.is_empty(), "an empty sky merged with the loaded one"); + + // An older save has no section at all: it loads its layout and leaves the sky as it was. + let mut old = test_mod(); + old.apply_state(&format!("sam {} 0 70 0 0\n", SamKind::S400.key())).unwrap(); + assert_eq!(old.sim.sams.len(), 1); + assert!(sim::Sim::parse_air("sam s400 0 70 0 0\n").unwrap().is_none()); + + // A scenario's body is a start state: it does not carry the sky. + let scenario = game.scenario_text("t", false); + assert!(!scenario.contains("\nair 1 "), "a scenario carries the air state"); + assert!(sim::Sim::parse_air(&scenario).unwrap().is_none()); + } } diff --git a/airdef-mod/src/map.rs b/airdef-mod/src/map.rs index bef71b6..2f205e8 100644 --- a/airdef-mod/src/map.rs +++ b/airdef-mod/src/map.rs @@ -104,6 +104,7 @@ enum Act { Reserve, Salvo, Role(u8), + RadarEmission, ResetDoc, Strike(u32), Centre, @@ -573,7 +574,7 @@ impl AirDef { self.map_close(); return; } - if self.replay.is_some() { + if self.presentation.playback().is_some() { say("Карта: сначала закройте повтор"); return; } @@ -1033,6 +1034,12 @@ impl AirDef { } ui.mode = Mode::View; } + Act::RadarEmission => { + if let Some(i) = self.sel_sam(ui) { + self.sim.sams[i].radar_on = !self.sim.sams[i].radar_on; + self.dirty = true; + } + } Act::Role(bit) => { if let Some(i) = self.sel_sam(ui) { let s = &mut self.sim.sams[i]; @@ -1047,6 +1054,8 @@ impl AirDef { let mut out = sim::Out::default(); match self.sim.order_strike(site, DVec3::new(p.x, p.y, p.z), &mut out) { Ok(m) => { + // The stock went down: the durable state must not forget it (#113). + self.dirty = true; say(&m); ui.note(tick, "Ударная ракета пошла"); } @@ -1418,13 +1427,13 @@ impl AirDef { }; self.map_sector(ui, &mut s, sam.pos, &d, sam.kind.range().max(200.0)); } - // Flights of the strike missiles. + // Flights of the strike missiles: the line to the target, the marker where the missile really is (#106). for f in &self.sim.strikes.flights { let a = ui.to_screen(f.from.x, f.from.z); let b = ui.to_screen(f.aim.x, f.aim.y); s.push(line(a, b, 1.0, alpha(0xFFA040, 0.5))); - let k = ((now.saturating_sub(f.start)) as f64 / (f.arrive.saturating_sub(f.start)).max(1) as f64).clamp(0.0, 1.0) as f32; - s.push(diamond(a.0 + (b.0 - a.0) * k, a.1 + (b.1 - a.1) * k, 3.0, ORANGE)); + let c = ui.to_screen(f.pos.x, f.pos.z); + s.push(diamond(c.0, c.1, 3.0, ORANGE)); } // Objects. if ui.filters[F_OBJECTS] { @@ -2320,14 +2329,31 @@ impl AirDef { let Some((n, sam)) = self.sim.sams.iter().enumerate().find(|(_, s)| s.id == id) else { return }; let k = sam.kind; lines.push((format!("{} №{}", k.name(), n + 1), CYAN)); + if k == SamKind::Radar { + lines.push((format!("Роль РЛС: {}", crate::battery_view::radar_role_name(sam.radar_role)), WHITE)); + } else if sam.mode == sim::battery::SamMode::LauncherOnly { + lines.push(("Отдельная пусковая: внешняя РЛС сопровождения".into(), WHITE)); + } + if k.spec().sensor > 0.0 && sam.mode != sim::battery::SamMode::LauncherOnly { + lines.push((format!("Излучение РЛС: {}", if sam.radar_on { "вкл" } else { "выкл" }), WHITE)); + btns.push((if sam.radar_on { "Выключить РЛС" } else { "Включить РЛС" }.into(), Act::RadarEmission, sam.radar_on)); + } lines.push((sam.status(), WHITE)); lines.push((format!("Исправен {:.0}% · {:.0} {:.0}", sam.health * 100.0, sam.pos.x, sam.pos.z), GREY)); + if k == SamKind::Depot { + lines.push((format!("Ударные RM48U: {} · снятые 48Н6: {}", sam.ground_stock, sam.air_48n6_stock), WHITE)); + lines.push((format!("Обычный БК ПВО: {} перезарядок", sam.cargo), WHITE)); + if sam.ground_stock == 0 { lines.push((format!("Начальный запас: /airdef ground-stock {} <число>", n + 1), GREY)); } + } else if k == SamKind::Tzm { + lines.push((format!("Груз RM48U: {} · 48Н6: {}{}", sam.ground_stock, sam.air_48n6_stock, if sam.return_cargo { " · возврат на склад" } else { "" }), WHITE)); + } if k.armed() { + if sam.kind == SamKind::S400 { lines.push((format!("Ударных RM48U: {} (общие ячейки с ПВО){}", sam.ground_rounds, if sam.ground_return { " · ожидает вывоза" } else if sam.ground_request { " · ожидает доставки" } else { "" }), WHITE)); } lines.push((format!("Боезапас: {} ракет, {} очередей{}", sam.missiles, sam.bursts, if sam.reloading > 0 { format!(" · перезарядка {} с", sam.reloading / 20) } else { String::new() }), WHITE)); lines.push((format!("Дальность огня: {}", distance(k.range())), WHITE)); } - if k.spec().sensor > 0.0 { - lines.push((format!("Свой обзор: {} (зелёное кольцо)", distance(k.spec().sensor)), WHITE)); + if let Some(sensor) = sam.radar_up().then(|| self.sim.low_sensor(k, sam.pos, Some(sam.id))).flatten() { + lines.push((format!("Свой обзор для низкой цели: {} (зелёное кольцо)", distance(sensor.reach)), WHITE)); } if let Some(t) = sam.gun_target.or(sam.target) { lines.push((format!("Ведёт цель: трек №{t}"), YELLOW)); @@ -2393,12 +2419,12 @@ impl AirDef { (None, _) => lines.push(("Данных нет".into(), GREY)), } lines.push((format!("Пусков замечено: {}; последний {} с назад", k.launches, now.saturating_sub(k.last) / 20), GREY)); - lines.push((format!("Ударных ракет: {}", self.sim.strikes.stock), WHITE)); + lines.push(("Для удара нужен готовый С-400 с RM48U".into(), GREY)); if k.aimed { lines.push(("Ракета по ней уже в полёте".into(), YELLOW)); } if k.est.is_some_and(|e| e.fix) && !matches!(k.state, SiteState::Destroyed(_)) { - btns.push(("Ударить по пусковой".into(), Act::Strike(id), false)); + btns.push(("Нанести удар".into(), Act::Strike(id), false)); } btns.push(("В центр".into(), Act::Centre, false)); } diff --git a/airdef-mod/src/monitor.rs b/airdef-mod/src/monitor.rs index 0642000..64a07c3 100644 --- a/airdef-mod/src/monitor.rs +++ b/airdef-mod/src/monitor.rs @@ -255,9 +255,10 @@ fn cell(sim: &Sim, index: usize, x: f32, y: f32, w: f32, h: f32, s: &mut Vec 0 || spec.key == "gepard" { - line += &format!("С{} ", sam.bursts); + if crate::sim::catalog::unit(sam.kind).has_gun() { + line += &format!("Оч{} ", sam.bursts); } line += &format!("сб.{} [{}]", sam.kills, roles_text(sam.roles)); s.push(text(fit(line, cw), cx, y + h - 10.0, 0xFF9FE0FF)); @@ -272,7 +273,7 @@ pub fn draw(sim: &Sim, index: usize) -> Vec { s.push(fill(4.0, 156.0, 300.0, 4.0 + 10.0 * checks.len() as f32, 0xA0080C0A)); for (k, c) in checks.iter().enumerate() { let color = if c.ok { 0xFF80FF90 } else { 0xFFFF9060 }; - s.push(text(fit(format!("{} {}", if c.ok { "+" } else { "-" }, c.text), 294.0), 7.0, 158.0 + 10.0 * k as f32, color)); + s.push(text(fit(format!("{} {}", if c.ok { "+" } else { "-" }, crate::why_view::reason(&c.reason)), 294.0), 7.0, 158.0 + 10.0 * k as f32, color)); } s } diff --git a/airdef-mod/src/panel.rs b/airdef-mod/src/panel.rs index 6aec1df..4bec565 100644 --- a/airdef-mod/src/panel.rs +++ b/airdef-mod/src/panel.rs @@ -13,10 +13,7 @@ const GREEN: u32 = 0x80FF90; /// The most lines the summary holds (the HUD's room). const MAX_LINES: usize = 16; -/// Metres (a block is a metre) as a short distance. -pub fn distance(m: f64) -> String { - if m >= 1000.0 { format!("{:.1} км", m / 1000.0) } else { format!("{:.0} м", m) } -} +pub use crate::sim::distance; /// Blocks a tick as km/h. fn kmh(v: DVec3) -> f64 { diff --git a/airdef-mod/src/presentation.rs b/airdef-mod/src/presentation.rs new file mode 100644 index 0000000..386b79a --- /dev/null +++ b/airdef-mod/src/presentation.rs @@ -0,0 +1,573 @@ +//! Presentation playback ownership, read-only view context and cleanup transitions. + +use glam::DVec3; + +/// The one active presentation mode. Export owns its playback so it cannot outlive its film. +pub enum State { + Live, + Replay(P), + Export { playback: P, export: E }, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ExitCause { + UserStop, + WorldChange, + Replaced, + Finished, + Failed, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Mode { + Live, + Replay, + Export, +} + +/// Clocks are deliberately tagged: recording time can seek or slow down without being confused +/// with the live simulation tick that ages durable combat state and live sound delays. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Clock { + Live(u64), + Recording(f64), +} + +impl Clock { + pub fn value(self) -> f64 { + match self { + Self::Live(tick) => tick as f64, + Self::Recording(tick) => tick, + } + } + + pub fn deadline_after(self, delay: u64) -> f64 { + self.value().max(0.0) + delay as f64 + } + + pub fn has_reached(self, deadline: f64) -> bool { + self.value() >= deadline + } + + pub fn same_domain(self, other: Self) -> bool { + matches!( + (self, other), + (Self::Live(_), Self::Live(_)) | (Self::Recording(_), Self::Recording(_)) + ) + } +} + +/// Immutable inputs for everything that presents a scene: clock, listener and environment. +/// The context is a snapshot; consumers cannot advance playback or mutate the live world through it. +#[derive(Clone, Debug, PartialEq)] +pub struct Context { + pub mode: Mode, + pub generation: u64, + pub world: Option>, + /// Combat/simulation time. + pub combat_tick: u64, + /// Host/UI callback time; it must not be substituted for recorded or combat time. + pub ui_tick: u64, + pub clock: Clock, + pub listener: DVec3, + pub day_time: Option, + /// Share of a live tick represented by the current presentation step. + pub rate: f32, + pub filming: bool, +} + +impl Context { + pub fn live( + generation: u64, + world: Option>, + ui_tick: u64, + combat_tick: u64, + day_time: Option, + listener: DVec3, + ) -> Self { + Self { + mode: Mode::Live, + generation, + world, + combat_tick, + ui_tick, + clock: Clock::Live(combat_tick), + listener, + day_time, + rate: 1.0, + filming: false, + } + } + + pub fn playback( + mode: Mode, + generation: u64, + world: Option>, + ui_tick: u64, + combat_tick: u64, + time: f64, + day_time: Option, + rate: f32, + listener: DVec3, + filming: bool, + ) -> Self { + debug_assert!(matches!(mode, Mode::Replay | Mode::Export)); + Self { + mode, + generation, + world, + combat_tick, + ui_tick, + clock: Clock::Recording(time), + listener, + day_time, + rate, + filming, + } + } + + pub fn is_playback(&self) -> bool { + !matches!(self.mode, Mode::Live) + } + + /// Map playback presents the current world view; only a filmed replay advances recorded effects. + pub fn display_clock(&self) -> Clock { + if self.is_playback() && self.filming { + self.clock + } else { + Clock::Live(self.combat_tick) + } + } + + /// The clock used to age audible delays: filmed playback follows its recording; live and map + /// playback follow the current combat timeline. + pub fn sound_clock(&self) -> Clock { + self.display_clock() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CleanupAction { + CancelCapture, + ClearPendingSounds, + RestoreFilmQuality, + RestoreEffects, + RestoreWorldView, + ClearTimeOverride, + ClearMainView, + ReleaseWheel, + ClearHud, + ClearViews, + HideMonitor, +} + +/// A cleanup plan is produced once when an active presentation ends. A successful export has +/// already drained its capture, so its plan omits `CancelCapture`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Cleanup { + pub actions: Vec, +} + +pub trait CleanupHost { + fn apply_cleanup_action(&mut self, action: CleanupAction); +} + +impl Cleanup { + pub fn apply(&self, host: &mut impl CleanupHost) { + for &action in &self.actions { + host.apply_cleanup_action(action); + } + } +} + +pub struct Session { + state: State, + generation: u64, +} + +impl Default for Session { + fn default() -> Self { + Self { + state: State::Live, + generation: 0, + } + } +} + +impl Session { + pub fn is_live(&self) -> bool { + matches!(self.state, State::Live) + } + + pub fn is_exporting(&self) -> bool { + matches!(self.state, State::Export { .. }) + } + + pub fn generation(&self) -> u64 { + self.generation + } + + pub fn playback(&self) -> Option<&P> { + match &self.state { + State::Live => None, + State::Replay(player) + | State::Export { + playback: player, .. + } => Some(player), + } + } + + pub fn playback_mut(&mut self) -> Option<&mut P> { + match &mut self.state { + State::Live => None, + State::Replay(player) + | State::Export { + playback: player, .. + } => Some(player), + } + } + + pub fn replay(&mut self, playback: P) { + assert!( + self.is_live(), + "end the current presentation before replacing it" + ); + self.generation = self.generation.wrapping_add(1).max(1); + self.state = State::Replay(playback); + } + + /// Promote the current replay into an export without changing its generation. + pub fn export(&mut self, playback: P, export: E) { + assert!( + self.is_live(), + "end the current presentation before starting export" + ); + assert!( + self.generation != 0, + "an export must belong to a started presentation" + ); + self.state = State::Export { playback, export }; + } + + /// Temporarily move the active state out while a tick mutates the player and export together. + pub fn take(&mut self) -> State { + std::mem::replace(&mut self.state, State::Live) + } + + pub fn restore(&mut self, state: State) { + assert!(self.is_live(), "restore only into a detached live session"); + self.state = state; + } + + /// Return no plan for an already-live session, so repeated stop/unload paths cannot repeat host cleanup. + pub fn end(&mut self, cause: ExitCause) -> Option { + let was_export = self.is_exporting(); + if self.is_live() { + return None; + } + let mut actions = Vec::with_capacity(11); + if was_export && cause != ExitCause::Finished { + actions.push(CleanupAction::CancelCapture); + } + actions.extend([ + CleanupAction::ClearPendingSounds, + CleanupAction::RestoreFilmQuality, + CleanupAction::RestoreEffects, + CleanupAction::RestoreWorldView, + CleanupAction::ClearTimeOverride, + CleanupAction::ClearMainView, + CleanupAction::ReleaseWheel, + CleanupAction::ClearHud, + CleanupAction::ClearViews, + CleanupAction::HideMonitor, + ]); + self.state = State::Live; + self.generation = self.generation.wrapping_add(1).max(1); + Some(Cleanup { actions }) + } + + /// The world is gone (the title screen): the game stops ticking there, so an active export + /// would otherwise go on recording the title with its playback clock frozen. Nothing to do + /// in Live, and a world still open is not a leave. + pub fn world_left(&mut self, world: Option<&str>) -> Option { + if self.is_live() || world.is_some() { + return None; + } + self.end(ExitCause::WorldChange) + } + + /// A world change invalidates live work too. Active playback uses the normal cleanup path, + /// which advances generation exactly once; Live advances generation without host cleanup. + pub fn world_changed(&mut self) -> Option { + if self.is_live() { + self.generation = self.generation.wrapping_add(1).max(1); + None + } else { + self.end(ExitCause::WorldChange) + } + } +} + +#[cfg(test)] +mod tests { + use super::{CleanupAction, CleanupHost, Clock, Context, ExitCause, Mode, Session, State}; + use glam::DVec3; + + #[derive(Default)] + struct HostFixture { + actions: Vec, + } + + impl HostFixture { + fn apply(&mut self, cleanup: &super::Cleanup) { + cleanup.apply(self); + } + } + + impl CleanupHost for HostFixture { + fn apply_cleanup_action(&mut self, action: CleanupAction) { + self.actions.push(action); + } + } + + #[test] + fn playback_context_uses_recording_clock_and_captured_environment() { + let live = Context::live( + 4, + Some(Some("world-a".into())), + 890, + 900, + Some(12000), + DVec3::X, + ); + assert_eq!(live.clock, Clock::Live(900)); + let replay = Context::playback( + Mode::Replay, + 5, + live.world.clone(), + 9010, + 901, + 31.25, + Some(13000), + 0.3, + DVec3::Z, + true, + ); + let slow = Context::playback( + Mode::Replay, + 5, + live.world.clone(), + 9020, + 902, + 31.55, + Some(13000), + 0.3, + DVec3::Z, + true, + ); + assert_eq!(replay.clock, Clock::Recording(31.25)); + assert_eq!(replay.sound_clock(), Clock::Recording(31.25)); + assert_eq!(replay.day_time, Some(13000)); + assert_eq!(replay.listener, DVec3::Z); + assert_eq!(slow.clock, Clock::Recording(31.55)); + let map = Context::playback( + Mode::Replay, + 5, + live.world.clone(), + 9011, + 901, + 31.25, + Some(13000), + 1.0, + DVec3::Z, + false, + ); + assert_eq!(map.sound_clock(), Clock::Live(901)); + assert!(replay.is_playback()); + assert_ne!(replay.clock.value(), live.clock.value()); + assert_eq!(replay.generation, slow.generation); + } + + #[test] + fn seek_and_slow_motion_change_only_the_recording_clock() { + let at = |live_tick, record_tick, rate| { + Context::playback( + Mode::Replay, + 12, + Some(Some("w".into())), + live_tick * 10, + live_tick, + record_tick, + Some(8000), + rate, + DVec3::ZERO, + true, + ) + }; + let before = at(400, 80.0, 1.0); + let seek = at(401, 20.0, 0.0); + let slow = at(402, 20.3, 0.3); + assert_eq!(before.display_clock(), Clock::Recording(80.0)); + assert_eq!(seek.display_clock(), Clock::Recording(20.0)); + assert_eq!(slow.display_clock(), Clock::Recording(20.3)); + assert_eq!(slow.sound_clock(), Clock::Recording(20.3)); + assert_eq!(slow.sound_clock().deadline_after(5), 25.3); + assert!(!slow.sound_clock().has_reached(25.3)); + assert!(Clock::Recording(25.5).has_reached(25.5)); + assert_eq!((seek.ui_tick, slow.ui_tick), (4010, 4020)); + assert_eq!((seek.combat_tick, slow.combat_tick), (401, 402)); + assert_eq!(seek.world, slow.world); + assert_eq!(seek.day_time, slow.day_time); + assert_eq!(seek.generation, slow.generation); + assert_eq!( + seek.clock, + Clock::Recording(20.0), + "a context snapshot is not advanced by live ticks" + ); + } + + #[test] + fn world_change_invalidates_live_generation_and_cleans_active_once() { + let mut session = Session::::default(); + let initial = session.generation(); + assert!( + session.world_changed().is_none(), + "Live has no presentation resources to clean up" + ); + assert_ne!(session.generation(), initial); + + session.replay(1); + let active = session.generation(); + let cleanup = session + .world_changed() + .expect("active playback needs cleanup"); + assert_ne!(session.generation(), active); + let mut host = HostFixture::default(); + host.apply(&cleanup); + let action_count = host.actions.len(); + assert_eq!( + host.actions + .iter() + .filter(|a| **a == CleanupAction::ClearPendingSounds) + .count(), + 1 + ); + + assert!( + session.world_changed().is_none(), + "a second call sees Live state" + ); + assert_eq!(host.actions.len(), action_count, "cleanup is not repeated"); + } + + #[test] + fn restart_gets_new_generation_and_export_keeps_its_parent_generation() { + let mut session = Session::::default(); + session.replay(1); + let first = session.generation(); + assert_ne!(first, 0); + let State::Replay(player) = session.take() else { + panic!("replay state lost") + }; + session.export(player, "capture"); + assert_eq!(session.generation(), first); + let _ = session.end(ExitCause::UserStop); + session.replay(2); + assert_ne!(session.generation(), first); + } + + #[test] + fn a_gone_world_ends_an_active_export_once() { + let mut session = Session::<(), ()>::default(); + assert!(session.world_left(None).is_none(), "Live has nothing to clean up"); + session.replay(()); + assert!(session.world_left(Some("world:w")).is_none(), "the world is still open"); + let State::Replay(playback) = session.take() else { + panic!("replay state lost") + }; + session.export(playback, ()); + let cleanup = session.world_left(None).expect("the title screen ends the export"); + assert!(cleanup.actions.contains(&CleanupAction::CancelCapture)); + + let mut host = HostFixture::default(); + host.apply(&cleanup); + let count = host.actions.len(); + assert!(session.world_left(None).is_none(), "a second frame sees Live"); + assert_eq!(host.actions.len(), count, "cleanup is not repeated"); + assert_eq!( + host.actions.iter().filter(|a| **a == CleanupAction::CancelCapture).count(), + 1 + ); + } + + #[test] + fn end_and_restart_emit_cleanup_once_for_stop_unload_failure_and_success() { + for cause in [ + ExitCause::UserStop, + ExitCause::WorldChange, + ExitCause::Failed, + ExitCause::Finished, + ] { + let mut session = Session::<(), ()>::default(); + session.replay(()); + let State::Replay(()) = session.take() else { + panic!("replay state lost") + }; + session.restore(State::Replay(())); + let mut host = HostFixture::default(); + let cleanup = session.end(cause).expect("active session must clean up"); + host.apply(&cleanup); + assert!( + session.end(cause).is_none(), + "second end repeated cleanup for {cause:?}" + ); + for action in cleanup.actions { + assert_eq!( + host.actions.iter().filter(|a| **a == action).count(), + 1, + "{action:?} for {cause:?}" + ); + } + assert!( + !host.actions.contains(&CleanupAction::CancelCapture), + "a replay has no capture to cancel" + ); + } + } + + #[test] + fn export_cleanup_cancels_only_interrupted_captures_and_success_drains() { + for (cause, cancels) in [ + (ExitCause::UserStop, true), + (ExitCause::WorldChange, true), + (ExitCause::Failed, true), + (ExitCause::Finished, false), + ] { + let mut session = Session::<(), ()>::default(); + session.replay(()); + let State::Replay(playback) = session.take() else { + panic!("replay state lost") + }; + session.export(playback, ()); + let cleanup = session.end(cause).expect("export must clean up"); + let mut host = HostFixture::default(); + host.apply(&cleanup); + assert_eq!( + host.actions.contains(&CleanupAction::CancelCapture), + cancels, + "{cause:?}" + ); + for action in cleanup.actions { + assert_eq!( + host.actions.iter().filter(|a| **a == action).count(), + 1, + "{action:?} for {cause:?}" + ); + } + assert!( + session.end(cause).is_none(), + "second end repeated cleanup for {cause:?}" + ); + } + } +} diff --git a/airdef-mod/src/radar.rs b/airdef-mod/src/radar.rs index d47d8ff..8767595 100644 --- a/airdef-mod/src/radar.rs +++ b/airdef-mod/src/radar.rs @@ -139,9 +139,7 @@ pub fn draw(sim: &Sim, player: DVec3, yaw: f32) -> Vec { } // Strike missiles in flight, on the same footing: a small bright dot where the scope reaches, else nothing. for f in &sim.strikes.flights { - let t = (sim.tick.saturating_sub(f.start)) as f64 / (f.arrive - f.start).max(1) as f64; - let p = f.from.lerp(DVec3::new(f.aim.x, f.from.y, f.aim.y), t.min(1.0)); - if let Some((x, y, _)) = scope(p) { + if let Some((x, y, _)) = scope(f.pos) { s.push(square(x, y, 1.5, 0xFFFFFFA0)); } } diff --git a/airdef-mod/src/replay.rs b/airdef-mod/src/replay.rs index 716f688..25742b4 100644 --- a/airdef-mod/src/replay.rs +++ b/airdef-mod/src/replay.rs @@ -4,6 +4,8 @@ //! world and a director's camera (a full-screen camera view) follows them — //! behind the drone, then behind the interceptor or from the side — slowing //! time round the moment itself. +use crate::sim::catalog::sound; + use crate::film::{self, Film}; use crate::panel::distance; use crate::sim::Ground; @@ -145,6 +147,7 @@ fn strike_word(result: StrikeResult) -> &'static str { StrikeResult::Missed => "промах", StrikeResult::Intercepted => "ракета сбита ПВО противника", StrikeResult::Halted => "пуски прекращены (вне карты)", + StrikeResult::Blocked => "разбилась о рельеф", } } @@ -496,9 +499,23 @@ impl Player { } } What::Missile(kind, _) => { - if self.t - tr.first as f64 > kind.lit_after() as f64 { + if rec.missile_is_lit(tr, self.t, kind.lit_after()) { let (event, gain, pitch) = kind.engine_voice(); out.engine(key, event, at, vel, gain, pitch, false); + } else { + out.engine_stops.push(key); + } + } + What::StrikeCarrier(_) => { + if self.t - tr.first as f64 >= crate::sim::strike::EJECT_TICKS as f64 { + let (event, gain, pitch) = crate::sim::MissileKind::M48n6.engine_voice(); + out.engine(key, event, at, vel, gain, pitch, false); + } + } + What::Strike => { + // A counter-strike's motor from its launch, as the live flight has it (#109). + if let Some((event, pitch)) = DroneKind::Kalibr.engine_voice() { + out.engine(key, event, at, vel, 0.8, pitch, false); } } } @@ -511,11 +528,11 @@ impl Player { for e in rec.events.iter().filter(|e| (self.heard..self.t).contains(&(e.at as f64))) { let event = match e.kind { EventKind::Kill { kind, outcome, .. } => crate::sim::outcome::look(kind, outcome).3, - EventKind::Hit { power, .. } | EventKind::Crash { power, .. } => ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][(power as usize).min(4)], - EventKind::SamLost { .. } => "airdef:blast.big", - EventKind::Split { .. } => "airdef:boom.sonic", + EventKind::Hit { power, .. } | EventKind::Crash { power, .. } => [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][(power as usize).min(4)], + EventKind::SamLost { .. } => sound::BLAST_BIG, + EventKind::Split { .. } => sound::BOOM_SONIC, EventKind::Strike { result: StrikeResult::Halted, .. } => continue, - EventKind::Strike { result, .. } => ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][match result { + EventKind::Strike { result, .. } => [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][match result { StrikeResult::Destroyed => 4, StrikeResult::Intercepted => 0, _ => 3, @@ -524,17 +541,41 @@ impl Player { let gain = if let EventKind::Kill { kind, outcome, .. } = e.kind { crate::sim::outcome::look(kind, outcome).2 } else { 1.0 }; out.sound(event, heard(e.pos), gain, 1.0); if let EventKind::Kill { outcome: crate::sim::outcome::Outcome::AirDetonation, .. } = e.kind { - out.sound("airdef:boom.sonic", heard(e.pos), 0.8, 1.2); + out.sound(sound::BOOM_SONIC, heard(e.pos), 0.8, 1.2); + } + } + // New recordings carry the exact launch and ignition sounds and their actual positions. + if let Some(scene) = &rec.scene { + for e in scene.events().iter().filter(|e| (self.heard..self.t).contains(&(e.at as f64))) { + match e.kind { + crate::sim::record::SceneKind::MissileLaunch { sound, pitch, .. } => out.sound(sound, heard(e.pos), 1.0, pitch), + crate::sim::record::SceneKind::MotorIgnition { sound: Some(sound), .. } => out.sound(sound, heard(e.pos), 1.0, 1.0), + _ => {} + } } } for tr in rec.tracks.iter().filter(|tr| (self.heard..self.t).contains(&(tr.first as f64))) { - if let What::Missile(kind, _) = tr.what + let event = match tr.what { + What::Missile(kind, _) if rec.missile_launch(tr.id).is_none() => Some(kind.launch_sound()), + What::Missile(..) => None, + What::StrikeCarrier(_) => Some(crate::sim::MissileKind::M48n6.launch_sound()), + What::Strike => Some(DroneKind::Kalibr.launch_sound()), + What::Drone(_) => None, + }; + if let Some(event) = event && let Some(p) = tr.at(rec.origin, tr.first as f64) { - out.sound(kind.launch_sound(), heard(p), 1.0, 1.0); + out.sound(event, heard(p), 1.0, 1.0); } } - self.heard = self.t; + // Cold-ejected physical strikes light only after the same delay as live playback. + for tr in rec.tracks.iter().filter(|tr| matches!(tr.what, What::StrikeCarrier(_))) { + let at = tr.first as f64 + crate::sim::strike::EJECT_TICKS as f64; + if (self.heard..self.t).contains(&at) && let Some(p) = tr.at(rec.origin, at) { + out.sound(sound::LAUNCH_S400, heard(p), 1.0, 1.0); + } + } + self.heard = self.t; } /// One tick of the film: its clock runs, the plan gives the camera and the replay's moment. @@ -930,6 +971,7 @@ impl Player { What::Drone(kind) if kind.spec().decoy => (0x70A0A0A0, 0xFFC0C0C0), What::Drone(_) => (0x90FF4040, 0xFFFF6060), What::Missile(..) => (0x90FFD040, 0xFFFFE070), + What::Strike | What::StrikeCarrier(_) => (0x90FFA040, 0xFFFFC060), }; let upto = ((self.t - tr.first as f64) / STEP as f64).floor(); if upto < 0.0 { @@ -1094,6 +1136,94 @@ mod engine_tests { let _ = rec; p } + #[test] + fn recorded_launch_and_ignition_sounds_use_exact_events_without_duplicates() { + use crate::sim::record::{SceneEvent, SceneJournal, SceneKind}; + let mut rec = Recording::blank(100); + rec.straight(2, What::Missile(MissileKind::M48n6, 5), 20, 80, DVec3::new(0.0, 66.0, 0.0), DVec3::new(0.0, 70.0, 300.0)); + let mut journal = SceneJournal::default(); + journal.fixture_push(SceneEvent { at: 19, pos: DVec3::new(2.0, 66.0, 0.0), kind: SceneKind::MissileLaunch { missile: 2, sam: 5, target: 1, kind: MissileKind::M48n6, sound: "airdef:launch.s400_eject", pitch: 1.0 } }); + journal.fixture_push(SceneEvent { at: 24, pos: DVec3::new(2.0, 75.0, 0.0), kind: SceneKind::MotorIgnition { missile: 2, kind: MissileKind::M48n6, sound: Some("airdef:launch.s400") } }); + journal.fixture_push(SceneEvent { at: 50, pos: DVec3::new(2.0, 75.0, 100.0), kind: SceneKind::MotorBurnout { missile: 2, kind: MissileKind::M48n6 } }); + rec.scene = Some(journal); + assert!(rec.missile_is_lit(&rec.tracks[0], 49.0, 5)); + assert!(!rec.missile_is_lit(&rec.tracks[0], 50.0, 5)); + assert!(rec.missile_is_lit(&rec.tracks[0], 49.0, 5), "seek back restores the recorded state"); + let mut p = player(&rec); + p.rate = 1.0; + p.t = 50.0; + let mut burnout = Out::default(); + p.engines(&rec, DVec3::ZERO, false, &mut burnout); + assert!(burnout.engine_stops.contains(&0x8000_0000)); + let mut out = Out::default(); + p.heard = 18.0; + p.t = 21.0; + p.sounds(&rec, DVec3::ZERO, &mut out); + assert_eq!(out.sounds.len(), 1, "the first track sample must not repeat the launch"); + assert_eq!(out.sounds[0].0, "airdef:launch.s400_eject"); + out.sounds.clear(); + p.t = 25.0; + p.sounds(&rec, DVec3::ZERO, &mut out); + assert_eq!(out.sounds.len(), 1); + assert_eq!(out.sounds[0].0, "airdef:launch.s400"); + assert!(!rec.missile_is_lit(&rec.tracks[0], 23.0, 5)); + assert!(rec.missile_is_lit(&rec.tracks[0], 24.0, 5)); + out.sounds.clear(); + p.heard = 18.0; + p.t = 25.0; + p.sounds(&rec, DVec3::ZERO, &mut out); + assert_eq!(out.sounds.iter().map(|s| s.0).collect::>(), vec!["airdef:launch.s400_eject", "airdef:launch.s400"]); + } + + #[test] + fn counter_strike_launch_is_heard_once_and_can_be_replayed_after_seek() { + let mut rec = Recording::blank(100); + rec.straight(1, What::Strike, 10, 70, DVec3::ZERO, DVec3::new(300.0, 70.0, 0.0)); + let mut p = player(&rec); + p.heard = 9.0; + p.t = 11.0; + let mut out = Out::default(); + p.sounds(&rec, DVec3::ZERO, &mut out); + assert_eq!(out.sounds.len(), 1); + assert_eq!(out.sounds[0].0, DroneKind::Kalibr.launch_sound()); + out.sounds.clear(); + p.t = 12.0; + p.sounds(&rec, DVec3::ZERO, &mut out); + assert!(out.sounds.is_empty(), "the loop engine must not repeat the launch"); + p.heard = 9.0; // seek/cut resets the one-shot cursor + p.t = 11.0; + p.sounds(&rec, DVec3::ZERO, &mut out); + assert_eq!(out.sounds.len(), 1); + } + + #[test] + fn physical_strike_replay_delays_ignition_and_resets_after_seek() { + let mut rec = Recording::blank(100); + rec.straight(1, What::StrikeCarrier(99), 10, 70, DVec3::ZERO, DVec3::new(0.0, 70.0, 300.0)); + let mut p = player(&rec); + p.rate = 1.0; + p.heard = 9.0; + p.t = 11.0; + let mut out = Out::default(); + p.sounds(&rec, DVec3::ZERO, &mut out); + p.engines(&rec, DVec3::ZERO, false, &mut out); + assert_eq!(out.sounds.iter().map(|s| s.0).collect::>(), vec!["airdef:launch.s400_eject"]); + assert!(out.engines.is_empty()); + out = Out::default(); + p.t = 10.0 + crate::sim::strike::EJECT_TICKS as f64 + 1.0; + p.sounds(&rec, DVec3::ZERO, &mut out); + p.engines(&rec, DVec3::ZERO, false, &mut out); + assert_eq!(out.sounds.iter().map(|s| s.0).collect::>(), vec!["airdef:launch.s400"]); + assert_eq!(out.engines.len(), 1); + out = Out::default(); + p.sounds(&rec, DVec3::ZERO, &mut out); + assert!(out.sounds.is_empty(), "held frames must not repeat ignition"); + p.heard = 9.0; + p.t = 11.0; + p.engines(&rec, DVec3::ZERO, false, &mut out); + assert!(out.engine_cut && out.engines.is_empty(), "seek before ignition releases motor voice"); + } + /// A host that remembers which sources run. #[derive(Default)] struct Host(std::collections::BTreeMap); diff --git a/airdef-mod/src/scenario.rs b/airdef-mod/src/scenario.rs index 34b6f7a..4049134 100644 --- a/airdef-mod/src/scenario.rs +++ b/airdef-mod/src/scenario.rs @@ -36,9 +36,10 @@ fn names() -> Vec { impl AirDef { /// The scenario as text. - fn scenario_text(&self, name: &str, fresh: bool) -> String { + pub(crate) fn scenario_text(&self, name: &str, fresh: bool) -> String { let mut text = format!("scenario {VERSION}\nname {name}\nworld {}\n", self.state_key()); - text += &self.state_text(fresh, false); + // A scenario is a start state: its body is the layout, not the raid a save happened to catch. + text += &self.state_text_with(fresh, false, false); text += &format!("seed {}\nwave {}\n", self.sim.rng.state(), self.wave.text()); text } @@ -61,7 +62,7 @@ impl AirDef { } // The whole body is read and checked before anything is touched (#86). let layout = layout::Layout::parse(text).map_err(|e| format!("Сценарий не загружен, сцена осталась прежней: {e}"))?; - if self.replay.is_some() { + if self.presentation.playback().is_some() { self.stop_replay(); } // Everything of the last layout goes: its systems with their jobs and targets, the sky, diff --git a/airdef-mod/src/scene.rs b/airdef-mod/src/scene.rs new file mode 100644 index 0000000..35502e3 --- /dev/null +++ b/airdef-mod/src/scene.rs @@ -0,0 +1,329 @@ +//! Read-only scene data shared by live rendering and recorded playback adapters. + +use crate::{presentation::Context, sim}; +use glam::DVec3; +use std::borrow::Cow; + +pub enum Source<'a> { + Live(&'a sim::Sim), + Recorded(&'a sim::record::Recording), + Empty, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MoverKind { + Drone(sim::DroneKind), + Missile { kind: sim::MissileKind, engine_lit: bool }, + Strike { physical: bool, engine_lit: bool }, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Mover { + /// Stable identity within its `group`. + pub id: u32, + /// The old live-smoke trail categories: drone, interceptor, counter-strike. + pub group: u8, + pub kind: MoverKind, + pub pos: DVec3, +} + +pub enum Fire<'a> { + Live(&'a sim::fires::Fire), + Recorded(sim::record::ReplayFire), +} + +impl Fire<'_> { + pub fn key(&self) -> u32 { + match self { + Self::Live(f) => f.id, + Self::Recorded(f) => f.key as u32, + } + } + pub fn at(&self) -> DVec3 { + match self { + Self::Live(f) => f.at, + Self::Recorded(f) => f.at, + } + } + pub fn start(&self) -> u64 { + match self { + Self::Live(f) => f.start, + Self::Recorded(f) => f.start as u64, + } + } + pub fn age_offset(&self) -> u64 { + match self { + Self::Live(_) => 0, + Self::Recorded(f) => f.age_offset as u64, + } + } + pub fn size(&self) -> f32 { + match self { + Self::Live(f) => f.size, + Self::Recorded(f) => f.size, + } + } + pub fn quiet(&self) -> bool { + match self { + Self::Live(f) => f.quiet, + Self::Recorded(f) => f.quiet, + } + } + pub fn shown(&self) -> bool { + match self { + Self::Live(f) => f.shown, + Self::Recorded(f) => f.shown, + } + } + pub fn level(&self, tick: u64) -> (f32, f32) { + match self { + Self::Live(f) => f.level(tick), + Self::Recorded(f) => { + let (mut flame, mut smoke) = sim::fires::level( + tick.saturating_sub(self.start()) as f64 + self.age_offset() as f64, + f.life as f64, + ); + if f.water == Some(true) { + flame = 0.0; + smoke *= 0.5; + } + (flame, smoke) + } + } + } +} + +/// A selected instant from exactly one source. Recorded snapshots borrow only the recording; +/// changing the current live simulation cannot change their contents. +pub struct Snapshot<'a> { + source: Source<'a>, + time: f64, +} + +impl<'a> Snapshot<'a> { + pub fn at_context(ctx: &Context, source: Source<'a>) -> Self { + let time = match &source { + Source::Live(_) => ctx.combat_tick as f64, + Source::Recorded(_) | Source::Empty => ctx.display_clock().value(), + }; + Self { source, time } + } + + pub fn source(&self) -> &'static str { + match &self.source { + Source::Live(_) => "live", + Source::Recorded(_) => "recorded", + Source::Empty => "empty", + } + } + + pub fn time(&self) -> f64 { + self.time + } + + pub fn movers(&self) -> Vec { + match &self.source { + Source::Live(sim) => sim + .drones + .iter() + .map(|d| Mover { + id: d.id, + group: 0, + kind: MoverKind::Drone(d.kind), + pos: d.pos, + }) + .chain(sim.missiles.iter().map(|m| Mover { + id: m.id, + group: 1, + kind: MoverKind::Missile { kind: m.kind, engine_lit: m.engine_lit() }, + pos: m.pos, + })) + .chain(sim.strikes.flights.iter().map(|f| { + let physical = f.carrier.is_some(); + Mover { + id: f.id, + group: 2, + kind: MoverKind::Strike { + physical, + engine_lit: !physical + || (self.time.max(0.0) as u64).saturating_sub(f.start) + >= sim::strike::EJECT_TICKS as u64, + }, + pos: f.pos, + } + })) + .collect(), + Source::Recorded(rec) => rec + .tracks + .iter() + .filter_map(|tr| { + let pos = tr.at(rec.origin, self.time)?; + let (group, kind) = match tr.what { + sim::record::What::Drone(kind) => (0, MoverKind::Drone(kind)), + sim::record::What::Missile(kind, _) => (1, MoverKind::Missile { kind, engine_lit: rec.missile_is_lit(tr, self.time, kind.lit_after()) }), + sim::record::What::Strike => ( + 2, + MoverKind::Strike { + physical: false, + engine_lit: true, + }, + ), + sim::record::What::StrikeCarrier(_) => ( + 2, + MoverKind::Strike { + physical: true, + engine_lit: (self.time - tr.first as f64).max(0.0) as u64 + >= sim::strike::EJECT_TICKS as u64, + }, + ), + }; + Some(Mover { + id: tr.id, + group, + kind, + pos, + }) + }) + .collect(), + Source::Empty => Vec::new(), + } + } + + pub fn blasts(&self) -> Cow<'_, [sim::Blast]> { + match &self.source { + Source::Live(sim) => Cow::Borrowed(&sim.blasts), + Source::Recorded(rec) => Cow::Owned(sim::record::replay_blasts(rec, self.time)), + Source::Empty => Cow::Borrowed(&[]), + } + } + + /// Smoke billows intentionally outlive the visual blast. Live blasts are already retained + /// according to their own lifetime; recorded events use the longer smoke projection. + pub fn smoke_blasts(&self) -> Cow<'_, [sim::Blast]> { + match &self.source { + Source::Live(sim) => Cow::Borrowed(&sim.blasts), + Source::Recorded(rec) => Cow::Owned(sim::record::replay_smoke_blasts(rec, self.time)), + Source::Empty => Cow::Borrowed(&[]), + } + } + + pub fn fires(&self) -> Vec> { + match &self.source { + Source::Live(sim) => sim.fires.list.iter().map(Fire::Live).collect(), + Source::Recorded(rec) => sim::record::replay_fires(rec, self.time) + .into_iter() + .map(Fire::Recorded) + .collect(), + Source::Empty => Vec::new(), + } + } + + pub fn recording(&self) -> Option<&sim::record::Recording> { + match &self.source { + Source::Live(_) => None, + Source::Recorded(rec) => Some(rec), + Source::Empty => None, + } + } + + /// Whether this snapshot was selected from a playback presentation, including a missing + /// recording. An absent playback source must never expose cached live-only trails. + pub fn is_recorded_view(&self) -> bool { + matches!(&self.source, Source::Recorded(_) | Source::Empty) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::presentation::{Clock, Mode}; + + #[test] + fn recorded_snapshot_seek_is_independent_of_interleaved_live_simulations() { + use sim::record::{Event, EventKind, Recording, What}; + + let mut rec = Recording::blank(160); + rec.straight( + 7, + What::Drone(sim::DroneKind::Geran3), + 0, + 160, + DVec3::new(3.0, 70.0, 0.0), + DVec3::new(3.0, 70.0, 160.0), + ); + rec.events.push(Event { + at: 40, + pos: DVec3::new(3.0, 69.0, 40.0), + kind: EventKind::Hit { + drone: 7, + kind: sim::DroneKind::Geran3, + power: 2, + object: None, + sam: None, + }, + }); + let mut live_a = sim::Sim::new(1); + live_a.blasts.push(sim::Blast { + id: 101, + at: DVec3::X, + size: 4.0, + start: 40, + life: 60, + }); + let mut live_b = sim::Sim::new(2); + live_b.blasts.push(sim::Blast { + id: 202, + at: DVec3::Y, + size: 8.0, + start: 40, + life: 60, + }); + + let recorded_at = |time| { + let ctx = Context::playback( + Mode::Replay, + 3, + None, + 0, + 90_000, + time, + None, + 0.25, + DVec3::ZERO, + true, + ); + let scene = Snapshot::at_context(&ctx, Source::Recorded(&rec)); + let mover = scene.movers().into_iter().find(|m| m.id == 7).unwrap(); + let blast_ids = scene.blasts().iter().map(|b| b.id).collect::>(); + (scene.time(), mover.pos, blast_ids) + }; + + let first = recorded_at(40.0); + assert_eq!(first.0, 40.0); + assert_eq!(first.2, vec![0x4000_0000]); + let ctx_live = || Context::live(3, None, 0, 40, None, DVec3::ZERO); + let a_ctx = ctx_live(); + let a_scene = Snapshot::at_context(&a_ctx, Source::Live(&live_a)); + assert_eq!(a_scene.blasts()[0].id, 101); + let middle = recorded_at(120.0); + let b_ctx = ctx_live(); + let b_scene = Snapshot::at_context(&b_ctx, Source::Live(&live_b)); + assert_eq!(b_scene.blasts()[0].id, 202); + let again = recorded_at(40.0); + + assert_eq!( + again, first, + "seek back to the same recording instant is deterministic" + ); + assert_ne!( + middle.1, first.1, + "recorded mover poses advance with recording time" + ); + assert_eq!(first.1, DVec3::new(3.0, 70.0, 40.0)); + assert_ne!(first.2, vec![101]); + assert_ne!(first.2, vec![202]); + assert_eq!(a_scene.time(), 40.0); + assert_eq!(b_scene.time(), 40.0); + assert!(matches!(a_ctx.clock, Clock::Live(40))); + } +} diff --git a/airdef-mod/src/sim/balance.rs b/airdef-mod/src/sim/balance.rs index 75cf1ed..dbaf5ec 100644 --- a/airdef-mod/src/sim/balance.rs +++ b/airdef-mod/src/sim/balance.rs @@ -54,6 +54,12 @@ pub(super) struct Result { pub missed: u32, /// Missiles fired by the heavy systems (S-400, S-500, Patriot). pub heavy: u32, + /// Interceptors whose fuse ran without a kill; whose life ran out; that lost their target on the way; + /// and that fell below their guidance speed after motor burnout (#120's coast/energy tally). + pub fuse_missed: u32, + pub expired: u32, + pub orphaned: u32, + pub energy_lost: u32, pub by_kind: Vec<(DroneKind, u32, u32)>, pub tally: String, /// Drones that vanished short of their aim without being shot down: kind, where, how far from the aim. @@ -236,7 +242,7 @@ pub(super) fn fly(s: &Setup) -> Result { land.serve(&mut ground, sim.tick); sim.step(&mut ground, None, &mut out); } - let mut order = crate::wave::WaveConfig::mix(s.size, s.dist); + let mut order = RaidOrder::mix(s.size, s.dist); // The Oreshnik's blocks are a raid of their own; this measures the ordinary mass raid. order.kinds.retain(|k| k.0 != DroneKind::Oreshnik); if s.smart { @@ -322,6 +328,10 @@ pub(super) fn fly(s: &Setup) -> Result { result.crashes = sim.crashes; result.blocked = sim.tally.blocked; result.scrubbed = sim.tally.scrubbed; + result.fuse_missed = sim.tally.missed; + result.expired = sim.tally.expired; + result.orphaned = sim.tally.orphaned; + result.energy_lost = sim.tally.energy_lost; result.leaked = sim.hits; let mut kinds: HashMap = HashMap::new(); for h in &gone_unkilled { @@ -514,6 +524,35 @@ fn one_raid() { } } +/// Issue #120's wider sampling: battery sizes, launch-range bounds and the target classes of a raid +/// of 36, on the same seeds against the same cluster mix as `one_raid`. Prints CSV rows: +/// `variant,seed,sams,dist,sent,kills,leaked,missiles,heavy,ticks,blocked,scrubbed,crashes,fuse_miss,expired,orphaned,energy_lost,leaks_by_kind`. +/// `SEEDS=1-8 cargo test --release issue_120_grid -- --ignored --nocapture` (SEEDS overrides the seed range). +#[test] +#[ignore] +fn issue_120_grid() { + let seeds = match std::env::var("SEEDS") { + Ok(v) => { + let (a, b) = v.split_once('-').expect("SEEDS is a range like 1-8"); + let a: u64 = a.parse().expect("SEEDS start"); + let b: u64 = b.parse().expect("SEEDS end"); + a..=b + } + Err(_) => 1..=8, + }; + for (variant, sams, dist) in [("batt10", 10usize, 500.0), ("batt20", 20, 500.0), ("batt50", 50, 500.0), ("near200", 20, 200.0), ("far1500", 20, 1500.0), ("hills20", 20, 500.0)] { + for seed in seeds.clone() { + let mut s = Setup::new(sams, false, seed); + s.size = 36; + s.dist = dist; + s.hilly = variant == "hills20"; + let r = fly(&s); + let kinds = r.by_kind.iter().map(|k| format!("{:?}:{}", k.0, k.1)).collect::>().join(";"); + eprintln!("{variant},{seed},{sams},{dist},{},{},{},{},{},{},{},{},{},{},{},{},{},{}", r.launched, r.kills, r.leaked, r.missiles, r.heavy, r.ticks, r.blocked, r.scrubbed, r.crashes, r.fuse_missed, r.expired, r.orphaned, r.energy_lost, kinds); + } + } +} + /// Far launches over mountains where only a disc round the player is known: who crashes, where and how often. /// `cargo test --release far_table -- --ignored --nocapture` (DISC=300 sets the radius, 0 for all known). #[test] diff --git a/airdef-mod/src/sim/battery.rs b/airdef-mod/src/sim/battery.rs new file mode 100644 index 0000000..562a626 --- /dev/null +++ b/airdef-mod/src/sim/battery.rs @@ -0,0 +1,330 @@ +//! Game-only configuration for a launcher using shared radar tracks. +use super::*; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum SamMode { + #[default] + Integrated, + LauncherOnly, +} + +impl SamMode { + pub fn key(self) -> &'static str { + match self { Self::Integrated => "integrated", Self::LauncherOnly => "launcher" } + } + pub fn from_key(key: &str) -> Option { + match key { "integrated" => Some(Self::Integrated), "launcher" => Some(Self::LauncherOnly), _ => None } + } +} + +/// Game roles for the generic shared radar; all parameters are fictional. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum RadarRole { + #[default] + Multifunction, + Search, + Engagement, + Height, +} +impl RadarRole { + pub fn key(self) -> &'static str { + match self { Self::Multifunction => "multi", Self::Search => "search", Self::Engagement => "engagement", Self::Height => "height" } + } + pub fn from_key(key: &str) -> Option { + match key { "multi" => Some(Self::Multifunction), "search" => Some(Self::Search), "engagement" => Some(Self::Engagement), "height" => Some(Self::Height), _ => None } + } + pub fn authorizes(self) -> bool { matches!(self, Self::Multifunction | Self::Engagement) } + pub(super) fn reach_factor(self) -> f64 { match self { Self::Multifunction => 1.0, Self::Search => 1.15, Self::Engagement => 0.8, Self::Height => 1.05 } } +} + +impl Sam { + pub fn set_radar_role(&mut self, role: RadarRole) -> bool { + if self.kind != SamKind::Radar { return false; } + if self.radar_role != role { + self.radar_role = role; + self.tracks.clear(); + self.seen.clear(); + self.own.clear(); + } + true + } + /// Change the physical configuration. Invalid combinations leave the unit untouched. + pub fn set_mode(&mut self, mode: SamMode) -> bool { + if mode == SamMode::LauncherOnly && !matches!(self.kind, SamKind::S400 | SamKind::S500) { return false; } + if self.mode != mode { + self.mode = mode; + self.tracks.clear(); + self.seen.clear(); + self.own.clear(); + self.target = None; + self.gun_target = None; + self.terrain.clear(); + self.waits.clear(); + } + true + } +} + +/// A snapshot of the relevant configuration, independent of its presentation. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BatteryStatus { + Radar { index: usize, role: RadarRole, emitting: bool, operational: bool }, + Launcher { index: usize, kind: SamKind, mode: SamMode, linked: bool }, +} + +impl Sim { + /// Current configurations and whether a launcher has an operational radar link. + pub fn battery_status(&self) -> Vec { + self.sams.iter().enumerate().filter_map(|(index, sam)| { + if sam.kind == SamKind::Radar { + Some(BatteryStatus::Radar { index, role: sam.radar_role, emitting: sam.radar_on, operational: sam.radar_up() && sam.link_range() > 0.0 }) + } else if matches!(sam.kind, SamKind::S400 | SamKind::S500) { + let linked = self.sams.iter().any(|r| r.kind == SamKind::Radar && r.radar_role.authorizes() && r.radar_up() && r.link_range() > 0.0 && sam.link_range() > 0.0 && r.pos.distance(sam.pos) <= r.link_range().min(sam.link_range())); + Some(BatteryStatus::Launcher { index, kind: sam.kind, mode: sam.mode, linked }) + } else { None } + }).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::super::{sight, why}; + + #[test] + fn status_reports_real_radar_links_and_preserves_indices() { + let mut sim = Sim::new(1); + sim.sams.push(Sam::new(1, SamKind::Pantsir, DVec3::ZERO, 0.0)); + let mut radar = Sam::new(2, SamKind::Radar, DVec3::ZERO, 0.0); + radar.radar_role = RadarRole::Engagement; + sim.sams.push(radar); + let mut launcher = Sam::new(3, SamKind::S400, DVec3::new(10.0, 0.0, 0.0), 0.0); + launcher.set_mode(SamMode::LauncherOnly); + sim.sams.push(launcher); + assert_eq!(sim.battery_status(), vec![ + BatteryStatus::Radar { index: 1, role: RadarRole::Engagement, emitting: true, operational: true }, + BatteryStatus::Launcher { index: 2, kind: SamKind::S400, mode: SamMode::LauncherOnly, linked: true }, + ]); + sim.sams[1].radar_on = false; + assert_eq!(sim.battery_status(), vec![ + BatteryStatus::Radar { index: 1, role: RadarRole::Engagement, emitting: false, operational: false }, + BatteryStatus::Launcher { index: 2, kind: SamKind::S400, mode: SamMode::LauncherOnly, linked: false }, + ]); + } + + fn track(now: u64) -> Track { + Track { id: 42, kind: DroneKind::Kh101, pos: DVec3::new(0.0, 100.0, -200.0), vel: DVec3::ZERO, tick: now, own_tick: now, ttl: 400, hits: 3, locked: false } + } + + #[test] + fn shared_radar_serves_multiple_launchers_and_failure_blocks_cue() { + let mut radar = Sam::new(1, SamKind::Radar, DVec3::ZERO, 0.0); + let mut a = Sam::new(2, SamKind::S400, DVec3::new(10.0, 0.0, 0.0), 0.0); + let mut b = Sam::new(3, SamKind::S500, DVec3::new(20.0, 0.0, 0.0), 0.0); + assert!(a.set_mode(SamMode::LauncherOnly)); + assert!(b.set_mode(SamMode::LauncherOnly)); + assert!(!a.radar_up() && !b.radar_up()); + radar.tracks.push(track(10)); + radar.set_radar_role(RadarRole::Search); + radar.tracks.push(track(10)); + assert!(!why::radar_cue(&a, &[&radar], 42, 10)); + radar.set_radar_role(RadarRole::Height); + radar.tracks.push(track(10)); + assert!(!why::radar_cue(&a, &[&radar], 42, 10)); + radar.set_radar_role(RadarRole::Engagement); + radar.tracks.push(track(10)); + assert!(why::radar_cue(&a, &[&radar], 42, 10)); + assert!(why::radar_cue(&b, &[&radar], 42, 10)); + assert!(!why::radar_cue(&a, &[&radar], 42, 10 + why::FAR_RADAR_AGE + 1)); + radar.comps.0[Part::Comms as usize] = 0.0; + assert!(!why::radar_cue(&a, &[&radar], 42, 10)); + radar.comps.0[Part::Comms as usize] = 1.0; + a.comps.0[Part::Comms as usize] = 0.0; + assert!(!why::radar_cue(&a, &[&radar], 42, 10)); + a.comps.0[Part::Comms as usize] = 1.0; + a.pos.x = sight::LINK + 1.0; + assert!(!why::radar_cue(&a, &[&radar], 42, 10)); + a.pos.x = 10.0; + radar.health = 0.0; + assert!(!why::radar_cue(&a, &[&radar], 42, 10)); + } + + #[test] + fn network_shares_a_measurement_to_both_launchers_without_fabricating_own_tracks() { + let mut sim = Sim::new(7); + sim.tick = 10; + let id = sim.id(); + sim.sams.push(Sam::new(id, SamKind::Radar, DVec3::ZERO, 0.0)); + for kind in [SamKind::S400, SamKind::S500] { + let id = sim.id(); + let mut launcher = Sam::new(id, kind, DVec3::new(10.0, 0.0, 0.0), 0.0); + launcher.set_mode(SamMode::LauncherOnly); + launcher.roles = 0xff; + sim.sams.push(launcher); + } + sim.add_drone(DroneKind::Kh101, DVec3::new(0.0, 100.0, -200.0), DVec3::Z, DVec3::ZERO); + let id = sim.drones[0].id; + let mut measurement = track(10); + measurement.id = id; + sim.sams[0].tracks.push(measurement); + sim.update_sight(&mut Ground::default()); + for launcher in &sim.sams[1..] { + let received = launcher.track(id).expect("shared measurement"); + assert_eq!(received.tick, 10); + assert_eq!(received.own_tick, 0); + assert!(why::radar_cue(launcher, &[&sim.sams[0]], id, 10)); + let load = launcher.kind.spec().missile.as_ref().unwrap(); + let opts = why::Opts { last_chance: false, far_shots: false }; + assert!(why::basis(launcher, load, received, &sim.drones[0], 10, 0, 1.0, opts, true, false).is_some()); + assert!(why::basis(launcher, load, received, &sim.drones[0], 10, 0, 1.0, opts, false, false).is_none()); + } + sim.sams[0].health = 0.0; + for launcher in &sim.sams[1..] { + assert!(launcher.track(id).is_some()); + assert!(!why::radar_cue(launcher, &[&sim.sams[0]], id, 10)); + } + } + + #[test] + fn search_cues_tracking_radar_but_only_its_own_measurement_authorizes_launch() { + for in_range in [true, false] { + let mut sim = Sim::new(71); + sim.tick = 10; + let mut search = Sam::new(1, SamKind::Radar, DVec3::ZERO, 0.0); + search.set_radar_role(RadarRole::Search); + let mut fire_control = Sam::new(2, SamKind::Radar, DVec3::ZERO, 0.0); + fire_control.set_radar_role(RadarRole::Engagement); + let mut launcher = Sam::new(3, SamKind::S400, DVec3::ZERO, 0.0); + launcher.set_mode(SamMode::LauncherOnly); + launcher.roles = 0xff; + sim.sams.extend([search, fire_control, launcher]); + let distance = if in_range { 100.0 } else { 10000.0 }; + sim.add_drone(DroneKind::Kh101, DVec3::new(0.0, 70.0, -distance), DVec3::Z, DVec3::ZERO); + let id = sim.drones[0].id; + let mut measurement = track(10); + measurement.id = id; + measurement.pos = sim.drones[0].pos; + sim.sams[0].tracks.push(measurement); + sim.sams[0].beam = std::f64::consts::PI; + sim.sams[1].beam = std::f64::consts::PI; + let mut ground = Ground::default(); + sim.update_sight(&mut ground); + assert_eq!(sim.sams[1].track(id).unwrap().own_tick, 0, "cue is not a measurement"); + assert!(!why::radar_cue(&sim.sams[2], &[&sim.sams[1]], id, sim.tick)); + for now in 11..40 { + sim.tick = now; + // Bearing is north (zero); force the ordinary beam away each tick. + sim.sams[1].beam = std::f64::consts::PI; + sim.update_sight(&mut ground); + if sim.sams[1].track(id).is_some_and(|t| t.own_tick > 0) { break; } + } + let got_own = sim.sams[1].track(id).is_some_and(|t| t.own_tick > 0); + assert_eq!(got_own, in_range); + assert_eq!(why::radar_cue(&sim.sams[2], &[&sim.sams[1]], id, sim.tick), in_range); + if in_range { + let tr = sim.sams[2].track(id).unwrap(); + let load = sim.sams[2].kind.spec().missile.as_ref().unwrap(); + let opts = why::Opts { last_chance: false, far_shots: false }; + assert!(why::basis(&sim.sams[2], load, tr, &sim.drones[0], sim.tick, 0, 1.0, opts, true, false).is_some()); + let mut out = Out::default(); + for _ in 0..100 { + sim.step(&mut ground, None, &mut out); + if sim.sams[2].fired > 0 { break; } + } + assert!(sim.sams[2].fired > 0, "launcher never fired after its tracking radar measured the cue"); + } + } + } + + #[test] + fn fictional_roles_change_scan_and_height_and_coverage() { + let mut r = Sam::new(1, SamKind::Radar, DVec3::ZERO, 0.0); + let base_mast = r.sensor_mast(); + let base_scan = r.scan_ticks(); + let base_reach = sight::reach(&r, DroneKind::Kh101, 5000.0); + r.set_radar_role(RadarRole::Height); + assert!(r.sensor_mast() > base_mast); + r.set_radar_role(RadarRole::Engagement); + assert!(r.scan_ticks() < base_scan && r.scan_profile().agile); + assert!(sight::reach(&r, DroneKind::Kh101, 5000.0) < base_reach); + r.set_radar_role(RadarRole::Search); + assert!(sight::reach(&r, DroneKind::Kh101, 5000.0) > base_reach); + } + + #[test] + fn launcher_has_no_own_map_sensor_and_mode_switch_drops_stale_tracks() { + let mut sim = Sim::new(3); + sim.sams.push(Sam::new(1, SamKind::S400, DVec3::ZERO, 0.0)); + assert!(sim.low_sensor(SamKind::S400, DVec3::ZERO, Some(1)).is_some()); + sim.sams[0].tracks.push(track(10)); + sim.sams[0].terrain.insert(42, (10, 1)); + assert!(sim.sams[0].set_mode(SamMode::LauncherOnly)); + assert!(sim.low_sensor(SamKind::S400, DVec3::ZERO, Some(1)).is_none()); + assert!(sim.low_sensors().is_empty()); + assert!(sim.sams[0].track(42).is_none()); + assert!(sim.sams[0].terrain.is_empty()); + sim.sams[0].set_mode(SamMode::Integrated); + assert!(sim.low_sensor(SamKind::S400, DVec3::ZERO, Some(1)).is_some()); + } + + #[test] + fn configuration_clears_old_tracks_and_rejects_other_kinds() { + let mut sam = Sam::new(1, SamKind::S400, DVec3::ZERO, 0.0); + sam.tracks.push(track(10)); + assert!(sam.set_mode(SamMode::LauncherOnly)); + assert!(sam.tracks.is_empty()); + assert!(sam.set_mode(SamMode::Integrated)); + assert!(sam.radar_up()); + let mut other = Sam::new(2, SamKind::Patriot, DVec3::ZERO, 0.0); + assert!(!other.set_mode(SamMode::LauncherOnly)); + assert_eq!(other.mode, SamMode::Integrated); + } +} + +#[cfg(test)] +mod emission_tests { + use super::*; + #[test] + fn silence_discloses_nothing_and_contacts_age_and_channels_still_place_a_unit() { + let mut sim = Sim::new(121); + sim.tick = 20; + let mut source = Sam::new(1, SamKind::Radar, DVec3::ZERO, 0.0); + let mut receiver = Sam::new(2, SamKind::Radar, DVec3::ZERO, 0.0); + receiver.set_radar_role(RadarRole::Engagement); + receiver.radar_on = false; + sim.add_drone(DroneKind::Kh101, DVec3::new(0.0, 70.0, -100.0), DVec3::Z, DVec3::ZERO); + let id = sim.drones[0].id; + source.tracks.push(Track { id, kind: DroneKind::Kh101, pos: sim.drones[0].pos, vel: DVec3::ZERO, tick: 20, own_tick: 20, ttl: 200, hits: 2, locked: false }); + sim.sams.extend([source, receiver]); + let mut ground = Ground::default(); + sim.update_sight(&mut ground); + assert_eq!(sim.sams[1].track(id).unwrap().own_tick, 0); + assert_eq!(sim.sams[1].track(id).unwrap().tick, 20); + assert!(!sim.sams[1].radar_up()); + // The emitting source is fixed by the drone; the silent receiver is not. + let picture = sim.picture_of_defences(); + assert_eq!(picture.len(), 1); + assert_eq!((picture[0].id, picture[0].at), (1, DVec3::ZERO)); + assert_eq!(picture[0].seen, ew::Seen::Emission); + sim.sams[0].radar_on = false; + // The truth moving is not an observation: the contact keeps the observed place. + sim.sams[0].pos = DVec3::new(400.0, 0.0, 0.0); + sim.tick = 40; + sim.update_sight(&mut ground); + assert_eq!(sim.sams[1].track(id).unwrap().tick, 20); + let picture = sim.picture_of_defences(); + assert_eq!(picture.len(), 1); + assert_eq!(picture[0].at, DVec3::ZERO, "a hidden move changed the picture"); + // A fresh emission fix re-places it where seen then. + sim.sams[0].radar_on = true; + sim.tick = 60; + sim.update_sight(&mut ground); + assert_eq!(sim.picture_of_defences()[0].at, DVec3::new(400.0, 0.0, 0.0)); + sim.sams[0].radar_on = false; + // Other disclosure channels still reveal a silent unit. + sim.intel.observe(&sim.sams[1], 60, ew::Seen::Launch); + assert!(sim.known_live(2)); + sim.tick = 6060; + assert!(sim.picture_of_defences().is_empty(), "an unrefreshed contact never ages out"); + } +} diff --git a/airdef-mod/src/sim/catalog.rs b/airdef-mod/src/sim/catalog.rs new file mode 100644 index 0000000..ff2aaab --- /dev/null +++ b/airdef-mod/src/sim/catalog.rs @@ -0,0 +1,203 @@ +//! Stable identities and availability for the existing simulation content. +//! +//! Balance and rendering remain in `DroneSpec` and `SamSpec`: the catalog +//! refers to those typed definitions instead of copying their values. The +//! first eleven drone entries retain the historical positional wave-save order. +use super::{DroneKind, SamKind}; + +#[derive(Clone, Copy, Debug)] +pub struct Aircraft { + pub kind: DroneKind, + /// Inventory item, when this type has a player launch item. + pub launch_item: Option<&'static str>, + /// May appear as a selectable type in the raid menu. + pub raid: bool, +} + +impl Aircraft { + /// Existing save/command key; changing it requires an explicit migration. + pub fn id(self) -> &'static str { self.kind.spec().key } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GroundStrike { + /// Game-only RM48U coordinate-strike profile for S-400. Evidence: official agency report, + /// not independent certification: https://gur.gov.ua/content/warsanctions-hur-mo-rozkryvaie-budovu-komponentnu-bazu-ta-pidpryiemstva-kooperatsii-z-vyrobnytstva-udarnykh-raket-rm48u.html + /// The range and flight are game abstractions, not real parameters. + Rm48uGame, + /// No implemented game profile; this is not a claim of physical impossibility. + Unavailable { reason: &'static str }, +} + +impl GroundStrike { + /// Evidence describes provenance, not independent certification. + pub fn evidence(self) -> Option<&'static str> { + match self { GroundStrike::Rm48uGame => Some("official-agency-report"), GroundStrike::Unavailable { .. } => None } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Unit { + pub kind: SamKind, + pub deploy_item: &'static str, +} + +impl Unit { + pub fn id(self) -> &'static str { self.kind.spec().key } + /// Whether the unit has a gun; callers need no access to the private weapon profile. + pub fn has_gun(self) -> bool { self.kind.spec().gun.is_some() } + pub fn ground_strike(self) -> GroundStrike { + if self.kind == SamKind::S400 { GroundStrike::Rm48uGame } else { GroundStrike::Unavailable { reason: "Для этой техники не задан профиль удара по координатам" } } + } +} + +use DroneKind::*; +/// Stable sound IDs shared with the audio resource generator. +pub mod sound { + include!("catalog_sound_ids.rs"); +} + +// Generated entries keep the historical raid and unit ordering. +include!("catalog_entries.rs"); + +/// Existing positional serialization makes the first entries' order part of the save format. +const fn raid_count() -> usize { + let mut i = 0; + let mut count = 0; + while i < AIRCRAFT.len() { + if AIRCRAFT[i].raid { count += 1; } + i += 1; + } + count +} +pub const RAID_COUNT: usize = raid_count(); +const fn raid_kinds() -> [DroneKind; RAID_COUNT] { + let mut result = [Gerbera; RAID_COUNT]; + let mut i = 0; + let mut out = 0; + while i < AIRCRAFT.len() { + if AIRCRAFT[i].raid { result[out] = AIRCRAFT[i].kind; out += 1; } + i += 1; + } + result +} +pub const RAID_KINDS: [DroneKind; RAID_COUNT] = raid_kinds(); + +pub const UNIT_COUNT: usize = UNITS.len(); +const fn unit_kinds() -> [SamKind; UNIT_COUNT] { + let mut result = [SamKind::Patriot; UNIT_COUNT]; + let mut i = 0; + while i < result.len() { result[i] = UNITS[i].kind; i += 1; } + result +} +pub const UNIT_KINDS: [SamKind; UNIT_COUNT] = unit_kinds(); + +pub fn unit(kind: SamKind) -> &'static Unit { + UNITS.iter().find(|entry| entry.kind == kind).expect("unit kind missing from catalog") +} + +/// Inventory ordering is stable for existing remotes, while availability comes from the same entries as raids. +const fn inventory_count() -> usize { + let mut n = UNIT_COUNT + 4; + let mut i = 0; + while i < AIRCRAFT.len() { + if AIRCRAFT[i].launch_item.is_some() { n += 1; } + i += 1; + } + n +} +pub const INVENTORY_COUNT: usize = inventory_count(); +const fn inventory_items() -> [&'static str; INVENTORY_COUNT] { + let mut out = [""; INVENTORY_COUNT]; + out[0] = "airdef:designator"; + let mut n = 1; + let mut i = 0; + while i < AIRCRAFT.len() { + if let Some(item) = AIRCRAFT[i].launch_item { out[n] = item; n += 1; } + i += 1; + } + i = 0; + while i < UNITS.len() { out[n] = UNITS[i].deploy_item; n += 1; i += 1; } + out[n] = "airdef:mover"; + out[n + 1] = "airdef:flare_gun"; + out[n + 2] = "airdef:remover"; + out +} +pub const INVENTORY: [&str; INVENTORY_COUNT] = inventory_items(); + +pub fn launch_item(id: &str) -> Option { + AIRCRAFT.iter().find(|entry| entry.launch_item == Some(id)).map(|entry| entry.kind) +} +pub fn deploy_item(id: &str) -> Option { + UNITS.iter().find(|entry| entry.deploy_item == id).map(|entry| entry.kind) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use super::super::units::Muzzle; + + #[test] + fn identities_cover_the_simulation_and_raid_order_is_compatible() { + let mut ids = HashSet::new(); + let mut kinds = HashSet::new(); + let mut items = HashSet::new(); + for (i, e) in AIRCRAFT.iter().enumerate() { + assert!(ids.insert(e.id()), "duplicate aircraft ID {}", e.id()); + assert!(kinds.insert(e.kind), "duplicate aircraft kind {:?}", e.kind); + if i < 11 { assert!(e.raid); } + if let Some(item) = e.launch_item { + assert!(items.insert(item), "duplicate item {item}"); + assert_eq!(item, format!("airdef:launch_{}", e.id())); + } + } + assert_eq!(kinds, DroneKind::ALL.into_iter().collect()); + let mut kinds = HashSet::new(); + for e in UNITS { + assert!(ids.insert(e.id()), "duplicate unit ID {}", e.id()); + assert!(kinds.insert(e.kind), "duplicate unit kind {:?}", e.kind); + assert!(items.insert(e.deploy_item), "duplicate item {}", e.deploy_item); + assert_eq!(e.deploy_item, format!("airdef:deploy_{}", e.id())); + let spec = e.kind.spec(); + assert!(spec.sensor.is_finite() && spec.sensor >= 0.0); + match (&spec.missile, spec.muzzle) { + (Some(_), Muzzle::None) => panic!("{} has missiles but no muzzle", e.id()), + (_, Muzzle::Grid(cols, rows, size)) => assert!(cols > 0 && rows > 0 && size.is_finite() && size > 0.0), + (_, Muzzle::Rails(xs)) => assert!(!xs.is_empty() && xs.iter().all(|v| v.is_finite())), + _ => {} + } + + } + assert_eq!(kinds, SamKind::ALL.into_iter().collect()); + assert_eq!(&RAID_KINDS[..11], &[Gerbera, Shahed, Geran3, Lancet, Kh101, Kalibr, Kh22, Zircon, Iskander, Kinzhal, Oreshnik]); + } + + /// The catalog is the only copy of the numbers: a kind without a row, or a row under + /// another kind, would be a silent balance change. + #[test] + fn generated_specs_cover_every_kind_and_keep_their_keys() { + let mut drones = HashSet::new(); + let mut drone_keys = HashSet::new(); + for (kind, key) in super::super::DRONE_SPEC_KEYS { + assert_eq!(kind.spec().key, key, "profile/key mismatch for {kind:?}"); + assert!(drones.insert(kind), "duplicate drone profile {kind:?}"); + assert!(drone_keys.insert(key), "duplicate drone save key {key}"); + assert!(kind.spec().rcs > 0.0 && kind.spec().launch_sound.starts_with("airdef:")); + } + assert_eq!(drones, DroneKind::ALL.into_iter().collect()); + let mut missiles = HashSet::new(); + for (kind, model) in super::super::MISSILE_SPEC_MODELS { + assert_eq!(kind.spec().model, model, "profile/model mismatch for {kind:?}"); + assert!(missiles.insert(kind), "duplicate missile profile {kind:?}"); + } + let mut units = HashSet::new(); + let mut unit_keys = HashSet::new(); + for (kind, key) in super::super::units::UNIT_SPEC_KEYS { + assert_eq!(kind.spec().key, key, "profile/key mismatch for {kind:?}"); + assert!(units.insert(kind), "duplicate unit profile {kind:?}"); + assert!(unit_keys.insert(key), "duplicate unit save key {key}"); + } + assert_eq!(units, SamKind::ALL.into_iter().collect()); + } +} diff --git a/airdef-mod/src/sim/catalog_entries.rs b/airdef-mod/src/sim/catalog_entries.rs new file mode 100644 index 0000000..c7b3a7a --- /dev/null +++ b/airdef-mod/src/sim/catalog_entries.rs @@ -0,0 +1,34 @@ +// Generated by tools/gen_catalog.py from catalog/content.json. Do not edit. +pub const AIRCRAFT: &[Aircraft] = &[ + Aircraft { kind: Gerbera, launch_item: None, raid: true }, + Aircraft { kind: Shahed, launch_item: Some("airdef:launch_shahed"), raid: true }, + Aircraft { kind: Geran3, launch_item: Some("airdef:launch_geran3"), raid: true }, + Aircraft { kind: Lancet, launch_item: Some("airdef:launch_lancet"), raid: true }, + Aircraft { kind: Kh101, launch_item: Some("airdef:launch_kh101"), raid: true }, + Aircraft { kind: Kalibr, launch_item: Some("airdef:launch_kalibr"), raid: true }, + Aircraft { kind: Kh22, launch_item: Some("airdef:launch_kh22"), raid: true }, + Aircraft { kind: Zircon, launch_item: Some("airdef:launch_zircon"), raid: true }, + Aircraft { kind: Iskander, launch_item: Some("airdef:launch_iskander"), raid: true }, + Aircraft { kind: Kinzhal, launch_item: Some("airdef:launch_kinzhal"), raid: true }, + Aircraft { kind: Oreshnik, launch_item: Some("airdef:launch_oreshnik"), raid: true }, + Aircraft { kind: OreshnikBlock, launch_item: None, raid: false }, + Aircraft { kind: OreshnikSub, launch_item: None, raid: false }, +]; + +pub const UNITS: &[Unit] = &[ + Unit { kind: SamKind::Patriot, deploy_item: "airdef:deploy_patriot" }, + Unit { kind: SamKind::Gepard, deploy_item: "airdef:deploy_gepard" }, + Unit { kind: SamKind::Pantsir, deploy_item: "airdef:deploy_pantsir" }, + Unit { kind: SamKind::S400, deploy_item: "airdef:deploy_s400" }, + Unit { kind: SamKind::S500, deploy_item: "airdef:deploy_s500" }, + Unit { kind: SamKind::IrisT, deploy_item: "airdef:deploy_irist" }, + Unit { kind: SamKind::Nasams, deploy_item: "airdef:deploy_nasams" }, + Unit { kind: SamKind::Buk, deploy_item: "airdef:deploy_buk" }, + Unit { kind: SamKind::Tor, deploy_item: "airdef:deploy_tor" }, + Unit { kind: SamKind::P1sun, deploy_item: "airdef:deploy_p1sun" }, + Unit { kind: SamKind::Radar, deploy_item: "airdef:deploy_radar" }, + Unit { kind: SamKind::Depot, deploy_item: "airdef:deploy_depot" }, + Unit { kind: SamKind::Tzm, deploy_item: "airdef:deploy_tzm" }, + Unit { kind: SamKind::Ew, deploy_item: "airdef:deploy_ew" }, + Unit { kind: SamKind::Light, deploy_item: "airdef:deploy_light" }, +]; diff --git a/airdef-mod/src/sim/catalog_sound_ids.rs b/airdef-mod/src/sim/catalog_sound_ids.rs new file mode 100644 index 0000000..b2715a5 --- /dev/null +++ b/airdef-mod/src/sim/catalog_sound_ids.rs @@ -0,0 +1,36 @@ +// Generated by tools/gen_catalog.py from catalog/sounds.json. Do not edit. +pub const DRONE_SHAHED: &str = "airdef:drone.shahed"; +pub const DRONE_GERAN3: &str = "airdef:drone.geran3"; +pub const DRONE_LANCET: &str = "airdef:drone.lancet"; +pub const DRONE_CRUISE: &str = "airdef:drone.cruise"; +pub const DRONE_ROCKET: &str = "airdef:drone.rocket"; +pub const MISSILE_FLIGHT: &str = "airdef:missile.flight"; +pub const DRONE_BALLISTIC: &str = "airdef:drone.ballistic"; +pub const BOOM_SONIC: &str = "airdef:boom.sonic"; +pub const BOOM_SONIC_FAR: &str = "airdef:boom.sonic_far"; +pub const LAUNCH_PATRIOT: &str = "airdef:launch.patriot"; +pub const LAUNCH_PATRIOT_FAR: &str = "airdef:launch.patriot_far"; +pub const LAUNCH_S400_EJECT: &str = "airdef:launch.s400_eject"; +pub const LAUNCH_S400_EJECT_FAR: &str = "airdef:launch.s400_eject_far"; +pub const LAUNCH_S400: &str = "airdef:launch.s400"; +pub const LAUNCH_S400_FAR: &str = "airdef:launch.s400_far"; +pub const LAUNCH_S500_EJECT: &str = "airdef:launch.s500_eject"; +pub const LAUNCH_S500_EJECT_FAR: &str = "airdef:launch.s500_eject_far"; +pub const LAUNCH_S500: &str = "airdef:launch.s500"; +pub const LAUNCH_S500_FAR: &str = "airdef:launch.s500_far"; +pub const LAUNCH_PANTSIR: &str = "airdef:launch.pantsir"; +pub const LAUNCH_PANTSIR_FAR: &str = "airdef:launch.pantsir_far"; +pub const GUN_GEPARD: &str = "airdef:gun.gepard"; +pub const GUN_GEPARD_FAR: &str = "airdef:gun.gepard_far"; +pub const GUN_PANTSIR: &str = "airdef:gun.pantsir"; +pub const GUN_PANTSIR_FAR: &str = "airdef:gun.pantsir_far"; +pub const BLAST_BIG: &str = "airdef:blast.big"; +pub const BLAST_BIG_FAR: &str = "airdef:blast.big_far"; +pub const BLAST_HEAVY: &str = "airdef:blast.heavy"; +pub const BLAST_HEAVY_FAR: &str = "airdef:blast.heavy_far"; +pub const BLAST_HUGE: &str = "airdef:blast.huge"; +pub const BLAST_HUGE_FAR: &str = "airdef:blast.huge_far"; +pub const BLAST_MEDIUM: &str = "airdef:blast.medium"; +pub const BLAST_MEDIUM_FAR: &str = "airdef:blast.medium_far"; +pub const BLAST_AIR: &str = "airdef:blast.air"; +pub const BLAST_AIR_FAR: &str = "airdef:blast.air_far"; diff --git a/airdef-mod/src/sim/damage.rs b/airdef-mod/src/sim/damage.rs index 705d4c9..25525d7 100644 --- a/airdef-mod/src/sim/damage.rs +++ b/airdef-mod/src/sim/damage.rs @@ -141,6 +141,16 @@ impl Comps { /// Health points an object loses at the centre of a blast. const OBJECT_HIT: [f64; 5] = [2.0, 6.0, 14.0, 26.0, 40.0]; +/// Three cheap game samples across a hull. Known cover leaves residual splash; +/// unknown geometry keeps the previous unattenuated damage (not a claim of air). +/// Zero async wait freezes the pre-destruction cache and avoids late double damage. +fn blast_exposure(ground: &Ground, from: DVec3, centre: DVec3, size: f64, now: u64) -> f64 { + let across = horizontal(centre - from).cross(DVec3::Y).normalize_or(DVec3::X) * size; + [centre, centre - across, centre + across].into_iter().map(|to| { + if ground.world.blast_cover(from, to, now) == Some(true) { 0.15 } else { 1.0 } + }).sum::() / 3.0 +} + impl MissileKind { /// The explosive a missile left in a burning launcher adds (TNT). fn charge(self) -> f64 { @@ -186,6 +196,21 @@ impl DroneKind { } impl Sim { + /// Apply combat consequences first, independently of whether a recorder is active. + pub(super) fn emit_combat_event(&mut self, pos: DVec3, kind: record::EventKind) { + match kind { + record::EventKind::Crash { power, .. } => self.ignite(pos, power, None), + record::EventKind::Hit { power, sam, .. } => { + let host = sam.and_then(|(_, i)| self.sams.get(i)).map(|s| s.id); + self.ignite(pos, power.max(1), host) + } + record::EventKind::SamLost { sam, .. } => self.ignite(pos, 2, Some(sam)), + record::EventKind::Strike { result, .. } if record::strike_power(result) > 0 => self.ignite(pos, record::strike_power(result), None), + _ => {} + } + self.record_event(pos, kind); + } + /// The aim error of a new strike: drawn once from the seed, the drone keeps it for its whole flight. pub(super) fn aim_error(&mut self, kind: DroneKind) -> DVec3 { let (cep, gross) = kind.accuracy(); @@ -225,7 +250,7 @@ impl Sim { } /// A blast of `power` (0 to 4) at `pos`: the systems and objects round it take damage. - pub(super) fn blast_damage(&mut self, pos: DVec3, power: usize, out: &mut Out) { + pub(super) fn blast_damage(&mut self, pos: DVec3, power: usize, ground: &Ground, out: &mut Out) { let (reach, hit) = (REACH[power], HIT[power]); let mut destroyed = Vec::new(); for (i, sam) in self.sams.iter_mut().enumerate() { @@ -238,8 +263,9 @@ impl Sim { continue; } let k = 1.0 - d / reach; + let cover = blast_exposure(ground, pos, sam.pos + DVec3::Y * (1.5 * sam.kind.spec().scale), sam.kind.spec().scale, self.tick); let before = sam.health; - sam.health -= hit * k * k * if d < 1.0 { 1.5 } else { 1.0 }; + sam.health -= cover * hit * k * k * if d < 1.0 { 1.5 } else { 1.0 }; if sam.health <= 0.0 { destroyed.push(i); continue; @@ -264,7 +290,7 @@ impl Sim { let kp = 1.0 - dp / reach; let roll = dice(self.seed, self.tick, sam.id as u64, part as u64); // The parts are softer than the hull: a blast the hull survives can still put one out. - let dmg = hit * kp * kp * (0.7 + roll) * 1.5 * if dp < 1.0 { 1.4 } else { 1.0 }; + let dmg = cover * hit * kp * kp * (0.7 + roll) * 1.5 * if dp < 1.0 { 1.4 } else { 1.0 }; let was = sam.comps.state(part); let v = &mut sam.comps.0[part as usize]; *v = (*v - dmg).max(0.0); @@ -285,7 +311,8 @@ impl Sim { if d < r { if let Some(h) = self.target_health.get_mut(i) { let before = *h; - *h = (*h - OBJECT_HIT[power] * (1.0 - d / r)).max(0.0); + let cover = blast_exposure(ground, pos, *t + DVec3::Y * 0.8, 0.75, self.tick); + *h = (*h - cover * OBJECT_HIT[power] * (1.0 - d / r)).max(0.0); if before > 0.0 && *h <= 0.0 { out.messages.push(format!("ОБЪЕКТ №{} УНИЧТОЖЕН", i + 1)); } @@ -318,7 +345,14 @@ impl Sim { // A depot's stock (or a truck's load) goes up with it. let stock = if matches!(self.sams[i].kind, SamKind::Depot | SamKind::Tzm) { self.sams[i].cargo } else { 0 }; self.sams[i].cargo = 0; - let missiles = missiles + stock * 2; + let typed = self.sams[i].ground_stock + self.sams[i].air_48n6_stock; + self.sams[i].ground_stock = 0; + self.sams[i].air_48n6_stock = 0; + self.sams[i].ground_rounds = 0; + self.sams[i].ground_request = false; + self.sams[i].ground_return = false; + self.sams[i].return_cargo = false; + let missiles = missiles + stock * 2 + typed; let charge = spec.missile.as_ref().map_or(0.0, |m| m.kind.charge() * missiles as f64) + bursts as f64 / 60.0 + stock as f64 * 1.5; // The vehicle's own fuel and the first of its load at once, then the rest cooking off. @@ -330,7 +364,7 @@ impl Sim { let tnt = if charge > 4.0 && self.rng.chance(0.4) { 2 } else { 1 }; self.later.push((when, at, tnt, true)); } - self.log(pos, record::EventKind::SamLost { sam: id, kind, index: i }); + self.emit_combat_event(pos, record::EventKind::SamLost { sam: id, kind, index: i }); // Shells pop in strings. for k in 0..(bursts / 12).min(14) { let when = now + 10 + (k as u64) * 6 + (self.rng.f() * 8.0) as u64; @@ -435,7 +469,9 @@ impl Sam { /// Missiles above what the cells hold are lost with them. pub fn fit_cells(&mut self) { if self.kind.spec().missile.is_some() { - self.missiles = self.missiles.min(self.cells()); + self.ground_rounds = self.ground_rounds.min(self.cells()); + self.missiles = self.missiles.min(self.cells().saturating_sub(self.ground_rounds)); + if self.ground_rounds == 0 { self.ground_return = false; } } } /// Ticks a reload takes: longer with a damaged launcher. @@ -478,7 +514,7 @@ impl Sim { // Rebuilt where it stands: a march under way is called off. sam.dest = None; sam.march = 0; - sam.missiles = spec.missile.as_ref().map_or(0, |m| m.magazine); + sam.missiles = spec.missile.as_ref().map_or(0, |m| m.magazine.saturating_sub(sam.ground_rounds)); sam.bursts = spec.gun.as_ref().map_or(0, |g| g.bursts); } for h in &mut self.target_health { @@ -507,3 +543,99 @@ pub(super) fn draw_wreck_of(sam: &Sam, out: &mut Out) { } } } + +#[cfg(test)] +mod cover_tests { + use super::*; + + struct CoverWorld { wall: bool, opening: bool } + impl world::WorldSource for CoverWorld { + fn solid(&self, x: i32, _y: i32, z: i32) -> Option { + Some(self.wall && x == 8 && !(self.opening && z == 8)) + } + } + + fn cache(src: &CoverWorld) -> Ground { + let mut ground = Ground::default(); + let mut answers = Vec::new(); + for x in -1..=1 { for z in -1..=1 { + let sec = (x, 4, z); + answers.push(world::SectionAnswer { sec, fill: world::section_of(src, sec) }); + }} + ground.world.answer(answers, 10); + ground + } + + fn victim() -> Sim { + let mut sim = Sim::new(7); + sim.tick = 10; + let id = sim.id(); + sim.sams.push(Sam::new(id, SamKind::S400, DVec3::new(10.5, 70.0, 8.5), 0.0)); + sim.targets.push(DVec3::new(10.5, 71.0, 8.5)); + sim.target_health.push(100.0); + sim + } + + #[test] + fn known_wall_and_opening_attenuate_hull_parts_and_objects() { + let from = DVec3::new(0.5, 71.5, 8.5); + let mut results = Vec::new(); + for src in [CoverWorld { wall: false, opening: false }, CoverWorld { wall: true, opening: false }, CoverWorld { wall: true, opening: true }] { + let mut sim = victim(); + sim.blast_damage(from, 2, &cache(&src), &mut Out::default()); + results.push((1.0 - sim.sams[0].health, 1.0 - sim.sams[0].comps.get(Part::Tracks), 100.0 - sim.target_health[0])); + } + for i in 0..3 { + let open = [results[0].0, results[0].1, results[0].2][i]; + let wall = [results[1].0, results[1].1, results[1].2][i]; + let gap = [results[2].0, results[2].1, results[2].2][i]; + assert!(open > 0.0); + assert!((wall / open - 0.15).abs() < 1e-9); + assert!(wall < gap && gap < open, "sample {i}: wall {wall}, gap {gap}, open {open}"); + } + } + + #[test] + fn unresolved_cover_has_bounded_zero_wait_and_no_late_damage() { + let from = DVec3::new(0.5, 71.5, 8.5); + let mut unknown = Ground::default(); + assert_eq!(unknown.world.blast_cover(from, DVec3::new(10.5, 71.5, 8.5), 10), None); + let mut sim = victim(); + let mut open = victim(); + sim.blast_damage(from, 2, &unknown, &mut Out::default()); + open.blast_damage(from, 2, &cache(&CoverWorld { wall: false, opening: false }), &mut Out::default()); + assert_eq!(sim.sams[0].health, open.sams[0].health); + let health = sim.sams[0].health; + let hit_points = sim.target_health[0]; + assert!(unknown.world.take_wanted(10).is_empty()); + assert!(unknown.world.take_rays(10).is_empty()); + // A reply, even one proving cover, is not a second combat event. + unknown.world.answer(vec![world::SectionAnswer { sec: (0, 4, 0), fill: world::Fill::Full }], 11); + assert_eq!(sim.sams[0].health, health); + assert_eq!(sim.target_health[0], hit_points); + } + + #[test] + fn direct_contact_is_not_weakened_by_cover_and_blast_does_not_mutate_world() { + let mut sim = victim(); + let ground = cache(&CoverWorld { wall: true, opening: false }); + let from = DVec3::new(0.5, 71.5, 8.5); + let end = DVec3::new(10.5, 71.5, 8.5); + assert_eq!(ground.world.blast_cover(from, end, 10), Some(true)); + sim.blast_damage(from, 2, &ground, &mut Out::default()); + assert_eq!(ground.world.blast_cover(from, end, 10), Some(true)); + let at = sim.sams[0].pos + DVec3::Y; + sim.direct_hit(at, DVec3::X * 2.0, 1, &mut Out::default()); + assert!(!sim.sams[0].alive()); + } + + #[test] + fn stale_cover_is_unresolved_and_source_voxel_does_not_shield_everything() { + let ground = cache(&CoverWorld { wall: true, opening: false }); + let from = DVec3::new(0.5, 71.5, 8.5); + let end = DVec3::new(10.5, 71.5, 8.5); + assert_eq!(ground.world.blast_cover(from, end, 1210), None); + let source = DVec3::new(8.5, 71.5, 8.5); + assert_eq!(ground.world.blast_cover(source, end, 10), Some(false)); + } +} diff --git a/airdef-mod/src/sim/doctrine.rs b/airdef-mod/src/sim/doctrine.rs index 358a861..fb8f5da 100644 --- a/airdef-mod/src/sim/doctrine.rs +++ b/airdef-mod/src/sim/doctrine.rs @@ -194,18 +194,6 @@ mod tests { assert_eq!(d.area_blocks(DVec3::ZERO, &away, 0), NO_THREAT); } - #[test] - fn the_save_line_is_what_the_layout_reads() { - let mut d = Doctrine { salvo: Salvo::Economy, reserve: 3, ..Default::default() }; - d.sector = Some((1.5, 0.5)); - d.guards = vec![DVec3::new(1.0, 2.0, 3.0)]; - let text = format!("sam {} 0 70 0 0\ndoctrine 0 {}\n", SamKind::Patriot.key(), d.save_text()); - let l = crate::layout::Layout::parse(&text).unwrap(); - assert_eq!(l.doctrines, vec![(0, d)]); - assert!(crate::layout::Layout::parse("doctrine 0 loud 1 - - 0\n").is_err()); - assert!(crate::layout::Layout::parse("doctrine 0 eco 1 - - 2 1 2 3\n").is_err(), "guards cut short"); - } - use super::sites::testland::{Land, serve}; /// One Patriot against one Shahed flying in from the north at its aim, the doctrine set before the raid: @@ -231,7 +219,7 @@ mod tests { } // The explanation while the target is in the sky: all the lines it gave then. if sim.tick % 10 == 0 { - lines.extend(sim.why(0).into_iter().map(|c| c.text)); + lines.extend(sim.why(0).into_iter().map(|c| c.legacy_text().to_owned())); } } (sim.sams[0].fired, first, lines) diff --git a/airdef-mod/src/sim/durable.rs b/airdef-mod/src/sim/durable.rs new file mode 100644 index 0000000..8df7d57 --- /dev/null +++ b/airdef-mod/src/sim/durable.rs @@ -0,0 +1,941 @@ +//! The enemy's air state in the autosave (#127): the raid's drones, the launch queue, the retarget +//! jobs still waiting, the picture they were chosen from and the land they were planned over. +//! +//! A route job alone is not durable. Its drone may still be on the ground or already flying, and its +//! aim is only meaningful with the picture the adaptive tick chose it from and the relief snapshot of +//! that knowledge version. So the save writes the whole air state that a pending job depends on, and +//! the load rebuilds it: drones, launch queue, jobs (with their id, aim, unit, version, priority and +//! intent), the contacts, and each relief version a pending job still uses. Nothing of the live +//! picture is mixed into a replay: a recording keeps trajectories, and this section is written and +//! read for the live simulation only. +//! +//! The format is its own versioned section of `state_text` (`air 1 ...`). A save without the section +//! (every save made before #127) loads exactly as before: the layout, and an empty sky. Every value is +//! checked before anything is restored, like [`layout`](crate::layout): a bad line refuses the whole +//! save and names the line and the field. Everything is bounded — drones, queued launches, jobs, +//! contacts, route points and relief cells — so a saved air state can never grow the file without limit. +use super::*; +use std::collections::{BTreeMap, HashMap}; + +/// The section's format version. A different one is refused rather than half-read. +const VERSION: u32 = 1; +/// Drones in the air a save may hold. A raid is normally tens of drones; this bound keeps the +/// section far inside the host's per-write storage budget even if a scenario makes thousands. +const MAX_DRONES: usize = 512; +/// Bytes the section may take before further drones are left out (the host allows 1 MiB a write; +/// the layout and the headroom of the rest of the state stay clear of it). +const MAX_BYTES: usize = 900_000; +/// Route points one drone may have in a save. +const MAX_ROUTE: usize = 512; +/// Contacts (the picture, and a job's picture rebuilt from it) a save may hold. +const MAX_CONTACTS: usize = 4096; +/// Relief versions a save may hold, and the cells of each. +const MAX_RELIEFS: usize = 8; +const MAX_CELLS: usize = 40_000; + +/// What the air section of a save holds, checked as a whole before the scene is touched. +pub struct AirSave { + tick: u64, + job_next: u32, + kills: [u32; ew::SECTORS], + hits: [u32; ew::SECTORS], + contacts: Vec, + drones: Vec, + queue: Vec<(u64, Drone)>, + jobs: Vec, + /// The land new retarget jobs take as their version. + known_relief: Option>, +} + +pub(crate) struct JobRec { + id: u32, + drone: u32, + aim: DVec3, + unit: u32, + version: u64, + intent: ew::Intent, + relief: Option>, +} + +/// A saved line's words, with the line number for the refusal. +struct Line<'a> { + n: usize, + word: &'a str, + w: Vec<&'a str>, +} + +impl Line<'_> { + fn bad(&self, field: &str, why: &str) -> String { + format!("строка {}: «{}», поле {field}: {why}", self.n, self.word) + } + fn word(&self, i: usize, field: &str) -> Result<&str, String> { + self.w.get(i).copied().ok_or_else(|| self.bad(field, "строка оборвана")) + } + fn count(&self, i: usize, field: &str, max: usize) -> Result { + let v = self.word(i, field)?; + let n: usize = v.parse().map_err(|_| self.bad(field, &format!("«{v}» не целое число")))?; + if n > max { + return Err(self.bad(field, &format!("{n} больше {max}"))); + } + Ok(n) + } + fn int(&self, i: usize, field: &str, lo: i64, hi: i64) -> Result { + let v = self.word(i, field)?; + let n: i64 = v.parse().map_err(|_| self.bad(field, &format!("«{v}» не целое число")))?; + if n < lo || n > hi { + return Err(self.bad(field, &format!("{n} вне допустимого {lo}..{hi}"))); + } + Ok(n) + } + fn flag(&self, i: usize, field: &str) -> Result { + match self.word(i, field)? { + "true" => Ok(true), + "false" => Ok(false), + v => Err(self.bad(field, &format!("«{v}»: нужно true или false"))), + } + } + fn num(&self, i: usize, field: &str) -> Result { + let v = self.word(i, field)?; + let x: f64 = v.parse().map_err(|_| self.bad(field, &format!("«{v}» не число")))?; + if !x.is_finite() || x.abs() > 3.0e7 { + return Err(self.bad(field, &format!("«{v}» не конечное допустимое число"))); + } + Ok(x) + } + fn opt_tick(&self, i: usize, field: &str) -> Result, String> { + if self.word(i, field)? == "-" { + return Ok(None); + } + Ok(Some(self.int(i, field, 0, 1 << 40)? as u64)) + } +} + +/// `x` as the save writes coordinates: centimetres are plenty for a flight, and keep the file small. +fn coord(x: f64) -> String { + format!("{x:.2}") +} + +/// One run of the relief as the save writes it: `u` for unknown land, `x` for known. +fn run_token(height: Option, run: usize) -> String { + height.map_or_else(|| format!("u{run}"), |h| format!("{h}x{run}")) +} + +fn tick(x: Option) -> String { + x.map_or_else(|| "-".into(), |v| v.to_string()) +} + +fn intent_of(key: &str) -> Option { + ew::Intent::from_key(key) +} + +fn flight_key(f: Flight) -> &'static str { + match f { + Flight::Cruise => "cruise", + Flight::Dive => "dive", + Flight::Ballistic => "ballistic", + Flight::Falling => "falling", + } +} + +fn flight_of(key: &str) -> Option { + [Flight::Cruise, Flight::Dive, Flight::Ballistic, Flight::Falling].into_iter().find(|f| flight_key(*f) == key) +} + +impl Drone { + /// The drone as one line of the save: its flight, its aim and intent, and its route. + fn air_line(&self, word: &str, lead: &str) -> String { + let mut route = String::from("-"); + if !self.route.is_empty() { + route = self.route.len().to_string(); + for p in &self.route { + route += &format!(" {} {} {}", coord(p.x), coord(p.y), coord(p.z)); + } + } + format!( + "{word} {lead}{id} {kind} {} {} {} {} {} {} {} {} {} {flight} {intent} {unit} {since} {switches} {conf} {} {} {} {age} {engaged} {sector} {low} {jammed} {smart} {hold} {} {} {} {route}", + coord(self.pos.x), + coord(self.pos.y), + coord(self.pos.z), + coord(self.vel.x), + coord(self.vel.y), + coord(self.vel.z), + coord(self.target.x), + coord(self.target.y), + coord(self.target.z), + coord(self.err.x), + coord(self.err.y), + coord(self.err.z), + if self.pace.best.is_finite() { coord(self.pace.best) } else { "-".into() }, + self.pace.at, + if self.pace.route == usize::MAX { "-".to_string() } else { self.pace.route.to_string() }, + kind = self.kind.spec().key, + flight = flight_key(self.flight), + intent = self.intent.key(), + unit = self.aim.unit, + since = self.aim.since, + switches = self.aim.switches, + conf = self.aim.confidence.map_or("-", |c| c.name()), + age = self.age, + engaged = self.engaged, + sector = self.sector, + low = self.low, + jammed = self.jammed, + smart = self.smart, + hold = self.hold, + id = self.id, + ) + } +} + +impl Sim { + /// A cheap shape of the air state for the autosave's dirty check: a launch, a loss, a new or + /// finished job changes it, a drone's own progress through the sky does not. A save takes the + /// air state at those moments, not on every tick of a flight (#127). + pub fn air_mark(&self) -> u64 { + let drones = self.drones.iter().filter(|d| d.alive()).count() as u64; + drones + ^ (self.queue.len() as u64) << 12 + ^ (self.retargets.len() as u64) << 24 + ^ (self.intel.contacts().len() as u64) << 36 + ^ (self.job_next as u64) << 48 + } + + /// The air section of the state text: `None` when there is nothing to keep (an empty sky and no + /// picture), so a save of a quiet world does not grow. Every pending job's drone is written with + /// it, together with the picture and the relief versions the job still plans over (#127). + pub fn air_text(&self) -> Option { + // What the parser accepts is what is written: a save this build writes always reads back. + let contacts: Vec = self.intel.contacts().values().copied().take(MAX_CONTACTS).collect(); + let drones: Vec<&Drone> = self.drones.iter().filter(|d| d.alive()).take(MAX_DRONES).collect(); + let queued: Vec<&(u64, Drone)> = self.queue.iter().take(MAX_DRONES).collect(); + // The relief versions still referenced: the global one and every pending job's. They are + // resolved before the drone lines so the head is fixed while the body's size is measured. + let mut reliefs: Vec> = Vec::new(); + let token_of = |r: &std::sync::Arc, reliefs: &mut Vec>| -> Option { + let at = std::sync::Arc::as_ptr(r) as usize; + if let Some(i) = reliefs.iter().position(|x| std::sync::Arc::as_ptr(x) as usize == at) { + return Some(i); + } + if reliefs.len() >= MAX_RELIEFS { + return None; + } + reliefs.push(std::sync::Arc::clone(r)); + Some(reliefs.len() - 1) + }; + let global = self.known_relief.as_ref().and_then(|r| token_of(r, &mut reliefs)); + let job_relief: Vec> = self.retargets.iter().map(|j| j.relief.as_ref().and_then(|r| token_of(r, &mut reliefs))).collect(); + let (kills, hits) = self.intel.sectors(); + // The head, with the counts filled in later; its length is what the body is measured against. + let mut head = format!("air {VERSION} {} {} 0 0 0 {} {}\n", self.tick, self.job_next, contacts.len(), reliefs.len()); + head += "airintel"; + for v in kills.iter().chain(hits.iter()) { + head += &format!(" {v}"); + } + head += "\n"; + head += &format!("airglobal {}\n", global.map_or_else(|| "-".into(), |t| t.to_string())); + for (i, r) in reliefs.iter().enumerate() { + let g = r.grid(); + let mut line = format!("airrelief {i} {} {} {} {} ", coord(g.min.x), coord(g.min.z), coord(g.cell), g.size); + let mut run: Option<(Option, usize)> = None; + for h in r.heights() { + let key = h.is_finite().then(|| h.round() as i32); + match run { + Some((k, n)) if k == key => run = Some((k, n + 1)), + Some((k, n)) => { + line += &format!("{} ", run_token(k, n)); + run = Some((key, 1)); + } + None => run = Some((key, 1)), + } + } + if let Some((k, n)) = run { + line += &run_token(k, n); + } + head += &line; + head += "\n"; + } + for c in &contacts { + head += &format!( + "aircontact {} {} {} {} {} {} {} {} {} {}\n", + c.id, + c.kind.spec().key, + coord(c.at.x), + coord(c.at.y), + coord(c.at.z), + c.tick, + c.seen.name(), + tick(c.emission), + tick(c.destroyed), + c.launches + ); + } + // The bodies go in while the section stays inside its byte bound; a drone left out takes its + // job with it (a job whose drone is not saved could never be restored). + let mut body = String::new(); + let mut saved: std::collections::HashSet = std::collections::HashSet::new(); + let fits = |head: &str, body: &str, line: &str| head.len() + 32 + body.len() + line.len() <= MAX_BYTES; + let mut drone_count = 0usize; + for d in &drones { + let line = d.air_line("airdrone", ""); + if !fits(&head, &body, &line) { + break; + } + body += &line; + body += "\n"; + saved.insert(d.id); + drone_count += 1; + } + let mut queued_count = 0usize; + for (at, d) in &queued { + let line = d.air_line("airqueue", &format!("{at} ")); + if !fits(&head, &body, &line) { + break; + } + body += &line; + body += "\n"; + saved.insert(d.id); + queued_count += 1; + } + let units: std::collections::HashSet = contacts.iter().map(|c| c.id).collect(); + let jobs: Vec<(&ew::RouteJob, Option)> = self + .retargets + .iter() + .zip(job_relief) + .filter(|(j, _)| saved.contains(&j.drone) && (j.unit == 0 || units.contains(&j.unit))) + .collect(); + if drone_count == 0 && queued_count == 0 && jobs.is_empty() && contacts.is_empty() { + return None; + } + let head = head.replacen( + &format!("air {VERSION} {} {} 0 0 0 ", self.tick, self.job_next), + &format!("air {VERSION} {} {} {drone_count} {queued_count} {} ", self.tick, self.job_next, jobs.len()), + 1, + ); + let mut text = head; + text += &body; + for (job, relief) in jobs { + text += &format!( + "airjob {} {} {} {} {} {} {} {} {} {}\n", + job.id, + job.drone, + coord(job.aim.x), + coord(job.aim.y), + coord(job.aim.z), + job.unit, + job.version, + job.priority(), + job.intent.key(), + relief.map_or_else(|| "-".into(), |t| t.to_string()) + ); + } + Some(text) + } + + /// Reads the air section of `text` (the lines `air_text` writes). `None` when the text has none: + /// an older save, restored with an empty sky as before. Everything is checked before it is used. + pub fn parse_air(text: &str) -> Result, String> { + let lines: Vec<(usize, Vec<&str>)> = text + .lines() + .enumerate() + .map(|(n, raw)| (n + 1, raw.split_whitespace().collect::>())) + .collect(); + let heads: Vec<&(usize, Vec<&str>)> = lines.iter().filter(|(_, w)| w.first() == Some(&"air")).collect(); + let Some((head_n, head)) = heads.first().copied() else { + // Lines of a section whose header is gone are a corrupt file, not an old save. + if let Some((n, w)) = lines.iter().find(|(_, w)| w.first().is_some_and(|w| w.starts_with("air"))) { + return Err(format!("строка {n}: «{}», воздушная часть без заголовка air", w[0])); + } + return Ok(None); + }; + if heads.len() > 1 { + return Err(format!("строка {}: воздушная часть записана дважды", heads[1].0)); + } + let l = Line { n: *head_n, word: "air", w: head.clone() }; + if head.len() != 9 { + return Err(l.bad("заголовок", "нужны версия, такт, счётчики и число версий рельефа")); + } + let version = l.int(1, "версия воздушной части", 0, 1 << 30)? as u32; + if version != VERSION { + return Err(l.bad("версия воздушной части", &format!("эта сборка читает только {VERSION}"))); + } + let tick = l.int(2, "такт", 0, 1 << 40)?.max(0) as u64; + let job_next = l.int(3, "номер задания", 0, 1 << 30)? as u32; + let n_drones = l.count(4, "дроны", MAX_DRONES)?; + let n_queue = l.count(5, "очередь запуска", MAX_DRONES)?; + let n_jobs = l.count(6, "задания", ew::JOBS_MAX)?; + let n_contacts = l.count(7, "контакты", MAX_CONTACTS)?; + let n_reliefs = l.count(8, "версии рельефа", MAX_RELIEFS)?; + // Every line of the section, and nothing else: a truncated or foreign line is refused. + let mut intel: Option<[u32; ew::SECTORS * 2]> = None; + let mut global: Option = None; + let mut reliefs: Vec>> = vec![None; n_reliefs]; + let mut contacts: BTreeMap = BTreeMap::new(); + let mut drones: BTreeMap = BTreeMap::new(); + let mut queue: Vec<(u64, Drone)> = Vec::new(); + let mut jobs: Vec = Vec::new(); + for (n, w) in &lines { + if w.first() == Some(&"air") { + continue; + } + let Some(&word) = w.first() else { continue }; + if !word.starts_with("air") { + continue; + } + let l = Line { n: *n, word, w: w.clone() }; + match word { + "airintel" => { + if w.len() != 1 + ew::SECTORS * 2 { + return Err(l.bad("счётчики секторов", &format!("нужны {} чисел", ew::SECTORS * 2))); + } + let mut v = [0u32; ew::SECTORS * 2]; + for (i, slot) in v.iter_mut().enumerate() { + *slot = l.int(i + 1, "счётчики секторов", 0, 1 << 30)? as u32; + } + intel = Some(v); + } + "airglobal" => { + if w.len() != 2 { + return Err(l.bad("рельеф", "нужна одна ссылка на версию рельефа")); + } + let t = l.word(1, "рельеф")?; + if t != "-" { + let at = l.count(1, "рельеф", MAX_RELIEFS)?; + if at >= n_reliefs { + return Err(l.bad("рельеф", "ссылка на неписаную версию")); + } + global = Some(at); + } + } + "airrelief" => { + let at = l.count(1, "номер рельефа", MAX_RELIEFS)?; + if at >= n_reliefs { + return Err(l.bad("номер рельефа", "больше объявленных")); + } + let grid = raid::Grid { + min: DVec3::new(l.num(2, "рельеф X")?, 0.0, l.num(3, "рельеф Z")?), + cell: l.num(4, "размер клетки")?, + size: l.int(5, "сторона сетки", 1, 400)? as i32, + }; + if grid.cell <= 0.0 { + return Err(l.bad("размер клетки", "должен быть больше нуля")); + } + let cells = (grid.size * grid.size) as usize; + if cells > MAX_CELLS { + return Err(l.bad("рельеф", &format!("{cells} клеток больше {MAX_CELLS}"))); + } + let mut heights = Vec::with_capacity(cells); + for t in &w[6..] { + // `u` is unknown land, `x` a known height repeated. + let (h, run) = if let Some(n) = t.strip_prefix('u') { + (f32::NAN, n) + } else { + let Some((head, n)) = t.split_once('x') else { + return Err(l.bad("рельеф", &format!("«{t}» не повтор высоты"))); + }; + let v: i32 = head.parse().map_err(|_| l.bad("рельеф", &format!("«{head}» не высота")))?; + (v as f32, n) + }; + let run: usize = run.parse().map_err(|_| l.bad("рельеф", "длина повтора не число"))?; + if run == 0 || run > cells { + return Err(l.bad("рельеф", &format!("повтор {run} вне 1..{cells}"))); + } + if heights.len() + run > cells { + return Err(l.bad("рельеф", "повторы длиннее сетки")); + } + heights.extend(std::iter::repeat_n(h, run)); + } + if heights.len() != cells { + return Err(l.bad("рельеф", "повторы не покрывают сетку")); + } + if reliefs[at].is_some() { + return Err(l.bad("номер рельефа", "версия записана дважды")); + } + reliefs[at] = Some(std::sync::Arc::new(ew::KnownRelief::new(grid, heights))); + } + "aircontact" => { + if w.len() != 11 { + return Err(l.bad("контакт", "нужны 10 полей наблюдения")); + } + let id = l.int(1, "номер юнита", 0, 1 << 30)? as u32; + let kind = SamKind::from_key(l.word(2, "тип")?).ok_or_else(|| l.bad("тип", "неизвестный тип"))?; + let at = DVec3::new(l.num(3, "контакт X")?, l.num(4, "контакт Y")?, l.num(5, "контакт Z")?); + let seen = ew::Seen::from_key(l.word(7, "канал")?).ok_or_else(|| l.bad("канал", "launch, emission или jamming"))?; + let c = ew::Contact { + id, + kind, + at, + tick: l.int(6, "такт наблюдения", 0, tick as i64)? as u64, + seen, + emission: l.opt_tick(8, "фиксация эмиссии")?, + destroyed: l.opt_tick(9, "наблюдённое уничтожение")?, + launches: l.int(10, "наблюдённые пуски", 0, u16::MAX as i64)? as u16, + }; + if contacts.insert(id, c).is_some() { + return Err(l.bad("номер юнита", "контакт записан дважды")); + } + } + "airdrone" | "airqueue" => { + let at = if word == "airqueue" { Some(l.int(1, "такт запуска", 0, 1 << 40)? as u64) } else { None }; + let (d, end) = parse_drone(&l, if at.is_some() { 2 } else { 1 })?; + if w.len() != end { + return Err(l.bad("дрон", "лишние поля")); + } + if at.is_some() { + queue.push((at.unwrap(), d)); + } else if drones.insert(d.id, d).is_some() { + return Err(l.bad("номер дрона", "дрон записан дважды")); + } + } + "airjob" => { + if w.len() != 11 { + return Err(l.bad("задание", "нужны 9 полей задания")); + } + let id = l.int(1, "номер задания", 0, 1 << 30)? as u32; + let drone = l.int(2, "дрон задания", 0, 1 << 30)? as u32; + let aim = DVec3::new(l.num(3, "цель X")?, l.num(4, "цель Y")?, l.num(5, "цель Z")?); + let unit = l.int(6, "юнит", 0, 1 << 30)? as u32; + let version = l.int(7, "версия знаний", 0, tick as i64)? as u64; + let priority = l.int(8, "приоритет", 0, 9)? as u8; + let intent = intent_of(l.word(9, "намерение")?).ok_or_else(|| l.bad("намерение", "plan, sead, react или replace"))?; + if priority != intent.priority() { + return Err(l.bad("приоритет", "не совпадает с намерением")); + } + let relief = match l.word(10, "рельеф")? { + "-" => None, + t => { + let at = t.parse::().map_err(|_| l.bad("рельеф", "не номер версии"))?; + if at >= n_reliefs { + return Err(l.bad("рельеф", "ссылка на неписаную версию")); + } + reliefs[at].clone() + } + }; + jobs.push(JobRec { id, drone, aim, unit, version, intent, relief }); + } + other => return Err(l.bad("строка", &format!("неизвестная строка воздушной части «{other}»"))), + } + } + // The counts are the file's own promise: a section cut short is refused, not half-restored. + if contacts.len() != n_contacts { + return Err(format!("воздушная часть: контактов {} вместо {n_contacts}", contacts.len())); + } + if drones.len() != n_drones { + return Err(format!("воздушная часть: дронов {} вместо {n_drones}", drones.len())); + } + if queue.len() != n_queue { + return Err(format!("воздушная часть: очередь {} вместо {n_queue}", queue.len())); + } + if jobs.len() != n_jobs { + return Err(format!("воздушная часть: заданий {} вместо {n_jobs}", jobs.len())); + } + if reliefs.iter().any(Option::is_none) { + return Err("воздушная часть: объявленная версия рельефа не записана".into()); + } + let mut seen_drones: std::collections::HashSet = drones.keys().copied().collect(); + for (_, d) in &queue { + if !seen_drones.insert(d.id) { + return Err("воздушная часть: дрон записан и в небе, и в очереди".into()); + } + } + if let Some(g) = global && reliefs[g].is_none() { + return Err("воздушная часть: глобальный рельеф не записан".into()); + } + let intel = intel.ok_or("воздушная часть: нет счётчиков секторов")?; + let mut kills = [0u32; ew::SECTORS]; + let mut hits = [0u32; ew::SECTORS]; + kills.copy_from_slice(&intel[..ew::SECTORS]); + hits.copy_from_slice(&intel[ew::SECTORS..]); + Ok(Some(AirSave { + tick, + job_next, + kills, + hits, + contacts: contacts.into_values().collect(), + drones: drones.into_values().collect(), + queue, + jobs, + known_relief: global.and_then(|g| reliefs[g].clone()), + })) + } + + /// Puts a checked air section back: the drones, the launch queue, the jobs (with the picture and + /// relief of each version) and the contacts. Stale jobs are cancelled, the queue is bounded, and a + /// job whose drone, aim or unit no longer stands is dropped — a restored job is never a ghost. + /// The live air state is replaced, never merged: what the sky held before the load goes. + pub fn restore_air(&mut self, save: AirSave) -> u32 { + self.tick = save.tick; + // New jobs must not reuse an id a restored job already holds. + let highest = save.jobs.iter().map(|j| j.id).max().unwrap_or(0); + self.job_next = save.job_next.max(1).max(highest.saturating_add(1)); + self.intel = ew::Intel::restore(save.contacts.clone(), save.kills, save.hits); + self.known_relief = save.known_relief; + self.drones = save.drones; + self.queue = save.queue; + self.retargets.clear(); + // A job's picture is its version's: the contacts observed no later than it, plus its own + // target (whose last saved observation stands in for one made after the version). + let mut by_version: HashMap>> = HashMap::new(); + for rec in save.jobs { + let known = by_version.entry(rec.version).or_insert_with(|| { + let picture: Vec = save + .contacts + .iter() + .filter(|c| c.tick <= rec.version || c.id == rec.unit) + .copied() + .collect(); + std::sync::Arc::new(picture) + }); + let job = ew::RouteJob::restored(rec.id, rec.drone, rec.aim, rec.unit, rec.version, rec.intent, std::sync::Arc::clone(known), rec.relief); + self.retargets.push_back(job); + } + // Jobs the saved picture no longer stands behind, and anything past the bound, go now. + self.prune_air() + } +} + +/// One drone's line (after its word and, for the queue, its launch tick). Returns the drone and the +/// number of words the body took. +fn parse_drone(l: &Line, first: usize) -> Result<(Drone, usize), String> { + let id = l.int(first, "номер дрона", 1, 1 << 30)? as u32; + let kind = DroneKind::from_key(l.word(first + 1, "тип дрона")?).ok_or_else(|| l.bad("тип дрона", "неизвестный тип"))?; + let pos = DVec3::new(l.num(first + 2, "дрон X")?, l.num(first + 3, "дрон Y")?, l.num(first + 4, "дрон Z")?); + let vel = DVec3::new(l.num(first + 5, "скорость X")?, l.num(first + 6, "скорость Y")?, l.num(first + 7, "скорость Z")?); + let target = DVec3::new(l.num(first + 8, "цель X")?, l.num(first + 9, "цель Y")?, l.num(first + 10, "цель Z")?); + let flight = flight_of(l.word(first + 11, "полёт")?).ok_or_else(|| l.bad("полёт", "cruise, dive, ballistic или falling"))?; + let intent = intent_of(l.word(first + 12, "намерение")?).ok_or_else(|| l.bad("намерение", "plan, sead, react или replace"))?; + let unit = l.int(first + 13, "юнит намерения", 0, 1 << 30)? as u32; + let since = l.int(first + 14, "такт выбора цели", 0, 1 << 40)? as u64; + let switches = l.int(first + 15, "переключения", 0, u16::MAX as i64)? as u16; + let conf = match l.word(first + 16, "уверенность")? { + "-" => None, + key => Some(ew::Confidence::from_key(key).ok_or_else(|| l.bad("уверенность", "doubtful, probable или confirmed"))?), + }; + let err = DVec3::new(l.num(first + 17, "ошибка X")?, l.num(first + 18, "ошибка Y")?, l.num(first + 19, "ошибка Z")?); + let age = l.int(first + 20, "возраст", 0, 1 << 30)? as u32; + let engaged = l.int(first + 21, "обстрелян", 0, 1 << 20)? as u32; + let sector = l.int(first + 22, "сектор", 0, ew::SECTORS as i64)? as u8; + let low = l.flag(first + 23, "низко")?; + let jammed = l.flag(first + 24, "подавлен")?; + let smart = l.flag(first + 25, "умный")?; + let hold = l.int(first + 26, "задержка", 0, u16::MAX as i64)? as u16; + let best = match l.word(first + 27, "лучшее приближение")? { + "-" => f64::INFINITY, + _ => l.num(first + 27, "лучшее приближение")?, + }; + let pat = l.int(first + 28, "такт приближения", 0, 1 << 30)? as u32; + let proute = match l.word(first + 29, "точек приближения")? { + "-" => usize::MAX, + _ => l.count(first + 29, "точек приближения", MAX_ROUTE)?, + }; + let mut at = first + 30; + let mut route = Vec::new(); + if l.word(at, "маршрут")? != "-" { + let n = l.count(at, "точек маршрута", MAX_ROUTE)?; + at += 1; + for _ in 0..n { + route.push(DVec3::new(l.num(at, "маршрут X")?, l.num(at + 1, "маршрут Y")?, l.num(at + 2, "маршрут Z")?)); + at += 3; + } + } else { + at += 1; + } + let drone = Drone { + id, + kind, + pos, + vel, + target, + flight, + roll: 0.0, + spin: 0.0, + age, + engaged, + route, + low, + jammed, + sector, + smart, + nav: Nav::default(), + err, + intent, + aim: ew::Aim { unit, since, switches, confidence: conf }, + hold, + pace: Pace { best, at: pat, route: proute }, + }; + Ok((drone, at)) +} + +/// The jobs' priorities and intents as the report words them (tests and diagnostics). +#[cfg(test)] +impl Sim { + pub(super) fn pending_intents(&self) -> Vec<(u32, ew::Intent)> { + self.retargets.iter().map(|j| (j.id, j.intent)).collect() + } +} + +#[cfg(test)] +mod durable_tests { + use super::*; + + fn contact(id: u32, kind: SamKind, at: DVec3, tick: u64, seen: ew::Seen, launches: u16) -> ew::Contact { + ew::Contact { id, kind, at, tick, seen, emission: Some(tick), destroyed: None, launches } + } + + /// A small force, a few known contacts and a relief version, as a smart raid leaves them. + fn raid_state(seed: u64) -> Sim { + let mut sim = Sim::new(seed); + let grid = raid::Grid { min: DVec3::new(-1024.0, 0.0, -1024.0), cell: 32.0, size: 64 }; + let mut heights = vec![71.0f32; (grid.size * grid.size) as usize]; + for i in 0..8 { + heights[(i * grid.size + 3) as usize] = f32::NAN; + } + sim.known_relief = Some(std::sync::Arc::new(ew::KnownRelief::new(grid, heights))); + let mut ids = Vec::new(); + for (k, kind) in [SamKind::S400, SamKind::Radar, SamKind::Ew].into_iter().enumerate() { + let id = sim.id(); + let at = DVec3::new(-400.0 + k as f64 * 400.0, 70.0, 0.0); + let c = contact(id, kind, at, 10, if kind == SamKind::Ew { ew::Seen::Jamming } else { ew::Seen::Emission }, k as u16); + sim.intel = ew::Intel::restore([c].to_vec(), [0; ew::SECTORS], [0; ew::SECTORS]); + ids.push(id); + } + sim.intel = ew::Intel::restore( + ids.iter().enumerate().map(|(k, id)| contact(*id, [SamKind::S400, SamKind::Radar, SamKind::Ew][k], DVec3::new(-400.0 + k as f64 * 400.0, 70.0, 0.0), 10, ew::Seen::Emission, k as u16)).collect(), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + ); + sim.tick = 120; + sim.job_next = 7; + // One drone in the air with a route and an aim, one still on the ground. + let mut flying = sim.make_drone(DroneKind::Kh22, DVec3::new(-900.0, 130.0, -300.0), DVec3::X, DVec3::new(-400.0, 70.0, 0.0)); + flying.smart = true; + flying.intent = ew::Intent::React; + flying.aim = ew::Aim::unit(ids[0], 100, ew::Confidence::Confirmed); + flying.target = DVec3::new(-400.0, 70.0, 0.0) + flying.err; + flying.route = vec![DVec3::new(-900.0, 70.0, -300.0), DVec3::new(-600.0, 70.0, -100.0), DVec3::new(-400.0, 70.0, 0.0)]; + flying.low = true; + flying.jammed = true; + flying.sector = 5; + flying.age = 77; + sim.drones.push(flying); + let mut queued = sim.make_drone(DroneKind::Geran3, DVec3::new(-1000.0, 90.0, 200.0), DVec3::X, DVec3::new(400.0, 70.0, 0.0)); + queued.smart = true; + queued.intent = ew::Intent::Sead; + queued.aim = ew::Aim::unit(ids[1], 110, ew::Confidence::Probable); + queued.route = vec![DVec3::new(-1000.0, 70.0, 200.0), DVec3::new(400.0, 70.0, 0.0)]; + let queued_id = queued.id; + sim.queue.push((300, queued)); + // Two pending jobs: one of the plan, one a replacement, both with their own version. + let job = |id, drone, aim, unit, version, intent| { + ew::RouteJob::restored(id, drone, aim, unit, version, intent, std::sync::Arc::new(sim.intel.contacts().values().copied().collect()), sim.known_relief.clone()) + }; + sim.retargets.push_back(job(5, sim.drones[0].id, sim.drones[0].target, ids[0], 100, ew::Intent::React)); + let queued_target = sim.queue[0].1.target; + sim.retargets.push_back(job(6, queued_id, queued_target, ids[1], 120, ew::Intent::Replace)); + sim + } + + #[test] + fn the_air_state_round_trips_every_job_field() { + let sim = raid_state(71); + let text = sim.air_text().expect("a raid state has something to keep"); + let save = Sim::parse_air(&text).expect("the written section must read back").expect("the section is there"); + let mut back = Sim::new(1); + back.restore_air(save); + assert_eq!(back.tick, sim.tick); + assert_eq!(back.job_next, sim.job_next); + assert_eq!(back.intel.sectors(), sim.intel.sectors()); + assert_eq!(back.intel.contacts().len(), sim.intel.contacts().len()); + for (id, c) in sim.intel.contacts() { + let b = back.intel.contacts().get(id).expect("a contact was lost"); + assert_eq!(b.kind, c.kind); + assert!((b.at.distance(c.at)).abs() < 0.02 && b.tick == c.tick && b.seen == c.seen && b.launches == c.launches); + } + assert_eq!(back.drones.len(), 1); + let (a, b) = (&sim.drones[0], &back.drones[0]); + assert_eq!((a.id, a.kind, a.smart, a.intent, a.sector, a.age, a.low, a.jammed), (b.id, b.kind, b.smart, b.intent, b.sector, b.age, b.low, b.jammed)); + assert_eq!((a.aim.unit, a.aim.since, a.aim.switches, a.aim.confidence), (b.aim.unit, b.aim.since, b.aim.switches, b.aim.confidence)); + assert!(a.pos.distance(b.pos) < 0.02 && a.target.distance(b.target) < 0.02 && a.vel.distance(b.vel) < 0.02); + assert_eq!(a.route.len(), b.route.len()); + assert!(a.route.iter().zip(&b.route).all(|(x, y)| x.distance(*y) < 0.02)); + assert_eq!(back.queue.len(), 1); + assert_eq!(back.queue[0].0, 300); + let (qa, qb) = (&sim.queue[0].1, &back.queue[0].1); + assert_eq!((qa.id, qa.intent, qa.aim.unit), (qb.id, qb.intent, qb.aim.unit)); + assert!(qa.route.iter().zip(&qb.route).all(|(x, y)| x.distance(*y) < 0.02)); + // The jobs keep their ids, aims, units, versions, priorities and intents; so do their pictures + // (the same version shares one) and the land they plan over. + assert_eq!(back.retargets.len(), 2); + for (j, b) in sim.retargets.iter().zip(&back.retargets) { + assert_eq!((j.id, j.drone, j.unit, j.version, j.priority(), j.intent), (b.id, b.drone, b.unit, b.version, b.priority(), b.intent)); + assert!(j.aim.distance(b.aim) < 0.02); + assert_eq!(j.relief.is_some(), b.relief.is_some()); + } + let (ra, rb) = (sim.known_relief.as_ref().unwrap(), back.known_relief.as_ref().unwrap()); + assert_eq!(ra.heights().len(), rb.heights().len()); + assert!(ra.heights().iter().zip(rb.heights()).all(|(x, y)| (x.is_nan() && y.is_nan()) || x == y)); + // Nothing is written for an empty sky. + assert!(Sim::new(3).air_text().is_none()); + } + + /// 40 smart drones, one adaptive tick, a bounded number of aims and jobs: the state a save is + /// taken in while routes are still being worked out. + fn pending_raid(seed: u64) -> Sim { + let mut sim = Sim::new(seed); + let mut contacts = Vec::new(); + for k in 0..8 { + let id = sim.id(); + contacts.push(contact(id, SamKind::S400, DVec3::new(-560.0 + k as f64 * 160.0, 70.0, -320.0), 0, ew::Seen::Emission, 0)); + } + sim.intel = ew::Intel::restore(contacts, [0; ew::SECTORS], [0; ew::SECTORS]); + for i in 0..40 { + let from = DVec3::new(-1800.0, 100.0, -800.0 + i as f64 * 40.0); + let mut d = sim.make_drone(DroneKind::Kh22, from, DVec3::X, DVec3::new(1800.0, 70.0, 1800.0)); + d.smart = true; + d.route = vec![from, DVec3::new(1800.0, 70.0, 1800.0)]; + sim.queue.push((0, d)); + } + sim.tick = 40; + sim.adapt(); + sim + } + + #[test] + fn a_pending_route_continues_after_a_save_and_a_load() { + let mut original = pending_raid(73); + assert!(!original.retargets.is_empty(), "the test no longer saves mid-queue"); + let text = original.air_text().expect("a queued raid must be durable"); + let mut restored = Sim::new(73); + restored.restore_air(Sim::parse_air(&text).unwrap().unwrap()); + assert_eq!(restored.retargets.len(), original.retargets.len(), "the pending jobs did not survive the save"); + assert_eq!(restored.retarget_route_state(), original.retarget_route_state(), "the restored jobs differ from the saved ones"); + // Both go on: the same adaptive ticks, the same jobs finished, the same routes. + for _ in 0..400 { + original.tick += 1; + restored.tick += 1; + original.adapt(); + restored.adapt(); + assert_eq!(restored.retarget_route_state(), original.retarget_route_state(), "the restored queue drifted"); + if original.retargets.is_empty() && restored.retargets.is_empty() && original.queue.iter().all(|(_, d)| d.aim.unit != 0) { + break; + } + } + assert!(original.retargets.is_empty() && restored.retargets.is_empty(), "the restored queue never finished"); + // Every drone of the restored raid has a real route, and every aim it was given is one it planned. + assert!(restored.queue.iter().all(|(_, d)| !d.route.is_empty() && d.aim.unit != 0)); + assert!(restored.queue.iter().all(|(_, d)| d.route.last().is_some_and(|p| p.distance(d.target) < 1e-6))); + } + + // #127: the section has a byte bound of its own. A raid with more (and longer-routed) drones than a + // save can hold keeps as many as fit, and every job it writes belongs to a drone that was written. + #[test] + fn the_section_stays_inside_its_byte_bound() { + let mut sim = Sim::new(101); + for i in 0..600 { + let from = DVec3::new(-2000.0 + i as f64, 120.0, 0.0); + let mut d = sim.make_drone(DroneKind::Kh22, from, DVec3::X, DVec3::new(2000.0, 70.0, 0.0)); + d.smart = true; + d.route = (0..200).map(|k| DVec3::new(-2000.0 + k as f64 * 20.0, 70.0, 0.0)).collect(); + sim.drones.push(d); + } + let text = sim.air_text().expect("a sky full of drones is durable"); + assert!(text.len() <= MAX_BYTES, "the section grew to {} bytes", text.len()); + let save = Sim::parse_air(&text).expect("a bounded section must read back").unwrap(); + assert!(save.drones.len() <= MAX_DRONES); + assert!(save.drones.len() > 100, "the bound left almost nothing"); + } + + #[test] + fn stale_ghost_and_duplicate_jobs_are_cancelled() { + let mut sim = pending_raid(79); + let kept = sim.retargets.len(); + let some = sim.retargets[0].clone(); + // A job for a drone that is not here. + sim.retargets.push_back(ew::RouteJob::restored(900, 999_999, DVec3::ZERO, 0, sim.tick, ew::Intent::Plan, std::sync::Arc::new(Vec::new()), None)); + // A job whose aim is no longer its drone's. + let mut stale = some.clone(); + stale.id = 901; + stale.aim += DVec3::X * 40.0; + sim.retargets.push_back(stale); + // An older duplicate for a drone that already has a newer job. + let mut older = some.clone(); + older.id = 902; + older.version = sim.tick.saturating_sub(1); + sim.retargets.push_back(older); + // A job older than the TTL. + let mut old = sim.retargets[0].clone(); + old.id = 903; + old.version = sim.tick.saturating_sub(ew::JOB_TTL + 1); + sim.retargets.push_back(old); + let cancelled = sim.prune_air(); + assert_eq!(cancelled, 4, "the stale, ghost, duplicate and old jobs were not all cancelled"); + assert_eq!(sim.retargets.len(), kept); + assert!(sim.retargets.iter().all(|j| j.id < 900)); + } + + #[test] + fn a_job_for_a_unit_the_picture_lost_is_cancelled_on_load() { + let sim = pending_raid(83); + let unit = sim.retargets[0].unit; + let text = sim.air_text().unwrap(); + assert!(sim.intel.contacts().contains_key(&unit)); + // Cut the job's unit out of the saved picture (and its count): the job cannot stand behind it. + let mut lines: Vec = text.lines().map(str::to_string).collect(); + let head = lines.iter().position(|l| l.starts_with("air ")).unwrap(); + let mut w: Vec = lines[head].split_whitespace().map(str::to_string).collect(); + w[7] = (w[7].parse::().unwrap() - 1).to_string(); + lines[head] = w.join(" "); + lines.retain(|l| !l.starts_with(&format!("aircontact {unit} "))); + let cut = lines.join("\n") + "\n"; + let mut restored = Sim::new(83); + let cancelled = restored.restore_air(Sim::parse_air(&cut).expect("the cut section is still well formed").unwrap()); + assert!(cancelled > 0, "a job for a unit the picture lost was kept"); + assert!(restored.retargets.iter().all(|j| j.unit != unit), "the ghost job survived the load"); + } + + #[test] + fn a_damaged_air_section_is_refused_with_its_line_and_field() { + let sim = raid_state(89); + let text = sim.air_text().unwrap(); + assert!(Sim::parse_air("sam s400 0 70 0 0\n").unwrap().is_none(), "an older save must read as an empty sky"); + let e = Sim::parse_air(&text.replace("air 1 ", "air 2 ")).err().expect("a future version was accepted"); + assert!(e.contains("версия"), "{e}"); + let e = Sim::parse_air(&format!("{text}airbogus 1\n")).err().expect("a foreign line in the section was accepted"); + assert!(e.contains("неизвестная строка"), "{e}"); + // A count that does not match the lines is a cut-off file, refused rather than half restored. + let cut: String = text.lines().filter(|l| !l.starts_with("airjob ")).map(|l| format!("{l}\n")).collect(); + let e = Sim::parse_air(&cut).err().expect("a cut-off section was accepted"); + assert!(e.contains("заданий"), "{e}"); + // A coordinate that is not a number, and a broken relief run, name their line. + let mut lines: Vec = text.lines().map(str::to_string).collect(); + let drone = lines.iter().position(|l| l.starts_with("airdrone ")).unwrap(); + let mut w: Vec = lines[drone].split_whitespace().map(str::to_string).collect(); + w[3] = "NaN".into(); + lines[drone] = w.join(" "); + let e = Sim::parse_air(&lines.join("\n")).err().expect("a non-finite coordinate was accepted"); + assert!(e.contains(&format!("строка {}", drone + 1)) && e.contains("дрон X"), "{e}"); + let e = Sim::parse_air(&text.replace("airrelief 0 ", "airrelief 0 x ")).err().expect("a broken relief line was accepted"); + assert!(e.contains("рельеф"), "{e}"); + } +} + +#[cfg(test)] +impl Sim { + /// The pending jobs and the routes of their drones, for the save/restore comparison. + pub(super) fn retarget_route_state(&self) -> Vec<(u32, u32, u32, u64, u8, i64, Vec<(i64, i64, i64)>)> { + self.retargets + .iter() + .map(|j| { + let route = self + .queue + .iter() + .map(|(_, d)| d) + .chain(self.drones.iter()) + .find(|d| d.id == j.drone) + .map(|d| d.route.iter().map(|p| ((p.x * 100.0).round() as i64, (p.y * 100.0).round() as i64, (p.z * 100.0).round() as i64)).collect()) + .unwrap_or_default(); + let q = |v: f64| (v * 100.0).round() as i64; + (j.id, j.drone, j.unit, j.version, j.priority(), q(j.aim.x), route) + }) + .collect() + } +} diff --git a/airdef-mod/src/sim/ew.rs b/airdef-mod/src/sim/ew.rs index 580905f..3d37fa9 100644 --- a/airdef-mod/src/sim/ew.rs +++ b/airdef-mod/src/sim/ew.rs @@ -7,7 +7,205 @@ //! away. The enemy keeps what it learns — which systems fired and which //! stations jammed (its next drones go for them), and from which bearings its //! raids were shot down or got through (its next raid comes from the best). +//! +//! What it learns is a picture, not the world (#127). Every unit it knows of +//! is a *contact*: the last observation a disclosure channel made of it — +//! a launch, a radar emission, a jamming transmission, or its fate watched by +//! the enemy's own munition. A contact is written only at the moment of the +//! observation and is never refreshed from what the unit does while +//! unobserved: a moved, switched-off, re-roled or destroyed unit stays in the +//! picture exactly as seen until something discloses it again. Between +//! observations the planner works from the picture alone; its confidence in a +//! contact is a plain function of the contact's age, and its idea of a unit's +//! capabilities is the type's published one — a contact says nothing about +//! damage, roles, link health or magazine. use super::*; +use std::collections::{BTreeMap, VecDeque}; +use std::sync::Arc; + +/// Why a smart drone is aimed where it is (#127). The reason is fixed when the aim is set and +/// kept apart from the aim itself: the plan's own objective, a SEAD job the plan chose, a +/// reaction to a fresh disclosure and a replacement after the picture killed the aim are +/// different decisions, with different priorities and different room to switch. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Intent { + /// The raid's own objective: the marked object, or the unit the plan sent it at when it was ordered. + Plan, + /// Planned SEAD: a radar the plan picked when the raid was ordered. + Sead, + /// A reaction to a disclosure: a station showed itself (jamming, an emission fix, a launch) + /// after the raid was in the air, and a drone was turned onto it. + React, + /// A replacement: the picture watched the previous aim die or forgot it, and this contact + /// is the nearest live one. + Replace, +} + +impl Intent { + pub fn name(self) -> &'static str { + match self { + Self::Plan => "plan", + Self::Sead => "sead", + Self::React => "react", + Self::Replace => "replace", + } + } + /// The key a save writes and reads. + pub(super) fn key(self) -> &'static str { + self.name() + } + pub(super) fn from_key(key: &str) -> Option { + [Self::Plan, Self::Sead, Self::React, Self::Replace].into_iter().find(|i| i.key() == key) + } + /// The planner's share of a tick this aim is worth: a planned SEAD job and a reaction to a + /// disclosure are finished before a replacement, and the plan's own objective before both (#127). + pub(super) fn priority(self) -> u8 { + match self { + Self::Plan => 3, + Self::Sead => 2, + Self::React => 1, + Self::Replace => 0, + } + } +} + +/// What a smart drone aims at and how firmly (#127): the unit (0 for a marked object), when the +/// aim was chosen, how many times it has been changed since, and the confidence of the contact it +/// rests on. This is the bookkeeping the switch throttle reads; [`Intent`] is why it aims there. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(super) struct Aim { + pub(super) unit: u32, + pub(super) since: u64, + pub(super) switches: u16, + pub(super) confidence: Option, +} + +impl Aim { + /// An aim at a marked object: no unit, and no confidence to speak of. + pub(super) fn point(since: u64) -> Self { + Self { unit: 0, since, switches: 0, confidence: None } + } + pub(super) fn unit(id: u32, since: u64, confidence: Confidence) -> Self { + Self { unit: id, since, switches: 0, confidence: Some(confidence) } + } + /// The same aim, chosen again: the switch count is kept, the clock and the belief are new. + pub(super) fn replaced(&self, unit: u32, since: u64, confidence: Confidence) -> Self { + Self { unit, since, switches: self.switches.saturating_add(1), confidence: Some(confidence) } + } +} + +/// A route still to be worked out after a retarget (#127). The drone already has its new aim; the job +/// gives it a route as the planner's budget allows, and its old route stays until then. +#[derive(Clone)] +pub(super) struct RouteJob { + /// The job's own id, for the save and the report: jobs may be replaced in place. + pub(super) id: u32, + /// The drone, by id: queue indices move as launches happen. + pub(super) drone: u32, + /// The new aim, the drone's navigation error included. + pub(super) aim: DVec3, + /// The unit it was sent at (0 when the aim is not a unit). + pub(super) unit: u32, + /// The adaptive tick whose picture chose the aim: the knowledge version. + pub(super) version: u64, + /// The enemy picture that tick knew, shared by every job of the tick. + pub(super) known: Arc>, + /// The land that tick's plan knew, shared by every job of the version. A later plan replacing the + /// global snapshot must not change a pending job's relief (#127). + pub(super) relief: Option>, + /// Why the aim was chosen, and the planner's share of a tick it is worth (#127). + pub(super) intent: Intent, +} + +impl RouteJob { + /// A job of this tick's picture; the id and the priority follow from the intent. + fn new(id: u32, drone: u32, aim: DVec3, unit: u32, version: u64, intent: Intent, known: Arc>, relief: Option>) -> Self { + Self { id, drone, aim, unit, version, known, relief, intent } + } + pub(super) fn priority(&self) -> u8 { + self.intent.priority() + } + /// A job read back from a save: its id, aim, unit, version, picture and relief are the file's. + pub(super) fn restored(id: u32, drone: u32, aim: DVec3, unit: u32, version: u64, intent: Intent, known: Arc>, relief: Option>) -> Self { + Self { id, drone, aim, unit, version, known, relief, intent } + } +} + +/// The known land of a smart plan (#127): the grid it was mapped on and its cell heights (NaN unknown). +/// Every retarget job of that knowledge version shares one. +pub(super) struct KnownRelief { + grid: raid::Grid, + heights: Vec, +} + +impl KnownRelief { + pub(super) fn new(grid: raid::Grid, heights: Vec) -> Self { + Self { grid, heights } + } + + /// The grid the land was mapped on, and its heights (NaN unknown): for the save. + pub(super) fn grid(&self) -> raid::Grid { + self.grid + } + pub(super) fn heights(&self) -> &[f32] { + &self.heights + } + + /// This land sampled on `grid`: the height of the nearest cell, NaN where this plan knew nothing. + /// None when no sampled cell was known at all. + pub(super) fn sampled_on(&self, grid: &raid::Grid) -> Option> { + let src = &self.grid; + let n = grid.size; + let mut out = vec![f32::NAN; (n * n) as usize]; + let (x1, z1) = (src.min.x + src.size as f64 * src.cell, src.min.z + src.size as f64 * src.cell); + let mut any = false; + for i in 0..n { + for j in 0..n { + let p = grid.centre((i, j), 0.0); + if p.x < src.min.x || p.x >= x1 || p.z < src.min.z || p.z >= z1 { + continue; + } + let (si, sj) = src.index(p); + let h = self.heights[(si * src.size + sj) as usize]; + if h.is_finite() { + out[(i * n + j) as usize] = h; + any = true; + } + } + } + any.then_some(out) + } +} + +#[cfg(test)] +impl RouteJob { + pub(super) fn for_test( + drone: u32, + aim: DVec3, + unit: u32, + version: u64, + known: Arc>, + relief: Option>, + ) -> Self { + Self { id: drone, drone, aim, unit, version, known, relief, intent: Intent::Plan } + } + pub(super) fn with_intent(mut self, intent: Intent) -> Self { + self.intent = intent; + self + } + pub(super) fn id(&self) -> u32 { + self.id + } +} + +/// The knowledge a route job was made with: its adaptive tick and the pointers to its known-units and +/// relief snapshots. A planner cache keyed only by one of them could mix two jobs' pictures (#127). +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct KnowledgeVersion { + tick: u64, + known: usize, + relief: Option, +} /// How far a station jams. pub const EW_RADIUS: f64 = 350.0; @@ -18,14 +216,224 @@ const COOL: f64 = 1.0 / 900.0; const READY: f64 = 0.3; /// Approach sectors the enemy keeps score of. pub const SECTORS: usize = 12; +/// Ticks an aim is kept before a fresh disclosure may replace it: a drone is not turned on +/// every adaptive tick, only when something new has actually shown itself (#127). +pub(super) const SWITCH_HOLD: u64 = 160; +/// How many times one drone's aim may be changed after its plan chose it. Past this it keeps +/// the aim it has: a long raid must not churn its routes on every new fix (#127). +pub(super) const SWITCH_CAP: u16 = 3; +/// New aims one adaptive tick may hand out, and ones it may give to the same contact: a mass +/// retarget is spread over the following ticks instead of replacing every route at once (#127). +pub(super) const ASSIGN_PER_TICK: usize = 16; +pub(super) const ASSIGN_PER_CONTACT: usize = 6; +/// How much nearer (blocks) a candidate must be than the current aim to be worth a switch when +/// its confidence is no better. A switch throws the current route away and costs the planner a +/// new one, so a marginal one is not made (#127). +pub(super) const SWITCH_GAIN: f64 = 120.0; +/// Route jobs the queue may hold at once, in memory and in a save (#127). Past this the lowest +/// priority (then the oldest) go: a retarget is best-effort, never an unbounded log. +pub(super) const JOBS_MAX: usize = 256; +/// A restored job older than this (ticks) is stale: the picture has moved on and the job is +/// cancelled rather than planned for an aim nobody chose any more (#127). +pub(super) const JOB_TTL: u64 = 1200; +/// What a cell of land a job's knowledge version does not know costs on top of the open ground +/// (the base cell is 1): unknown land is not free, so a replanned route keeps to what was mapped (#127). +pub(super) const UNKNOWN_LAND: f32 = 6.0; + +/// How a contact's trust decays with age alone (#127). The bands are the only source of +/// confidence: nothing is invented about a unit between observations. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum Confidence { + /// Older than `PROBABLE_AGE`: kept on the map and priced, not worth a strike of its own. + Doubtful, + /// Seen within `PROBABLE_AGE`: good enough to send SEAD at. + Probable, + /// Seen within `CONFIRMED_AGE`: as observed. + Confirmed, +} + +impl Confidence { + pub fn name(self) -> &'static str { + match self { + Self::Doubtful => "doubtful", + Self::Probable => "probable", + Self::Confirmed => "confirmed", + } + } + pub(super) fn from_key(key: &str) -> Option { + [Self::Doubtful, Self::Probable, Self::Confirmed].into_iter().find(|c| c.name() == key) + } +} + +/// What a disclosure channel showed the moment it disclosed a unit (#127). The channel +/// shows only its own fact: a launch says nothing about the unit's radar, an emission +/// fix says nothing about its weapons. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum Seen { + /// A launch was seen: it fired a missile. + Launch, + /// Its radar emission was fixed: the radar was up and transmitting. + Emission, + /// Its EW transmission was fixed: it was jamming. + Jamming, +} + +impl Seen { + pub fn name(self) -> &'static str { + match self { + Self::Launch => "launch", + Self::Emission => "emission", + Self::Jamming => "jamming", + } + } + pub(super) fn from_key(key: &str) -> Option { + [Self::Launch, Self::Emission, Self::Jamming].into_iter().find(|s| s.name() == key) + } +} + +/// The enemy's last observation of one unit (#127): where it was, what it was seen doing, +/// and when. Written only when a channel discloses the unit; what the unit does while +/// unobserved — moves, switches off, re-roles, spends its magazine, dies — never updates +/// it. Only a newer observation of the same unit can. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(super) struct Contact { + pub id: u32, + /// The type identified at the observation. + pub kind: SamKind, + /// Where it was when last observed. + pub at: DVec3, + /// When that was. + pub tick: u64, + /// What the last observation showed. + pub seen: Seen, + /// When its emission was last fixed (within this contact's life): the belief that its + /// own radar works rests on this, never on the unit's actual switch. + pub emission: Option, + /// When the enemy's own munition watched it die (battle damage assessment). Truth + /// read at that moment only; a unit destroyed unobserved keeps living in the picture. + pub destroyed: Option, + /// Launches seen of this unit, as observations. The channel counts what it watched go up; + /// it says nothing about the magazine that is left, which is never in the picture (#127). + pub launches: u16, +} + +/// A contact this old (ticks) is forgotten entirely (five minutes of silence). +pub(super) const CONTACT_TTL: u64 = 6000; +/// Younger than this a contact is `Confirmed`; younger than `PROBABLE_AGE`, `Probable`. +const CONFIRMED_AGE: u64 = 600; +const PROBABLE_AGE: u64 = 3000; + +impl Contact { + pub fn age(&self, now: u64) -> u64 { + now.saturating_sub(self.tick) + } + /// Whether the picture counts it as a live unit: observed, not watched dead, not aged out. + pub fn live(&self, now: u64) -> bool { + self.destroyed.is_none() && self.age(now) < CONTACT_TTL + } + pub fn confidence(&self, now: u64) -> Confidence { + match self.age(now) { + 0..=CONFIRMED_AGE => Confidence::Confirmed, + ..=PROBABLE_AGE => Confidence::Probable, + _ => Confidence::Doubtful, + } + } + /// Whether the enemy believes this unit sees on its own radar (#127). A kind without a + /// sensor never does. A kind with one does while an emission fix of this contact is + /// alive; the only contrary evidence the channels give is a launch seen with the unit's + /// emissions never fixed — a launcher fighting on the network (its radar is off or it + /// has none in its launcher-only mode). + pub fn believes_own_sensor(&self, now: u64) -> bool { + let spec = self.kind.spec(); + if spec.sensor <= 0.0 { + return false; + } + match self.emission { + Some(t) => now.saturating_sub(t) < CONTACT_TTL, + None => !(self.seen == Seen::Launch && self.network_launcher_kind()), + } + } + /// The kinds that can fire without their own radar in some configuration: an observation + /// of a launch without any emission fix reads as that configuration. + fn network_launcher_kind(&self) -> bool { + matches!(self.kind, SamKind::S400 | SamKind::S500) + } +} + +/// The radar reach the enemy's planners price by: the type's published figure, undamaged +/// and role-default. What a unit suffers or is configured to while unobserved is not in it. +pub(super) fn published_reach(kind: SamKind, drone: DroneKind, height: f64) -> f64 { + let spec = kind.spec(); + if spec.sensor <= 0.0 { + return 0.0; + } + let radar = spec.sensor * drone.rcs().powf(0.25); + let horizon = sight::HORIZON * (spec.mast.sqrt() + height.max(0.0).sqrt()); + radar.min(horizon).max(sight::NEAR) +} #[derive(Default)] pub(super) struct Intel { /// Shot down and got through, by approach sector, over every raid. pub kills: [u32; SECTORS], pub hits: [u32; SECTORS], - /// Units that gave themselves away: (id, where, when) — launches, jamming. - pub revealed: Vec<(u32, DVec3, u64)>, + /// The last observation of each unit the enemy knows of, by id. One entry per unit: + /// a newer observation replaces the older, so no log ever needs rescanning (#122). + contacts: BTreeMap, +} + +impl Intel { + /// The picture as a save writes it, and back: the contacts (one per unit) and the sector scores. + pub(super) fn contacts(&self) -> &BTreeMap { + &self.contacts + } + pub(super) fn sectors(&self) -> ([u32; SECTORS], [u32; SECTORS]) { + (self.kills, self.hits) + } + pub(super) fn restore(contacts: Vec, kills: [u32; SECTORS], hits: [u32; SECTORS]) -> Self { + Self { kills, hits, contacts: contacts.into_iter().map(|c| (c.id, c)).collect() } + } + + /// The one writer of contacts: what was seen of `s`, at its place, this moment. + pub(super) fn observe(&mut self, s: &Sam, now: u64, seen: Seen) { + self.note(s.id, s.kind, s.pos, s.alive(), now, seen); + } + + pub(super) fn note(&mut self, id: u32, kind: SamKind, at: DVec3, alive: bool, now: u64, seen: Seen) { + let c = self.contacts.entry(id).or_insert(Contact { id, kind, at, tick: now, seen, emission: None, destroyed: None, launches: 0 }); + // The newest observation wins; a fresh look at a live unit clears an old wreck call. + c.kind = kind; + c.at = at; + c.tick = now; + c.seen = seen; + if seen == Seen::Emission { + c.emission = Some(now); + } + if seen == Seen::Launch { + c.launches = c.launches.saturating_add(1); + } + if alive { + c.destroyed = None; + } + } + + /// Battle damage assessment: the enemy's own munition went down by a unit it knows of + /// and its last moments showed the unit's fate (truth read at this instant only). + pub(super) fn outcome(&mut self, id: u32, alive: bool, at: DVec3, now: u64) { + let Some(c) = self.contacts.get_mut(&id) else { return }; + if alive { + c.at = at; + c.tick = now; + c.destroyed = None; + } else { + c.destroyed = Some(now); + } + } + + /// Drops the contacts no observation has refreshed for `CONTACT_TTL`: silence is forgetting. + pub(super) fn expire(&mut self, now: u64) { + self.contacts.retain(|_, c| c.age(now) < CONTACT_TTL); + } } impl DroneKind { @@ -94,7 +502,7 @@ impl Sim { } // A transmitter on the air: the enemy finds it. if now % 20 == 0 { - self.intel.revealed.push((s.id, s.pos, now)); + self.intel.observe(s, now, Seen::Jamming); } } } @@ -134,94 +542,341 @@ impl Sim { (pick as f64 + 0.5 + self.rng.s() * 0.4) / SECTORS as f64 * TAU } - /// Units the enemy knows of and would go for, newest first: (id, where). - pub(super) fn known_units(&self) -> Vec<(u32, DVec3)> { - let mut out: Vec<(u32, DVec3)> = Vec::new(); - for &(id, at, _) in self.intel.revealed.iter().rev() { - if !out.iter().any(|o| o.0 == id) && self.sams.iter().any(|s| s.id == id && s.alive()) { - out.push((id, at)); - } - } + /// The enemy's live picture of the defences at this moment: the contacts a decision may + /// rest on, newest first (ties by id, newest id first — the order the old revelation log + /// produced, kept so the planner's picks stay as they were; deterministic either way). + /// What a unit does while unobserved changes nothing here; only a new observation can (#127). + pub(super) fn picture_of_defences(&self) -> Vec { + let now = self.tick; + let mut out: Vec = self.intel.contacts.values().copied().filter(|c| c.live(now)).collect(); + out.sort_by(|a, b| b.tick.cmp(&a.tick).then(b.id.cmp(&a.id))); out } - /// Whether the enemy has this unit in its intelligence: a radar emits, so it is known; the rest only once - /// they gave themselves away (a launch, jamming) and are in `known`. - pub(super) fn enemy_knows(s: &Sam, known: &[(u32, DVec3)]) -> bool { - s.alive() && (s.kind == SamKind::Radar || known.iter().any(|k| k.0 == s.id)) + /// Whether the picture holds a live contact for this unit: what the old revelation log + /// answered at a scan, now one map lookup. + pub(super) fn known_live(&self, id: u32) -> bool { + self.intel.contacts.get(&id).is_some_and(|c| c.live(self.tick)) } /// During a smart raid: its drones not yet launched go for what just gave itself away (a jamming station - /// first, then systems that fired), and drones whose system target is gone pick another. + /// first, then systems that fired), and drones whose system target is gone pick another. Routes for the + /// new aims are worked out over the following ticks, as the planner's share allows (#127). pub(super) fn adapt(&mut self) { - if self.tick % 40 != 0 { + if self.tick % 40 == 0 { + self.adapt_targets(); + } + self.replan_retargets(); + } + + /// The adaptive tick: chooses the new aims and leaves a route job for each drone that needs one. The + /// old route stays until a new one is ready: a drone is never left with no way round the defences. + /// + /// Every aim carries its [`Intent`] and the [`Aim`] bookkeeping that throttles changes: assignments + /// are spread over adaptive ticks and contacts, a fresh disclosure may replace an aim only after + /// `SWITCH_HOLD` and `SWITCH_CAP`, and only when it is worth a new route. A replacement of an aim + /// the picture killed is a distinct, lower-priority decision. + fn adapt_targets(&mut self) { + self.intel.expire(self.tick); + let picture = Arc::new(self.picture_of_defences()); + if picture.is_empty() { return; } - self.intel.revealed.retain(|r| self.tick - r.2 < 6000); - let known = self.known_units(); - let ew: Vec<(u32, DVec3)> = - known.iter().copied().filter(|(id, _)| self.sams.iter().any(|s| s.id == *id && s.kind == SamKind::Ew)).collect(); - if known.is_empty() { - return; - } - let pick = |i: usize, list: &[(u32, DVec3)]| list[i % list.len()].1; - // The drones sent at a new target: (waiting in the queue, index), to get a new route. - let mut retargeted: Vec<(bool, usize)> = Vec::new(); + // Every job of this adaptive tick plans over the same relief version, even if another smart + // plan finishes before the job gets its budget (#127). + let relief = self.known_relief.clone(); + let now = self.tick; + // Job ids are taken from the simulation's counter: a job keeps its id when it is replaced in + // place, and the save writes it back (#127). + let mut job_next = self.job_next; + // The drones sent at a new target: a route job each, to be worked out as the planner's budget allows. + let mut jobs: Vec = Vec::new(); let mut n = 0usize; - for (qi, (_, d)) in self.queue.iter_mut().enumerate().filter(|(_, (_, d))| d.smart) { + let mut assigned = 0usize; + let mut per_contact: HashMap = HashMap::new(); + for (_, d) in self.queue.iter_mut().filter(|(_, d)| d.smart) { let hunter = matches!(d.kind, DroneKind::Lancet | DroneKind::Kh22 | DroneKind::Zircon); - if !hunter && !(d.kind == DroneKind::Shahed && n % 4 == 0) { - n += 1; - continue; - } - // Only those not yet sent at a unit. - if self.sams.iter().any(|s| s.pos.distance(d.target - d.err) < 6.0) { - continue; - } - let to = if !ew.is_empty() { pick(n, &ew) } else { pick(n, &known) }; - d.target = to + d.err; - d.route.clear(); - retargeted.push((true, qi)); + let eligible = hunter || (d.kind == DroneKind::Shahed && n % 4 == 0); n += 1; - } - // In flight: a target system destroyed — the next known one, or keep going. - for (di, d) in self.drones.iter_mut().enumerate().filter(|(_, d)| d.alive() && d.smart) { - let aimed_at_wreck = self.sams.iter().any(|s| !s.alive() && s.pos.distance(d.target - d.err) < 6.0); - if aimed_at_wreck { - let next = known.iter().min_by(|a, b| a.1.distance(d.pos).total_cmp(&b.1.distance(d.pos))); - if let Some((_, at)) = next { - d.target = *at + d.err; + if !eligible { + continue; + } + // What it is aiming at in the picture now, if anything (a unit merely moved is still + // aimed where it was seen: a contact is matched by its observed place). + if let Some(current) = Self::aimed_contact(&picture, d.target - d.err) { + // Already aiming at a unit: a fresh disclosure may be worth a switch, under the throttle. + let Some(c) = Self::switch_candidate(&picture, current, d, now) else { continue }; + if per_contact.get(&c.id).copied().unwrap_or(0) >= ASSIGN_PER_CONTACT { + continue; + } + *per_contact.entry(c.id).or_insert(0) += 1; + assigned += 1; + d.target = c.at + d.err; + d.intent = Intent::React; + d.aim = d.aim.replaced(c.id, now, c.confidence(now)); + if Self::route_plannable(d) { + jobs.push(RouteJob::new(job_next, d.id, d.target, d.aim.unit, now, Intent::React, Arc::clone(&picture), relief.clone())); + job_next += 1; + } else { + d.route.clear(); + } + continue; + } + // Not sent at a unit yet: it may be given one, a bounded number a tick and per + // contact, so a mass retarget does not replace every route in one tick (#127). + if assigned >= ASSIGN_PER_TICK { + continue; + } + let Some(c) = Self::fresh_target(&picture, &per_contact, d.kind, n) else { continue }; + *per_contact.entry(c.id).or_insert(0) += 1; + assigned += 1; + d.target = c.at + d.err; + d.intent = Intent::React; + // A first assignment is not a switch: the aim's clock starts here, the count is kept. + d.aim = Aim { unit: c.id, since: now, switches: d.aim.switches, confidence: Some(c.confidence(now)) }; + if Self::route_plannable(d) { + jobs.push(RouteJob::new(job_next, d.id, d.target, d.aim.unit, now, Intent::React, Arc::clone(&picture), relief.clone())); + job_next += 1; + } else { + // Nothing will be replanned for it: its old route leads to the old aim, so it goes straight. + d.route.clear(); + } + } + // In flight: the aim is kept only while the picture says it is live. A unit watched dying, + // or forgotten after its contact aged out mid-flight, is replaced by the nearest live contact + // it can still turn onto — or, past the switch cap, the drone keeps its course. A unit merely + // moved is still aimed where seen. + for d in self.drones.iter_mut().filter(|d| d.alive() && d.smart) { + let aim = d.target - d.err; + let aim_gone = self + .intel + .contacts + .values() + .any(|c| c.at.distance(aim) < 6.0 && !c.live(self.tick)); + if !aim_gone { + continue; + } + let plannable = Self::route_plannable(d); + // A replacement is not a disclosure reaction: the aim is dead, so the hold does not apply, + // but the cap and the tick's assignment budget do — a drone whose aim keeps dying stops + // chasing the picture, and a tick that killed many aims does not replace them all at once. + if d.aim.switches >= SWITCH_CAP || assigned >= ASSIGN_PER_TICK { + continue; + } + let next = picture + .iter() + .filter(|c| !plannable || Self::can_turn_to(d, c.at)) + .filter(|c| per_contact.get(&c.id).copied().unwrap_or(0) < ASSIGN_PER_CONTACT) + .min_by(|a, b| a.at.distance(d.pos).total_cmp(&b.at.distance(d.pos))); + if let Some(c) = next { + *per_contact.entry(c.id).or_insert(0) += 1; + assigned += 1; + d.target = c.at + d.err; + d.intent = Intent::Replace; + d.aim = d.aim.replaced(c.id, now, c.confidence(now)); + if plannable { + jobs.push(RouteJob::new(job_next, d.id, d.target, d.aim.unit, now, Intent::Replace, Arc::clone(&picture), relief.clone())); + job_next += 1; + } else { d.route.clear(); - retargeted.push((false, di)); } } } - self.replan_routes(&retargeted); + self.job_next = job_next; + for job in jobs { + self.queue_route(job); + } } - /// Routes round the known defences for drones that were just sent at a new target, from where they are - /// (or wait). One grid and one set of prices cover them all, and the work is the planner's share for a - /// tick: those left over fly straight, as they did before there was a replan. A Lancet's strike on a - /// unit and the ballistic missiles go straight on purpose, as in the raid's own plan. - fn replan_routes(&mut self, jobs: &[(bool, usize)]) { - let drone = |s: &Sim, (queued, i): (bool, usize)| if queued { s.queue[i].1.clone() } else { s.drones[i].clone() }; - let jobs: Vec<(bool, usize)> = jobs + /// The contact the drone is aimed at now: the live one whose observed place is its aim. + fn aimed_contact(picture: &[Contact], aim: DVec3) -> Option { + picture.iter().copied().find(|c| c.at.distance(aim) < 6.0) + } + + /// The contact a drone not yet sent at a unit should be given: a station that gave itself away + /// (a jammer, a radar that emitted, a system that fired) before anything else, then the rest of + /// the picture, round-robin so a mass retarget spreads over what is known (#127). + fn fresh_target(picture: &[Contact], per_contact: &HashMap, kind: DroneKind, n: usize) -> Option { + let free = |c: &&Contact| per_contact.get(&c.id).copied().unwrap_or(0) < ASSIGN_PER_CONTACT; + // A station transmitting is the loudest disclosure: the raid turns on it first, as it always did. + let jammers: Vec<&Contact> = picture.iter().filter(|c| c.kind == SamKind::Ew).filter(free).collect(); + if let Some(c) = jammers.get(n % jammers.len().max(1)) { + return Some(**c); + } + // The hunters also take anything else that showed itself; the rest fall back to the picture. + let disclosed = |c: &&Contact| matches!(c.seen, Seen::Jamming | Seen::Emission | Seen::Launch); + let hunters = matches!(kind, DroneKind::Lancet | DroneKind::Kh22 | DroneKind::Zircon); + if hunters { + let shown: Vec<&Contact> = picture.iter().filter(disclosed).filter(free).collect(); + if let Some(c) = shown.get(n % shown.len().max(1)) { + return Some(**c); + } + } + let all: Vec<&Contact> = picture.iter().filter(free).collect(); + all.get(n % all.len().max(1)).map(|c| **c) + } + + /// Whether a fresh disclosure is worth replacing this drone's aim: a contact observed after the + /// aim was chosen, trusted (not `Doubtful`), clearly better placed or better believed, and the + /// throttle (hold time, switch cap) allows it (#127). A route change costs the planner a new + /// route, so a marginal candidate is not taken. + fn switch_candidate(picture: &[Contact], current: Contact, d: &Drone, now: u64) -> Option { + if d.aim.switches >= SWITCH_CAP || now.saturating_sub(d.aim.since) < SWITCH_HOLD { + return None; + } + let held = d.aim.confidence; + picture .iter() - .copied() - .filter(|&j| { - let d = drone(self, j); - let spec = d.kind.spec(); - d.smart && spec.ballistic.is_none() && !spec.decoy && d.kind != DroneKind::Lancet + .filter(|c| c.id != current.id) + // Only something that has shown itself since this aim was chosen is a reason to switch. + .filter(|c| c.tick > d.aim.since) + .filter(|c| c.confidence(now) >= Confidence::Probable) + .filter(|c| match held { + Some(h) => c.confidence(now) > h || c.at.distance(d.pos) + SWITCH_GAIN < current.at.distance(d.pos), + None => true, }) - .collect(); + .min_by(|a, b| a.at.distance(d.pos).total_cmp(&b.at.distance(d.pos))) + .copied() + } + + /// A kind whose route the replan works out: a Lancet's strike on a unit and the ballistic missiles go + /// straight on purpose, as in the raid's own plan. + fn route_plannable(d: &Drone) -> bool { + let spec = d.kind.spec(); + d.smart && spec.ballistic.is_none() && !spec.decoy && d.kind != DroneKind::Lancet + } + + /// Whether a drone can turn onto `at` from where it is. A slow missile with a close target behind + /// would only circle it: it finishes the approach it is on instead of turning to another unit (#127). + fn can_turn_to(d: &Drone, at: DVec3) -> bool { + let spec = d.kind.spec(); + let radius = (spec.speed / spec.turn.max(0.01)).max(24.0); + let to = horizontal(at - d.pos); + if to.length() >= radius * 2.0 { + return true; + } + let heading = horizontal(d.vel).normalize_or_zero(); + heading == DVec3::ZERO || heading.dot(to.normalize_or_zero()) > 0.0 + } + + /// Adds a job, replacing an older one for the same drone in place: a drone retargeted again before its + /// route was ready must not grow the queue or be planned for an aim it no longer has (#127). The queue + /// is bounded: past `JOBS_MAX` the lowest priority (then the oldest) job makes room, and a job that is + /// not worth more than what is queued is refused outright — the drone keeps its old route. + fn queue_route(&mut self, job: RouteJob) { + if let Some(i) = self.retargets.iter().position(|j| j.drone == job.drone) { + if self.retargets[i].version <= job.version { + self.retargets[i] = job; + } + return; + } + if self.retargets.len() >= JOBS_MAX { + let worst = self + .retargets + .iter() + .enumerate() + .min_by_key(|(i, j)| (j.priority(), *i)) + .map(|(i, _)| i); + match worst { + Some(i) if self.retargets[i].priority() < job.priority() => { + self.retargets.remove(i); + } + _ => return, + } + } + self.retargets.push_back(job); + } + + /// Drops the jobs a restored save can no longer stand behind (#127): a drone gone or dead, an aim + /// that has since changed, a unit the picture watched die or forgot, a duplicate for the same drone, + /// or a version older than `JOB_TTL` — the picture has moved on. Returns how many were cancelled. + pub(super) fn prune_air(&mut self) -> u32 { + let now = self.tick; + let mut seen: HashMap = HashMap::new(); + let mut cancelled = 0u32; + let mut kept: VecDeque = VecDeque::with_capacity(self.retargets.len()); + while let Some(job) = self.retargets.pop_front() { + let fresh = now.saturating_sub(job.version) <= JOB_TTL; + let newer = seen.get(&job.drone).is_none_or(|v| job.version >= *v); + let live = self.job_live(&job); + if fresh && newer && live { + seen.insert(job.drone, job.version); + kept.push_back(job); + } else { + cancelled += 1; + // A job dropped for staleness or as a duplicate leaves a route that still leads to the + // drone's own aim, so it is kept; one whose aim has changed leaves a route to nowhere, + // and that goes. + if let Some(d) = self.drone_of_mut(job.drone) && d.alive() && d.target != job.aim { + d.route.clear(); + } + } + } + self.retargets = kept; + cancelled + } + + /// The drone a job is for: waiting in the queue or already flying. + fn drone_of(&self, id: u32) -> Option<&Drone> { + self.queue.iter().find(|(_, d)| d.id == id).map(|(_, d)| d).or_else(|| self.drones.iter().find(|d| d.id == id)) + } + + fn drone_of_mut(&mut self, id: u32) -> Option<&mut Drone> { + if let Some(i) = self.queue.iter().position(|(_, d)| d.id == id) { + return Some(&mut self.queue[i].1); + } + self.drones.iter_mut().find(|d| d.id == id) + } + + /// Whether a job still says what its drone is doing. A dead drone or an aim changed again + /// cancels it; so does a unit the picture watched die or forgot: the enemy no longer plans + /// a route to a target it does not believe in. A unit destroyed unobserved cancels nothing — + /// the picture still believes in it (#127). + fn job_live(&self, job: &RouteJob) -> bool { + let Some(d) = self.drone_of(job.drone) else { return false }; + if !d.alive() || d.target != job.aim { + return false; + } + job.unit == 0 || self.known_live(job.unit) + } + + /// The land this job's knowledge version knew, sampled on `grid` (NaN outside it). It reads the job's + /// own snapshot: a later smart plan replacing `Sim::known_relief` must not change a pending job (#127). + fn retarget_relief(job: &RouteJob, grid: &raid::Grid) -> Option> { + job.relief.as_deref()?.sampled_on(grid) + } + + /// Works the retarget queue: one grid and one set of prices cover the jobs, and the work is the + /// planner's share for a tick. The queue is a bounded priority queue — the plan's own objectives first, + /// then SEAD, then reactions to disclosures, then replacements; FIFO within a priority. A job the budget + /// cannot reach stays queued for the next tick, and its drone keeps the old route (or, if it had none, + /// flies straight for a while — never for ever). + fn replan_retargets(&mut self) { + if self.retargets.is_empty() { + return; + } + let mut jobs: Vec = std::mem::take(&mut self.retargets).into_iter().filter(|job| { + if self.job_live(job) { + return true; + } + if let Some(d) = self.drone_of_mut(job.drone) && d.alive() { + // No new route will come from this job: the old route is a fallback, not a plan. + d.route.clear(); + } + false + }).collect(); if jobs.is_empty() { return; } + // Stable: jobs of one priority keep the order they were queued in. + jobs.sort_by_key(|j| std::cmp::Reverse(j.priority())); + self.retargets = jobs.into_iter().collect(); + let jobs: Vec = std::mem::take(&mut self.retargets).into(); // One grid over all their starts and goals, with room round them to go by. let (mut lo, mut hi) = (DVec3::splat(f64::INFINITY), DVec3::splat(f64::NEG_INFINITY)); - for &j in &jobs { - let d = drone(self, j); - for p in [d.pos, d.target] { + for job in &jobs { + let Some(d) = self.drone_of(job.drone) else { continue }; + for p in [d.pos, job.aim] { lo = lo.min(p); hi = hi.max(p); } @@ -230,31 +885,850 @@ impl Sim { let cell = raid::CELL.max(2.0 * span / raid::CELLS as f64); let centre = (lo + hi) / 2.0; let grid = raid::Grid { min: centre - DVec3::new(span, 0.0, span), cell, size: (2.0 * span / cell) as i32 + 1 }; - let mut prices: HashMap> = HashMap::new(); - let mut routes: HashMap<(DroneKind, (i32, i32), (i32, i32)), Vec> = HashMap::new(); + // Jobs may carry different knowledge versions (their own known units and relief); keep the + // sampled relief, prices and routes per version, so jobs of one adaptive tick share caches and + // jobs of another do not (#127). + let mut reliefs: HashMap, Option>> = HashMap::new(); + let mut prices: HashMap<(DroneKind, KnowledgeVersion), Vec> = HashMap::new(); + let mut routes: HashMap<(DroneKind, KnowledgeVersion, (i32, i32), (i32, i32)), Vec> = HashMap::new(); let mut work = 0usize; - for j in jobs { - let d = drone(self, j); + let mut kept: Vec = Vec::with_capacity(jobs.len()); + let mut i = 0usize; + while i < jobs.len() { if work >= raid::WORK_PER_TICK { + kept.extend(jobs[i..].iter().cloned()); break; } - let key = (d.kind, grid.index(d.pos), grid.index(d.target)); + let job = jobs[i].clone(); + if !self.job_live(&job) { + i += 1; + continue; + } + let Some(d) = self.drone_of(job.drone) else { + i += 1; + continue; + }; + let (pos, kind) = (d.pos, d.kind); + let version = KnowledgeVersion { + tick: job.version, + known: Arc::as_ptr(&job.known) as usize, + relief: job.relief.as_ref().map(|r| Arc::as_ptr(r) as usize), + }; + let land = reliefs.entry(version.relief).or_insert_with(|| Self::retarget_relief(&job, &grid)); + let land = land.as_deref(); + let key = (kind, version, grid.index(pos), grid.index(job.aim)); let route = match routes.get(&key) { Some(r) => r.clone(), None => { - if !prices.contains_key(&d.kind) { - let (p, cost) = self.price_grid(&grid, d.kind); + if !prices.contains_key(&(kind, version)) { + let (p, cost) = self.price_grid_known(&grid, kind, land, job.known.as_slice()); work += cost; - prices.insert(d.kind, p); + prices.insert((kind, version), p); } - let (r, cost) = raid::plan_route(&grid, &prices[&d.kind], d.pos, d.target); + // Land this job's version did not know is not free: every unknown cell costs more + // than the open ground, so a route keeps to what the plan mapped out (#127). + let unknown_land; + let heights = match land { + Some(h) => h, + None => { + unknown_land = vec![f32::NAN; (grid.size * grid.size) as usize]; + &unknown_land + } + }; + let relief = raid::Relief { + heights, + slope: (kind.climb_pitch().tan() * 0.5) as f32, + floor: (job.aim.y + kind.spec().clearance) as f32, + unknown: UNKNOWN_LAND, + }; + let (r, cost) = raid::plan_route_on(&grid, &prices[&(kind, version)], Some(&relief), pos, job.aim); work += cost; routes.insert(key, r.clone()); r } }; - let d = if j.0 { &mut self.queue[j.1].1 } else { &mut self.drones[j.1] }; - d.route = route; + if let Some(d) = self.drone_of_mut(job.drone) { + d.route = route; + } + i += 1; + } + self.retargets = kept.into_iter().collect(); + } + + /// The route jobs still waiting (#127; diagnostics). + pub fn retargets_len(&self) -> usize { + self.retargets.len() + } + + /// The drones with a route job still waiting (tests and diagnostics). + #[cfg(test)] + pub(super) fn retarget_ids(&self) -> Vec { + self.retargets.iter().map(|j| j.drone).collect() + } + + /// The relief a pending job carries (tests and diagnostics). + #[cfg(test)] + pub(super) fn pending_relief(&self, id: u32) -> Option<&KnownRelief> { + self.retargets.iter().find(|j| j.drone == id)?.relief.as_deref() + } +} + +/// One contact as the diagnostic reports it: the observation, its age and confidence, and the +/// threat the planner would price from it — the belief, not the truth (#127). +#[derive(Clone, Debug, PartialEq)] +pub struct ContactReport { + pub id: u32, + /// The type identified at the observation. + pub kind: SamKind, + /// Where it was last observed. + pub at: DVec3, + /// What the last observation showed: "launch", "emission" or "jamming". + pub seen: &'static str, + /// Ticks since the last observation. + pub age: u64, + pub confidence: Confidence, + /// Whether the picture counts it as a live unit (unexpired, not watched dead). + pub live: bool, + /// When its destruction was observed, if it was. + pub observed_destroyed: Option, + /// Launches of this unit the picture watched. The magazine behind them is never observed. + pub launches: u16, + /// The belief the threat model prices by: that its own radar works. + pub own_sensor: bool, + /// Whether a live radar contact stands within data-link range of its observed place. + pub donor_in_link: bool, + /// What the planner makes of it, in words. + pub threat: &'static str, + /// Drones aimed at this contact now (whatever the reason). + pub aimed_by: u32, + /// Route jobs still being worked out for this contact. + pub pending_jobs: u32, +} + +/// Why one drone is aimed where it is (#127): the reason, the unit, and the throttle's state. +#[derive(Clone, Debug, PartialEq)] +pub struct AimReport { + pub drone: u32, + /// The unit aimed at (0: a marked object). + pub unit: u32, + /// "plan", "sead", "react" or "replace". + pub intent: &'static str, + /// The tick the aim was chosen at. + pub since: u64, + /// How many times the aim has been changed since the plan chose it. + pub switches: u16, + /// The confidence of the contact the aim rests on, when it is a unit. + pub confidence: Option, +} + +/// A route job the planner has not finished (#127): what it is for, why it is still waiting, +/// and the knowledge version it will be planned over. +#[derive(Clone, Debug, PartialEq)] +pub struct PendingReport { + pub job: u32, + pub drone: u32, + pub unit: u32, + pub at: DVec3, + /// The adaptive tick whose picture chose the aim. + pub version: u64, + /// Higher goes first (see `Intent`). + pub priority: u8, + pub intent: &'static str, + /// Whether the land of this job's version is known at all. + pub relief_known: bool, + /// Why it is still queued, in words. + pub reason: &'static str, +} + +/// The enemy's whole picture at one tick: what it knows and what it assumes, structured for +/// diagnostics (#127). No truth anywhere in it — a unit the picture got wrong is reported wrong. +#[derive(Clone, Debug, PartialEq)] +pub struct EnemyPictureReport { + pub now: u64, + pub contacts: Vec, + /// Every smart drone's aim and why, for the plan a player's raid faces. + pub aims: Vec, + /// The route jobs still waiting. + pub pending: Vec, + /// The planner's work a tick, for the pending queue's budget. + pub work_per_tick: usize, +} + +impl Sim { + /// The structured picture: observation, confidence and priced threat per contact, the aims + /// every smart drone carries and why, and the pending route jobs with their reason (#127). + pub fn enemy_picture(&self) -> EnemyPictureReport { + let now = self.tick; + let live: Vec = self.picture_of_defences(); + let aimed_at = |unit: u32| -> u32 { + self.queue + .iter() + .map(|(_, d)| d) + .chain(self.drones.iter()) + .filter(|d| d.alive() && d.smart && d.aim.unit == unit && unit != 0) + .count() as u32 + }; + let pending_at = |unit: u32| -> u32 { self.retargets.iter().filter(|j| j.unit == unit && unit != 0).count() as u32 }; + let contacts = self + .intel + .contacts + .values() + .map(|c| { + let donor = live.iter().any(|d| d.kind == SamKind::Radar && d.id != c.id && d.at.distance(c.at) <= sight::LINK); + ContactReport { + id: c.id, + kind: c.kind, + at: c.at, + seen: c.seen.name(), + age: c.age(now), + confidence: c.confidence(now), + live: c.live(now), + observed_destroyed: c.destroyed, + launches: c.launches, + own_sensor: c.believes_own_sensor(now), + donor_in_link: donor, + threat: Self::threat_text(c, now, donor), + aimed_by: aimed_at(c.id), + pending_jobs: pending_at(c.id), + } + }) + .collect(); + let aims = self + .queue + .iter() + .map(|(_, d)| d) + .chain(self.drones.iter()) + .filter(|d| d.alive() && d.smart) + .map(|d| AimReport { + drone: d.id, + unit: d.aim.unit, + intent: d.intent.name(), + since: d.aim.since, + switches: d.aim.switches, + confidence: d.aim.confidence, + }) + .collect(); + let pending = self + .retargets + .iter() + .map(|j| PendingReport { + job: j.id, + drone: j.drone, + unit: j.unit, + at: j.aim, + version: j.version, + priority: j.priority(), + intent: j.intent.name(), + relief_known: j.relief.is_some(), + reason: if now.saturating_sub(j.version) > JOB_TTL { + "stale: the picture that chose the aim has moved on" + } else if j.relief.is_none() { + "waiting for the planner's share; the land is unknown and costs accordingly" + } else { + "waiting for the planner's share of this tick" + }, + }) + .collect(); + EnemyPictureReport { now, contacts, aims, pending, work_per_tick: raid::WORK_PER_TICK } + } + + /// What the planner makes of a contact, in words for the diagnostic (#127). + fn threat_text(c: &Contact, now: u64, donor: bool) -> &'static str { + if !c.live(now) { + return "not priced: forgotten with age or watched dead"; + } + let spec = c.kind.spec(); + if spec.missile.is_none() && spec.gun.is_none() { + return "not armed: a jamming or support station, priced only for satellite-nav drones"; + } + match (c.believes_own_sensor(now), donor) { + (true, true) => "missiles on its own sensor, reaching further on the linked radar; guns on its own sensor", + (true, false) => "missiles and guns on its own sensor", + (false, true) => "no own sensor believed: it fires on the linked radar's tracks", + (false, false) => "believed blind: no own sensor and no linked radar in reach", } } } + +#[cfg(test)] +mod picture_tests { + use super::*; + use super::super::{battery::SamMode, raid::Grid}; + + fn grid() -> Grid { + Grid { min: DVec3::new(-480.0, 0.0, -480.0), cell: 16.0, size: 60 } + } + + /// A small force the picture knows about: an emitting patriot, a fixed radar, a jammer. + fn watched_force(sim: &mut Sim) -> Vec { + let mut ids = Vec::new(); + for (kind, at, seen) in [ + (SamKind::Patriot, DVec3::new(-120.0, 70.0, 0.0), Seen::Emission), + (SamKind::Radar, DVec3::new(0.0, 70.0, 0.0), Seen::Emission), + (SamKind::Ew, DVec3::new(120.0, 70.0, 0.0), Seen::Jamming), + ] { + let id = sim.id(); + let sam = Sam::new(id, kind, at, 0.0); + sim.intel.observe(&sam, 0, seen); + sim.sams.push(sam); + ids.push(id); + } + ids + } + + // #127: what a unit does while unobserved — moves, switches off, re-roles, re-modes, spends + // its magazine, takes damage, dies — changes neither the picture nor anything priced or + // decided from it. Only a new observation can. + #[test] + fn hidden_changes_do_not_move_the_picture_or_its_decisions() { + let mut base = Sim::new(17); + let ids = watched_force(&mut base); + let mut changed = Sim::new(17); + let _ = watched_force(&mut changed); + base.tick = 100; + changed.tick = 100; + let grid = grid(); + let snapshot = |sim: &Sim| { + let (prices, cost) = sim.price_grid(&grid, DroneKind::Kh101); + (sim.picture_of_defences(), prices, cost, sim.enemy_picture()) + }; + let before = snapshot(&base); + // Every hidden change at once: moves, silence, roles emptied, launcher-only mode, + // an empty magazine, damage, and one unit destroyed where nothing could watch. + changed.sams[0].pos = DVec3::new(-500.0, 70.0, 400.0); + changed.sams[1].radar_on = false; + changed.sams[1].radar_role = super::super::battery::RadarRole::Search; + changed.sams[2].roles = 0; + changed.sams[0].set_mode(SamMode::LauncherOnly); + changed.sams[0].missiles = 0; + changed.sams[0].health = 0.3; + changed.sams[1].health = 0.0; + // ...and an EW station's heat, transmitter lock and switch are its own energy, not a disclosure. + changed.sams[2].heat = 1.0; + changed.sams[2].ew_lock = true; + changed.sams[2].ew_on = false; + let after = snapshot(&changed); + assert_eq!(before, after, "a hidden change moved the enemy's picture"); + // The adaptive tick decides the same aims on both. + for (sim, id) in [(&mut base, ids[0]), (&mut changed, ids[0])] { + let _ = id; + let from = DVec3::new(0.0, 120.0, -900.0); + let mut d = sim.make_drone(DroneKind::Kh22, from, DVec3::Z, DVec3::new(0.0, 70.0, 900.0)); + d.smart = true; + sim.queue.push((0, d)); + sim.adapt(); + } + let aims = |sim: &Sim| sim.queue.iter().map(|(_, d)| d.target).collect::>(); + assert_eq!(aims(&base), aims(&changed), "the same picture chose different aims"); + assert_eq!(base.retarget_ids(), changed.retarget_ids()); + // The structured intent is part of the decision: the same picture chooses the same reason, + // the same unit and the same throttle state (#127). + let intent = |sim: &Sim| sim.queue.iter().map(|(_, d)| (d.intent, d.aim.unit, d.aim.since, d.aim.switches, d.aim.confidence)).collect::>(); + assert_eq!(intent(&base), intent(&changed), "a hidden change moved an intent"); + let jobs = |sim: &Sim| sim.retargets.iter().map(|j| (j.id, j.drone, j.unit, j.version, j.priority(), j.intent)).collect::>(); + assert_eq!(jobs(&base), jobs(&changed), "a hidden change moved a route job"); + } + + // #127: the report explains each contact's part in the plan — how many drones aim at it, how many + // routes are still being worked out for it, and why a pending job waits — in plain English, since + // the core carries no player-facing text. + #[test] + fn the_report_explains_aims_and_pending_jobs_in_english() { + let mut sim = Sim::new(97); + watched_force(&mut sim); + for i in 0..6 { + let mut d = sim.make_drone(DroneKind::Kh22, DVec3::new(-900.0, 120.0, -400.0 + i as f64 * 80.0), DVec3::X, DVec3::new(900.0, 70.0, 0.0)); + d.smart = true; + sim.queue.push((0, d)); + } + sim.tick = 40; + sim.adapt(); + let report = sim.enemy_picture(); + assert!(!report.aims.is_empty(), "the report has no aims"); + assert!(report.aims.iter().all(|a| matches!(a.intent, "react" | "plan" | "sead" | "replace"))); + let aimed = report.contacts.iter().find(|c| c.aimed_by > 0).expect("no contact is aimed at"); + assert!(aimed.pending_jobs > 0, "the aim of a pending route is not reported"); + assert_eq!(report.pending.len(), report.contacts.iter().map(|c| c.pending_jobs).sum::() as usize); + let p = &report.pending[0]; + assert!(!p.reason.is_empty() && p.priority <= 3); + assert_eq!(report.work_per_tick, super::super::raid::WORK_PER_TICK); + // No player-facing Russian in the core's diagnostics: every reported word is ASCII. + assert!(aimed.threat.is_ascii() && p.reason.is_ascii() && p.intent.is_ascii()); + assert!(report.contacts.iter().all(|c| c.seen.is_ascii() && c.threat.is_ascii())); + } + + // #127: a new disclosure is what updates the picture. The moved patriot is re-fixed at its + // new place: the picture, the prices and the adaptive aims follow the observation. + #[test] + fn a_new_observation_is_what_updates_the_picture() { + let mut sim = Sim::new(19); + let ids = watched_force(&mut sim); + sim.tick = 100; + let grid = grid(); + let (before, _) = sim.price_grid(&grid, DroneKind::Kh101); + // A hidden move changes nothing... + sim.sams[0].pos = DVec3::new(-120.0, 70.0, 360.0); + let (mid, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert_eq!(before, mid, "a hidden move changed the prices"); + // ...until something discloses the unit again. + sim.intel.observe(&sim.sams[0], 200, Seen::Emission); + let picture = sim.picture_of_defences(); + let c = picture.iter().find(|c| c.id == ids[0]).unwrap(); + assert_eq!(c.at, DVec3::new(-120.0, 70.0, 360.0), "the new observation is not the one kept"); + assert_eq!(c.tick, 200); + let (after, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert_ne!(before, after, "the prices never noticed the re-observation"); + } + + // #127: a launcher-only S400 seen firing without its emissions ever fixed fights on the + // network in the picture: blind without a donor, dangerous out to the donor's reach with one. + #[test] + fn launcher_only_is_blind_in_the_picture_without_a_donor() { + let mut sim = Sim::new(5); + let id = sim.id(); + let mut sam = Sam::new(id, SamKind::S400, DVec3::new(0.0, 70.0, 0.0), 0.0); + assert!(sam.set_mode(SamMode::LauncherOnly)); + sim.intel.observe(&sam, 0, Seen::Launch); + sim.sams.push(sam); + let grid = grid(); + let (prices, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert!(prices.iter().all(|p| *p == 1.0), "a launcher believed blind priced a threat"); + let report = sim.enemy_picture(); + let c = report.contacts.iter().find(|c| c.id == id).unwrap(); + assert!(!c.own_sensor && !c.donor_in_link, "the report disagrees with the blindness"); + + // A radar contact within data-link range of the observed place feeds it. The radar's + // actual role is not observable: the picture assumes any live radar contact feeds the + // network, and the report says so. + let id = sim.id(); + let mut radar = Sam::new(id, SamKind::Radar, DVec3::new(300.0, 70.0, 0.0), 0.0); + radar.set_radar_role(super::super::battery::RadarRole::Search); // truth: cannot authorize + sim.intel.observe(&radar, 0, Seen::Emission); + sim.sams.push(radar); + let (prices, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert!(prices.iter().any(|p| *p > 1.0), "a launcher with a linked radar priced no threat"); + let report = sim.enemy_picture(); + let c = report.contacts.iter().find(|c| c.kind == SamKind::S400).unwrap(); + assert!(c.donor_in_link && !c.own_sensor, "the report does not name the network"); + + // The link breaking unobserved changes nothing in the picture: the radar's death is a + // secret until something watches it. Watched, the launcher goes blind again. + sim.sams.iter_mut().find(|s| s.kind == SamKind::Radar).unwrap().health = 0.0; + let (prices, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert!(prices.iter().any(|p| *p > 1.0), "an unobserved link break changed the picture"); + let radar_id = sim.sams.iter().find(|s| s.kind == SamKind::Radar).unwrap().id; + sim.intel.outcome(radar_id, false, DVec3::new(300.0, 70.0, 0.0), 50); + let (prices, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert!(prices.iter().all(|p| *p == 1.0), "the watched donor died and the launcher stayed dangerous"); + } + + // #127: destruction is knowledge only when watched. A unit dying unseen keeps living in the + // picture and in the prices; the enemy's own munition watching the kill takes it out, and a + // fresh look at a live unit (a repair) brings it back. + #[test] + fn destruction_moves_the_picture_only_when_watched() { + let mut sim = Sim::new(23); + let ids = watched_force(&mut sim); + sim.tick = 100; + let grid = grid(); + // Killed where nothing of the enemy's is watching: the picture never learns. + sim.sams[0].health = 0.0; + assert!(sim.picture_of_defences().iter().any(|c| c.id == ids[0]), "an unobserved kill removed the contact"); + let (prices, _) = sim.price_grid(&grid, DroneKind::Kh101); + assert!(prices.iter().any(|p| *p > 1.0), "an unobserved kill changed the prices"); + // The wreck watched by a strike: out of the picture and the prices... + sim.intel.outcome(ids[0], false, sim.sams[0].pos, 150); + assert!(!sim.known_live(ids[0])); + assert!(sim.picture_of_defences().iter().all(|c| c.id != ids[0])); + let (prices, _) = sim.price_grid(&grid, DroneKind::Kh101); + // The same force without the patriot: the watched death prices exactly as if it had + // never been seen (the jammer's cells stay: the jammer lives in the picture). + let mut without = Sim::new(23); + let kept = watched_force(&mut without); + without.intel.contacts.remove(&kept[0]); + let (expect, _) = without.price_grid(&grid, DroneKind::Kh101); + assert_eq!(prices, expect, "the watched-dead unit was still priced"); + // ...and a fresh look at the rebuilt unit restores it. + sim.sams[0].health = 1.0; + sim.intel.observe(&sim.sams[0], 200, Seen::Emission); + assert!(sim.known_live(ids[0]) && sim.picture_of_defences().iter().find(|c| c.id == ids[0]).unwrap().tick == 200); + } + + // #127: confidence is age alone. The bands gate what SEAD trusts (checked in raid.rs); + // here the picture and the report carry it. + #[test] + fn confidence_is_a_function_of_age_alone() { + let mut sim = Sim::new(29); + let ids = watched_force(&mut sim); + for (tick, want) in [(0, Confidence::Confirmed), (600, Confidence::Confirmed), (601, Confidence::Probable), (3000, Confidence::Probable), (3001, Confidence::Doubtful)] { + sim.tick = tick; + let c = sim.picture_of_defences().iter().find(|c| c.id == ids[0]).unwrap().confidence(tick); + assert_eq!(c, want, "tick {tick}"); + let report = sim.enemy_picture(); + let c = report.contacts.iter().find(|c| c.id == ids[0]).unwrap(); + assert_eq!(c.confidence, want); + assert_eq!(c.age, tick); + } + sim.tick = ew::CONTACT_TTL - 1; + assert!(sim.known_live(ids[0]), "a contact dies before its TTL"); + sim.tick = ew::CONTACT_TTL; + assert!(!sim.known_live(ids[0]), "a contact outlived its TTL"); + sim.tick = ew::CONTACT_TTL + 2; + sim.adapt(); + assert!(sim.picture_of_defences().is_empty(), "expired contacts were not pruned"); + } + + // #127: the structured report separates what was seen from what is believed, in English. + #[test] + fn enemy_picture_report_describes_observation_and_belief() { + let mut sim = Sim::new(31); + let ids = watched_force(&mut sim); + let id = sim.id(); + let mut launcher = Sam::new(id, SamKind::S400, DVec3::new(0.0, 70.0, -3000.0), 0.0); + launcher.set_mode(SamMode::LauncherOnly); + sim.intel.observe(&launcher, 0, Seen::Launch); + sim.sams.push(launcher); + sim.tick = 40; + let report = sim.enemy_picture(); + assert_eq!(report.contacts.len(), 4); + let by_id = |report: &EnemyPictureReport, id: u32| report.contacts.iter().find(|c| c.id == id).unwrap().clone(); + let ew_c = by_id(&report, ids[2]); + assert_eq!(ew_c.seen, "jamming"); + assert_eq!(ew_c.confidence, Confidence::Confirmed); + assert!(ew_c.live && ew_c.threat.contains("jamming"), "{}", ew_c.threat); + let sam_c = by_id(&report, id); + assert_eq!(sam_c.seen, "launch"); + assert!(!sam_c.own_sensor, "a launch without emission fixes read as an own sensor"); + assert!(sam_c.threat.contains("blind"), "{}", sam_c.threat); + let pat = by_id(&report, ids[0]); + assert!(pat.own_sensor && pat.seen == "emission"); + assert!(pat.donor_in_link, "the radar contact should read as the patriot's network"); + } +} + +#[cfg(test)] +mod outcome_tests { + use super::*; + + // #127: the battle-damage channel end to end. A drone the picture knows the target of flies + // to a unit that cannot shoot back (a radar); its impact watches the unit die, and the + // picture — not the truth — is what learns it. An identical loss nobody watches stays a + // live contact. + #[test] + fn an_impact_watches_the_kill_and_updates_only_the_picture() { + let mut sim = Sim::new(37); + let at = DVec3::new(0.0, 70.0, 0.0); + let id = sim.id(); + let sam = Sam::new(id, SamKind::Radar, at, 0.0); + sim.intel.observe(&sam, 0, Seen::Launch); + sim.sams.push(sam); + // Low and level, so the impact sweeps the hull instead of diving into the ground early. + sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 71.0, -200.0), DVec3::Z, at); + let mut ground = Ground::default(); + let mut out = Out::default(); + let mut seen_dead = None; + for now in 1..1200 { + sim.tick = now; + sim.step(&mut ground, None, &mut out); + seen_dead = sim.intel.contacts.get(&id).and_then(|c| c.destroyed); + if seen_dead.is_some() && !sim.drones.iter().any(|d| d.alive()) { + break; + } + } + let contact = sim.intel.contacts.get(&id).expect("the contact is forgotten while fresh"); + assert!(!sim.sams[0].alive(), "the setup: the impact must kill the unit"); + assert_eq!(seen_dead, contact.destroyed); + assert!(contact.destroyed.is_some(), "the impact watched the kill and the picture learned nothing"); + assert!(!sim.known_live(id)); + assert_eq!(sim.picture_of_defences(), Vec::new(), "a watched-dead unit stayed a target"); + + // The same loss, unwatched: the drone dies far from the unit and the contact lives on. + let mut other = Sim::new(37); + let id2 = other.id(); + let sam = Sam::new(id2, SamKind::Radar, at, 0.0); + other.intel.observe(&sam, 0, Seen::Launch); + other.sams.push(sam); + other.add_drone(DroneKind::Shahed, DVec3::new(0.0, 80.0, -600.0), DVec3::Z, at); + // The drone is shot down far short of the aim: nothing watches the unit's fate. + other.drones[0].pos = DVec3::new(0.0, 80.0, -400.0); + let _ = other.shoot_down(0, 0, None, &Ground::default(), &mut Out::default()); + let mut ground = Ground::default(); + let mut out = Out::default(); + for now in 1..1200 { + other.tick = now; + other.step(&mut ground, None, &mut out); + if !other.drones.iter().any(|d| d.alive()) { + break; + } + } + assert!(other.intel.contacts.get(&id2).unwrap().destroyed.is_none(), "an unwatched fate changed the picture"); + assert!(other.known_live(id2)); + } +} +#[cfg(test)] +mod intent_tests { + use super::*; + use super::super::raid::Grid; + use std::collections::HashMap; + + /// A unit the picture knows of, placed and observed once. + fn watch(sim: &mut Sim, kind: SamKind, at: DVec3, tick: u64, seen: Seen) -> u32 { + let id = sim.id(); + let sam = Sam::new(id, kind, at, 0.0); + sim.intel.observe(&sam, tick, seen); + sim.sams.push(sam); + id + } + + fn smart_drone(sim: &mut Sim, kind: DroneKind, from: DVec3, aim: DVec3) -> u32 { + let mut d = sim.make_drone(kind, from, DVec3::X, aim); + d.smart = true; + let id = d.id; + sim.queue.push((0, d)); + id + } + + fn goal_unit(sim: &Sim, id: u32) -> u32 { + sim.drones.iter().chain(sim.queue.iter().map(|(_, d)| d)).find(|d| d.id == id).unwrap().aim.unit + } + + // #127: the plan records *why* a drone is aimed where it is. A radar strike is planned SEAD; + // everything else the plan chose is its own objective, never a random re-pick. + #[test] + fn the_plan_records_sead_separately_from_its_objective() { + let mut sim = Sim::new(41); + let radar = watch(&mut sim, SamKind::Radar, DVec3::new(0.0, 70.0, 0.0), 0, Seen::Emission); + let order = raid::RaidOrder { + kinds: vec![(DroneKind::Kh22, 8), (DroneKind::Shahed, 4)], + approach: raid::Approach::Sector, + dist: 1200.0, + aim: raid::Aim::Smart, + }; + let objects = [DVec3::new(0.0, 70.0, 300.0)]; + let mut out = Out::default(); + sim.order_raid(&order, &objects, &mut out); + let intents = sim.planned_intents(); + assert_eq!(intents.len(), 12, "the plan did not hold every drone"); + let sead = intents.iter().filter(|i| i.1 == Intent::Sead).count(); + assert!(sead > 0, "no SEAD job was recorded for the observed radar"); + assert!(intents.iter().filter(|i| i.1 == Intent::Plan).count() > 0, "the plan's own objective was not recorded"); + assert!(intents.iter().all(|i| matches!(i.1, Intent::Sead | Intent::Plan))); + // Every SEAD job goes for the observed radar; the planner's own picks may also aim at it, + // but they are recorded as its objective, not as SEAD. + assert!(sim.planned_aims().iter().filter(|(_, i)| *i == Intent::Sead).all(|(unit, _)| *unit == radar)); + } + + // #127: a mass retarget is spread over adaptive ticks and contacts: one tick assigns a bounded + // number of new aims, and a single contact does not draw the whole force. + #[test] + fn assignments_are_throttled_per_tick_and_per_contact() { + let mut sim = Sim::new(43); + for k in 0..6 { + watch(&mut sim, SamKind::S400, DVec3::new(-600.0 + k as f64 * 240.0, 70.0, -200.0), 0, Seen::Emission); + } + for i in 0..40 { + let from = DVec3::new(-1800.0, 120.0, -900.0 + i as f64 * 40.0); + smart_drone(&mut sim, DroneKind::Kh22, from, DVec3::new(1800.0, 70.0, 1800.0)); + } + sim.tick = 40; + sim.adapt(); + let assigned: Vec<(u32, Intent, u64, u16)> = sim + .queue + .iter() + .map(|(_, d)| d) + .filter(|d| d.aim.unit != 0) + .map(|d| (d.aim.unit, d.intent, d.aim.since, d.aim.switches)) + .collect(); + assert_eq!(assigned.len(), ASSIGN_PER_TICK, "the first adaptive tick did not assign its bounded share"); + let mut per: HashMap = HashMap::new(); + for (unit, intent, since, switches) in &assigned { + *per.entry(*unit).or_insert(0) += 1; + assert_eq!(*intent, Intent::React, "a reaction to a disclosure was recorded as something else"); + assert!(*switches == 0 && *since == 40); + } + assert!(per.values().all(|n| *n <= ASSIGN_PER_CONTACT), "one contact drew more than {ASSIGN_PER_CONTACT} drones: {per:?}"); + assert!(sim.retarget_ids().len() <= ASSIGN_PER_TICK); + + sim.tick = 80; + sim.adapt(); + let now = sim.queue.iter().filter(|(_, d)| d.aim.unit != 0).count(); + assert!(now > assigned.len(), "the next adaptive tick assigned nobody"); + assert_eq!(now, 32, "the second adaptive tick did not assign its bounded share"); + // No drone's aim repeats a unit twice in the queue: an assignment is per drone. + let mut drones: Vec = sim.queue.iter().map(|(_, d)| d.id).collect(); + drones.sort_unstable(); + drones.dedup(); + assert_eq!(drones.len(), 40); + } + + // #127: a fresh disclosure replaces an aim only after the hold and within the cap, and only when + // it is clearly better placed — a switch throws the route away and costs a new one. + #[test] + fn a_fresh_disclosure_switches_an_aim_only_after_the_hold() { + let mut sim = Sim::new(47); + let far = watch(&mut sim, SamKind::Patriot, DVec3::new(400.0, 70.0, 400.0), 0, Seen::Emission); + let near = watch(&mut sim, SamKind::Ew, DVec3::new(-520.0, 70.0, -520.0), 0, Seen::Jamming); + let mut d = sim.make_drone(DroneKind::Kh22, DVec3::new(-600.0, 120.0, -600.0), DVec3::X, DVec3::new(400.0, 70.0, 400.0)); + d.smart = true; + let at = sim.intel.contacts()[&far].at; + d.target = at + d.err; + d.intent = Intent::Plan; + d.aim = Aim::unit(far, 0, Confidence::Confirmed); + let id = d.id; + sim.queue.push((0, d)); + // The jammer shows itself again later: a fresh disclosure. + sim.intel.note(near, SamKind::Ew, sim.intel.contacts()[&near].at, true, 100, Seen::Jamming); + + sim.tick = 120; + sim.adapt_targets(); + assert_eq!(goal_unit(&sim, id), far, "the aim was switched before the hold elapsed"); + sim.tick = 160; + sim.adapt_targets(); + assert_eq!(goal_unit(&sim, id), near, "a clearly better placed fresh disclosure did not switch the aim"); + let d = sim.queue.iter().map(|(_, d)| d).find(|x| x.id == id).unwrap(); + assert_eq!((d.intent, d.aim.switches), (Intent::React, 1), "the switch was not recorded as a reaction"); + + // The cap holds: a drone that has switched its fill keeps the aim it has. + let d = sim.queue.iter_mut().map(|(_, d)| d).find(|x| x.id == id).unwrap(); + d.aim.switches = SWITCH_CAP; + let before = d.target; + sim.intel.note(far, SamKind::S400, DVec3::new(-640.0, 70.0, -640.0), true, 300, Seen::Launch); + sim.tick = 400; + sim.adapt_targets(); + assert_eq!(sim.queue.iter().map(|(_, d)| d).find(|x| x.id == id).unwrap().target, before, "a drone past the switch cap was turned again"); + } + + // #127: an aim the picture watched die is replaced by the nearest live contact — a distinct, + // lower-priority decision than a disclosure reaction. + #[test] + fn a_watched_kill_replaces_the_aim_and_queues_a_lower_priority_route() { + let mut sim = Sim::new(53); + let dead = watch(&mut sim, SamKind::S400, DVec3::new(0.0, 70.0, 0.0), 0, Seen::Emission); + let alive = watch(&mut sim, SamKind::S400, DVec3::new(300.0, 70.0, 300.0), 0, Seen::Emission); + let mut d = sim.make_drone(DroneKind::Kh22, DVec3::new(-600.0, 120.0, -600.0), DVec3::X, DVec3::new(0.0, 70.0, 0.0)); + d.smart = true; + d.target = sim.intel.contacts()[&dead].at + d.err; + d.aim = Aim::unit(dead, 0, Confidence::Confirmed); + let id = d.id; + sim.drones.push(d); + // The enemy's own munition watched it die: the picture stops believing in it. + sim.intel.outcome(dead, false, DVec3::new(0.0, 70.0, 0.0), 40); + sim.tick = 40; + sim.adapt_targets(); + let d = sim.drones.iter().find(|x| x.id == id).unwrap(); + assert_eq!(d.aim.unit, alive, "the dead aim was not replaced by the nearest live contact"); + assert_eq!(d.intent, Intent::Replace); + assert_eq!(d.aim.switches, 1); + let pending = sim.pending_intents(); + assert!(pending.iter().any(|(_, i)| *i == Intent::Replace), "the replacement has no route job"); + } + + // #127: a unit that fires is observed to fire, and the picture counts the launches it watched. The + // magazine behind them is never in the picture: a hidden empty one changes nothing. + #[test] + fn launched_rounds_are_observed_but_the_magazine_is_not() { + let mut sim = Sim::new(59); + let id = watch(&mut sim, SamKind::Patriot, DVec3::new(0.0, 70.0, 0.0), 10, Seen::Launch); + sim.intel.note(id, SamKind::Patriot, DVec3::new(0.0, 70.0, 0.0), true, 20, Seen::Launch); + sim.tick = 20; + assert_eq!(sim.intel.contacts()[&id].launches, 2, "observed launches are not counted"); + let report = sim.enemy_picture(); + assert_eq!(report.contacts.iter().find(|c| c.id == id).unwrap().launches, 2); + let grid = Grid { min: DVec3::new(-480.0, 0.0, -480.0), cell: 16.0, size: 60 }; + let before = sim.price_grid(&grid, DroneKind::Kh101); + // The magazine is empty and the launcher split from its radar: hidden changes. + let sam = sim.sams.iter_mut().find(|s| s.id == id).unwrap(); + sam.missiles = 0; + sam.bursts = 0; + sam.set_mode(super::super::battery::SamMode::LauncherOnly); + sim.sams.iter_mut().find(|s| s.id == id).unwrap().radar_on = false; + assert_eq!(before, sim.price_grid(&grid, DroneKind::Kh101), "a hidden magazine changed the prices"); + assert_eq!(sim.enemy_picture().contacts.iter().find(|c| c.id == id).unwrap().launches, 2); + } + + // #127: land the job's knowledge version did not map is not free. Given the same grid, the route + // with unknown cells priced keeps to the mapped land where the free-unknown one cuts across. + #[test] + fn unknown_land_is_not_free_in_a_replanned_route() { + let size = 80; + let grid = Grid { min: DVec3::new(-640.0, 0.0, -640.0), cell: 16.0, size }; + // The plan mapped the left half; under it the land is high, so the mapped way costs altitude. + let mut heights = vec![f32::NAN; (size * size) as usize]; + for i in 0..size / 2 { + for j in 0..size { + heights[(i * size + j) as usize] = 400.0; + } + } + let prices = vec![1.0f32; (size * size) as usize]; + let from = DVec3::new(-520.0, 120.0, -520.0); + let to = DVec3::new(-40.0, 120.0, 520.0); + let plan = |unknown: f32| { + let relief = raid::Relief { heights: &heights, slope: 1.0, floor: 70.0, unknown }; + let (route, _) = raid::plan_route_on(&grid, &prices, Some(&relief), from, to); + route + }; + let unknown_cells = |route: &[DVec3]| { + route.iter().filter(|p| { let (i, j) = grid.index(**p); heights[(i * size + j) as usize].is_nan() }).count() + }; + let free = plan(0.0); + let priced = plan(UNKNOWN_LAND); + assert!(unknown_cells(&free) > 0, "the test's free-unknown route never left the mapped land"); + assert!(unknown_cells(&priced) < unknown_cells(&free), "priced unknown land did not keep the route to what was mapped"); + assert_eq!(priced.last().unwrap(), &to); + } + + // #127: the queue is bounded, and a job that is not worth more than what is queued does not + // displace anything. + #[test] + fn the_route_queue_is_bounded_and_refuses_a_worthless_job() { + let mut sim = Sim::new(61); + for i in 0..JOBS_MAX { + let mut d = sim.make_drone(DroneKind::Kh22, DVec3::new(i as f64, 100.0, 0.0), DVec3::X, DVec3::new(1000.0, 70.0, 0.0)); + d.smart = true; + let id = d.id; + sim.drones.push(d); + sim.queue_route(RouteJob::for_test(id, DVec3::new(1000.0, 70.0, 0.0), 0, 1, Arc::new(Vec::new()), None)); + } + assert_eq!(sim.retargets.len(), JOBS_MAX); + // A replacement is worth less than a plan's objective: the queue refuses it. + let mut d = sim.make_drone(DroneKind::Kh22, DVec3::new(0.0, 100.0, 50.0), DVec3::X, DVec3::new(1000.0, 70.0, 50.0)); + d.smart = true; + let id = d.id; + sim.drones.push(d); + sim.queue_route(RouteJob::for_test(id, DVec3::new(1000.0, 70.0, 50.0), 0, 2, Arc::new(Vec::new()), None).with_intent(Intent::Replace)); + assert_eq!(sim.retargets.len(), JOBS_MAX, "a worthless job grew the queue"); + // Nothing queued is a replacement: a replacement is the lowest priority and does displace one. + sim.retargets.iter_mut().for_each(|j| j.intent = Intent::Replace); + let mut d = sim.make_drone(DroneKind::Kh22, DVec3::new(0.0, 100.0, 60.0), DVec3::X, DVec3::new(1000.0, 70.0, 60.0)); + d.smart = true; + let id = d.id; + sim.drones.push(d); + sim.queue_route(RouteJob::for_test(id, DVec3::new(1000.0, 70.0, 60.0), 0, 2, Arc::new(Vec::new()), None).with_intent(Intent::Plan)); + assert_eq!(sim.retargets.len(), JOBS_MAX, "the bound was broken"); + assert!(sim.retargets.back().is_some_and(|j| j.drone == id && j.intent == Intent::Plan), "the plan's job did not make room"); + } + + // #127: the same seed makes the same intents, aims and jobs; the throttle and the priority order + // never make it seed-dependent in a way that differs between two runs of one seed. + #[test] + fn the_same_seed_chose_the_same_intents_and_jobs() { + let build = |seed: u64| { + let mut sim = Sim::new(seed); + for k in 0..5 { + watch(&mut sim, SamKind::S400, DVec3::new(-500.0 + k as f64 * 250.0, 70.0, 0.0), 0, Seen::Emission); + } + for i in 0..24 { + smart_drone(&mut sim, DroneKind::Kh22, DVec3::new(-1600.0, 110.0, -500.0 + i as f64 * 40.0), DVec3::new(1600.0, 70.0, 1600.0)); + } + sim.tick = 40; + sim.adapt(); + let aims: Vec<_> = sim.queue.iter().map(|(_, d)| (d.id, d.intent, d.aim.unit, d.aim.since, d.aim.switches)).collect(); + let jobs: Vec<_> = sim.retargets.iter().map(|j| (j.id, j.drone, j.unit, j.version, j.priority())).collect(); + (aims, jobs) + }; + let a = build(67); + let b = build(67); + assert_eq!(a, b, "the same seed produced different intents or jobs"); + assert!(!a.1.is_empty(), "the test no longer leaves jobs pending"); + } +} diff --git a/airdef-mod/src/sim/fires.rs b/airdef-mod/src/sim/fires.rs index 3fc2feb..3ccc65b 100644 --- a/airdef-mod/src/sim/fires.rs +++ b/airdef-mod/src/sim/fires.rs @@ -84,6 +84,9 @@ impl Fires { } impl Fire { + /// The observed surface for recording; None means it has not been resolved. + pub(super) fn surface_is_water(&self) -> Option { self.land.map(|land| !land) } + fn take_blocks(self) -> Vec { self.blocks .into_iter() diff --git a/airdef-mod/src/sim/generated_bounds.rs b/airdef-mod/src/sim/generated_bounds.rs new file mode 100644 index 0000000..e915677 --- /dev/null +++ b/airdef-mod/src/sim/generated_bounds.rs @@ -0,0 +1,104 @@ +// Generated by tools/gen_models.py from the final staged models; do not edit. +pub fn bounds(id: &str) -> Option<([f64; 3], [f64; 3])> { + Some(match id { + "airdef:amraam_missile" => ([0.97, 0.97, -16.0], [15.03, 15.03, 25.9]), + "airdef:barge" => ([-16.0, 0.0, -16.0], [32.0, 6.5, 32.0]), + "airdef:beam" => ([6.5, 6.5, -16.0], [9.5, 9.5, 32.0]), + "airdef:buk_chassis" => ([-13.3, 0.0, -16.0], [29.3, 15.7, 31.0]), + "airdef:buk_chassis_burnt" => ([-13.3, 0.0, -16.0], [29.3, 15.7, 31.0]), + "airdef:buk_rails" => ([-12.0, 0.0, -16.0], [28.0, 7.5, 27.0]), + "airdef:buk_rails_burnt" => ([-12.0, 0.0, -16.0], [28.0, 7.5, 27.0]), + "airdef:buk_turret" => ([-4.0, 0.0, -10.0], [20.0, 14.0, 20.0]), + "airdef:buk_turret_burnt" => ([-4.0, 0.0, -10.0], [20.0, 14.0, 20.0]), + "airdef:debris" => ([2.0, 6.0, 3.0], [14.0, 9.0, 13.0]), + "airdef:depot" => ([-14.0, 0.0, -14.0], [30.0, 17.0, 30.0]), + "airdef:depot_burnt" => ([-14.0, 0.0, -14.0], [30.0, 17.0, 30.0]), + "airdef:ew_truck" => ([-8.56, 0.0, -16.0], [24.56, 32.0, 32.0]), + "airdef:ew_truck_burnt" => ([-8.56, 0.0, -16.0], [24.56, 32.0, 32.0]), + "airdef:fireball" => ([0.0, 0.0, 0.0], [16.0, 16.0, 16.0]), + "airdef:flame" => ([5.625, 5.625, -8.0], [10.375, 10.375, 26.0]), + "airdef:flare" => ([6.5, 6.5, 6.5], [9.5, 16.0, 9.5]), + "airdef:flash" => ([-4.0, -4.0, -4.0], [20.0, 20.0, 20.0]), + "airdef:gepard_dish" => ([-4.0, 0.0, 6.0], [20.0, 14.0, 8.0]), + "airdef:gepard_guns" => ([-9.0, 3.5, -16.0], [25.0, 9.0, 14.0]), + "airdef:gepard_guns_burnt" => ([-9.0, 3.5, -16.0], [25.0, 9.0, 14.0]), + "airdef:gepard_hull" => ([-14.5, 0.0, -15.0], [30.5, 15.9, 31.0]), + "airdef:gepard_hull_burnt" => ([-14.5, 0.0, -15.0], [30.5, 15.9, 31.0]), + "airdef:gepard_turret" => ([-5.745184, 0.0, -10.0], [21.745184, 18.0, 16.0]), + "airdef:gepard_turret_burnt" => ([-5.745184, 0.0, -10.0], [21.745184, 18.0, 16.0]), + "airdef:geran3" => ([-10.0, 4.341543, -16.0], [26.0, 17.005161, 29.9]), + "airdef:geran3_flame" => ([6.15, 6.15, 6.0], [9.85, 9.85, 24.0]), + "airdef:gerbera" => ([-9.0, 3.0, -15.0], [25.0, 13.0, 24.5]), + "airdef:irist_chassis" => ([-8.56, 0.0, -16.0], [24.56, 20.0, 32.0]), + "airdef:irist_chassis_burnt" => ([-8.56, 0.0, -16.0], [24.56, 20.0, 32.0]), + "airdef:irist_missile" => ([1.47, 1.47, -16.0], [14.53, 14.53, 23.9]), + "airdef:irist_tubes" => ([-2.5, 0.0, -16.0], [18.5, 9.0, 32.0]), + "airdef:irist_tubes_burnt" => ([-2.5, 0.0, -16.0], [18.5, 9.0, 32.0]), + "airdef:iskander" => ([-1.74, -1.74, -16.0], [17.74, 17.74, 29.9]), + "airdef:kalibr" => ([-6.0, 1.52, -16.0], [22.0, 14.48, 29.9]), + "airdef:kh101" => ([-12.5, 3.2, -16.0], [28.5, 16.0, 28.4]), + "airdef:kh22" => ([-10.0, -1.2, -16.0], [26.0, 17.2, 29.9]), + "airdef:kinzhal" => ([-3.06, -3.06, -16.0], [19.06, 19.06, 29.9]), + "airdef:lancet" => ([-1.40452, -1.40452, -16.0], [17.40452, 17.40452, 24.5]), + "airdef:launch_ramp" => ([1.0, 0.0, -12.708033], [15.0, 19.342915, 32.0]), + "airdef:launcher_truck" => ([-10.5, -1.379664, -16.379664], [26.5, 31.288582, 32.0]), + "airdef:m48n6" => ([0.09, 0.09, -16.0], [15.91, 15.91, 27.9]), + "airdef:m57e6" => ([1.47, 1.47, -16.0], [14.53, 14.53, 26.9]), + "airdef:m77n6" => ([0.82, 0.82, -16.0], [15.18, 15.18, 29.9]), + "airdef:nasams_launcher" => ([0.0, 0.0, -16.0], [16.0, 9.0, 32.0]), + "airdef:nasams_launcher_burnt" => ([0.0, 0.0, -16.0], [16.0, 9.0, 32.0]), + "airdef:nasams_trailer" => ([-8.56, 0.0, -16.0], [24.56, 13.0, 26.0]), + "airdef:nasams_trailer_burnt" => ([-8.56, 0.0, -16.0], [24.56, 13.0, 26.0]), + "airdef:p1sun" => ([-0.5, -0.5, -14.0], [16.5, 16.5, 10.0]), + "airdef:p1sun_launcher" => ([-0.5, 0.0, -16.0], [16.5, 7.0, 32.0]), + "airdef:p1sun_launcher_burnt" => ([-0.5, 0.0, -16.0], [16.5, 7.0, 32.0]), + "airdef:pac3" => ([1.47, 1.47, -16.0], [14.53, 14.53, 25.9]), + "airdef:pantsir_dish" => ([-6.5, -8.0, 7.0], [22.5, 16.5, 10.0]), + "airdef:pantsir_pack" => ([-11.0, 2.0, -16.0], [27.0, 15.0, 12.0]), + "airdef:pantsir_pack_burnt" => ([-11.0, 2.0, -16.0], [27.0, 15.0, 12.0]), + "airdef:pantsir_truck" => ([-8.56, 0.0, -16.0], [24.56, 20.0, 32.0]), + "airdef:pantsir_truck_burnt" => ([-8.56, 0.0, -16.0], [24.56, 20.0, 32.0]), + "airdef:pantsir_turret" => ([-2.0, 0.0, -11.0], [18.0, 17.0, 16.0]), + "airdef:pantsir_turret_burnt" => ([-2.0, 0.0, -11.0], [18.0, 17.0, 16.0]), + "airdef:patriot_launcher" => ([-8.0, 0.0, -14.0], [24.0, 19.0, 30.0]), + "airdef:patriot_launcher_burnt" => ([-8.0, 0.0, -14.0], [24.0, 19.0, 30.0]), + "airdef:patriot_radar" => ([-9.0, -1.410323, -10.0], [25.0, 30.0, 26.0]), + "airdef:patriot_trailer" => ([-9.06, 0.0, -16.0], [25.06, 16.0, 32.0]), + "airdef:patriot_trailer_burnt" => ([-9.06, 0.0, -16.0], [25.0, 16.0, 32.0]), + "airdef:patriot_truck" => ([-8.56, 0.0, -16.0], [24.56, 22.0, 32.0]), + "airdef:radar_mast" => ([4.0, 0.0, 4.0], [12.0, 32.0, 12.0]), + "airdef:radar_mast_burnt" => ([4.0, 0.0, 4.0], [12.0, 32.0, 12.0]), + "airdef:radar_panel" => ([-10.5, -6.0, 7.0], [26.5, 26.5, 12.0]), + "airdef:radar_trailer" => ([-9.0, -1.379664, -12.0], [25.0, 18.0, 26.379664]), + "airdef:radar_truck" => ([-8.56, 0.0, -16.0], [24.56, 20.0, 32.0]), + "airdef:radar_truck_burnt" => ([-8.56, 0.0, -16.0], [24.56, 20.0, 32.0]), + "airdef:reentry" => ([6.5, 6.5, -16.0], [9.5, 9.5, 32.0]), + "airdef:rm48u" => ([0.09, 0.09, -16.0], [15.91, 15.91, 27.9]), + "airdef:s400_chassis" => ([-6.05, 0.0, -16.0], [22.05, 19.4, 32.0]), + "airdef:s400_chassis_burnt" => ([-6.05, 0.0, -16.0], [22.05, 19.4, 32.0]), + "airdef:s400_tubes" => ([-0.5, 0.0, -16.0], [16.5, 15.0, 32.0]), + "airdef:s400_tubes_burnt" => ([-0.5, 0.0, -16.0], [16.5, 15.0, 32.0]), + "airdef:s500_chassis" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]), + "airdef:s500_chassis_burnt" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]), + "airdef:s500_tubes" => ([-3.0, 0.0, -16.0], [19.0, 9.5, 32.0]), + "airdef:s500_tubes_burnt" => ([-3.0, 0.0, -16.0], [19.0, 9.5, 32.0]), + "airdef:searchlight" => ([1.0, 0.0, 1.0], [15.0, 14.0, 15.0]), + "airdef:searchlight_burnt" => ([1.0, 0.0, 1.0], [15.0, 14.0, 15.0]), + "airdef:searchlight_head" => ([3.0, 2.0, -2.5], [13.0, 14.0, 15.0]), + "airdef:searchlight_head_burnt" => ([3.0, 2.0, -2.5], [13.0, 14.0, 14.0]), + "airdef:shahed" => ([-13.0, 4.73352, -16.0], [29.0, 13.2, 28.0]), + "airdef:shahed_prop" => ([-4.0, -4.0, 7.0], [20.0, 20.0, 9.5]), + "airdef:target" => ([2.0, -8.0, 2.0], [14.0, 24.0, 14.0]), + "airdef:tor_chassis" => ([-13.3, 0.0, -16.0], [29.3, 15.7, 31.0]), + "airdef:tor_chassis_burnt" => ([-13.3, 0.0, -16.0], [29.3, 15.7, 31.0]), + "airdef:tor_turret" => ([-4.0, 0.0, -11.0], [20.0, 17.5, 22.0]), + "airdef:tor_turret_burnt" => ([-4.0, 0.0, -11.0], [20.0, 17.5, 22.0]), + "airdef:tpk_cap" => ([0.0, 0.0, 7.7], [16.0, 16.0, 8.3]), + "airdef:tpk_cap_burnt" => ([0.0, 0.0, 7.7], [16.0, 16.0, 8.3]), + "airdef:tracer" => ([7.3, 7.3, -16.0], [8.7, 8.7, 32.0]), + "airdef:tzm_truck" => ([-8.56, 0.0, -16.0], [24.56, 26.0, 32.0]), + "airdef:tzm_truck_burnt" => ([-8.5, 0.0, -16.0], [24.56, 26.0, 32.0]), + "airdef:zircon" => ([0.16, 0.16, -16.0], [15.84, 15.84, 29.9]), + _ => return None, + }) +} diff --git a/airdef-mod/src/sim/launch.rs b/airdef-mod/src/sim/launch.rs new file mode 100644 index 0000000..c3ae25c --- /dev/null +++ b/airdef-mod/src/sim/launch.rs @@ -0,0 +1,117 @@ +//! Short, deliberately fictional cold-launch choreography and physical tube visuals. +//! Timing and geometry are Minecraft presentation settings, not weapon specifications. +use super::*; + +pub const OPEN_TICKS: u64 = 6; +#[derive(Clone, Copy, Debug)] +pub(super) struct Prepare { + pub target: u32, + pub cell: u32, + pub start: u64, + pub ground: bool, +} + +impl Sam { + pub fn animated_tubes(&self) -> bool { matches!(self.kind, SamKind::S400 | SamKind::S500) } + /// Reconcile visual occupancy with the actual shared magazine after load, repair or supply. + pub fn sync_tube_cells(&mut self) { + if !self.animated_tubes() { self.spent_cells = 0; return; } + let cells = self.kind.spec().missile.as_ref().map_or(0, |m| m.magazine); + let valid = ((1u16 << cells) - 1) as u8; + self.spent_cells &= valid; + let loaded = (self.missiles + self.ground_rounds).min(cells); + let desired = cells - loaded; + while self.spent_cells.count_ones() > desired { + let bit = self.spent_cells.trailing_zeros(); + self.spent_cells &= !(1 << bit); + } + while self.spent_cells.count_ones() < desired { + let bit = ((!self.spent_cells) & valid).trailing_zeros(); + self.spent_cells |= 1 << bit; + } + if self.preparing.is_none() { self.cap_open = 0.0; } + } + pub(super) fn next_tube_cell(&self) -> u32 { + let cells = self.kind.spec().missile.as_ref().map_or(1, |m| m.magazine); + (0..cells).find(|n| self.spent_cells & (1 << n) == 0).unwrap_or(0) + } + pub(super) fn cancel_prepare(&mut self) { + self.preparing = None; + self.cap_open = 0.0; + } + /// Opening reserves a particular cell; ammunition is consumed only at actual ejection. + pub(super) fn prepare_tube(&mut self, target: u32, now: u64) -> bool { + if !self.animated_tubes() { return true; } + if self.preparing.is_none_or(|p| p.target != target) { + self.preparing = Some(Prepare { target, cell: self.next_tube_cell(), start: now, ground: false }); + } + let p = self.preparing.unwrap(); + self.cap_cell = p.cell as u8; + self.cap_open = (now.saturating_sub(p.start) as f32 / OPEN_TICKS as f32).clamp(0.0, 1.0); + self.cap_open >= 1.0 + } + pub(super) fn eject_cell(&mut self, cell: u32) { + if self.animated_tubes() { self.spent_cells |= 1 << cell; } + self.cancel_prepare(); + } +} + +/// A hinge is a readable game abstraction. No claim is made about the real cover mechanism. +pub(super) fn draw_caps(sam: &Sam, out: &mut Out) { + if !sam.animated_tubes() || !sam.alive() { return; } + let Muzzle::Grid(cols, rows, size) = sam.kind.spec().muzzle else { return }; + let (_, basis) = sam.mount(); + let side = size / 16.0 * sam.scale(); + for cell in 0..cols * rows { + let open = if sam.spent_cells & (1 << cell) != 0 { 1.0 } + else if sam.cap_cell as u32 == cell { sam.cap_open } else { 0.0 }; + // Grid muzzle includes TUBE_PIVOT and is exactly the front plane of the model. + let (closed, _) = sam.muzzle(cell); + let hinge = closed + basis * DVec3::new(-side * 0.5, 0.0, 0.0); + let rotation = basis * DQuat::from_rotation_y((open as f64) * 1.8); + let center = hinge + rotation * DVec3::new(side * 0.5, 0.0, 0.0); + out.model(sam.id + 8 + cell, "airdef:tpk_cap", center, rotation, side, false); + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn every_animated_magazine_has_one_round_per_drawn_tube() { + for kind in [SamKind::S400, SamKind::S500] { + let sam = Sam::new(16, kind, DVec3::ZERO, 0.0); + let Muzzle::Grid(cols, rows, _) = kind.spec().muzzle else { panic!("tube grid") }; + assert_eq!(sam.missiles, cols * rows); + let mut out = Out::default(); + draw_caps(&sam, &mut out); + assert_eq!(out.models.len(), sam.missiles as usize); + for (cell, model) in out.models.iter().enumerate() { + assert!(model.at.distance(sam.muzzle(cell as u32).0) < 1e-9, "closed lid covers the real muzzle"); + } + } + } + + #[test] + fn opening_reserves_a_cell_and_abort_does_not_spend_rounds() { + let mut sam = Sam::new(16, SamKind::S400, DVec3::ZERO, 0.0); + assert!(!sam.prepare_tube(42, 10)); + assert!(!sam.prepare_tube(42, 13)); + assert_eq!(sam.cap_open, 0.5); + let cell = sam.preparing.unwrap().cell; + assert_eq!(sam.missiles, 4); + sam.cancel_prepare(); + assert_eq!((sam.missiles, sam.spent_cells, sam.cap_open), (4, 0, 0.0)); + assert!(!sam.prepare_tube(42, 20)); + assert_eq!(sam.preparing.unwrap().cell, cell); + assert!(sam.prepare_tube(42, 20 + OPEN_TICKS)); + sam.missiles -= 1; + sam.eject_cell(cell); + assert_ne!(sam.next_tube_cell(), cell); + sam.sync_tube_cells(); + assert_eq!(sam.spent_cells.count_ones(), 1); + sam.missiles = 4; + sam.sync_tube_cells(); + assert_eq!(sam.spent_cells, 0); + } +} diff --git a/airdef-mod/src/sim/lights.rs b/airdef-mod/src/sim/lights.rs index afeecbf..41921b1 100644 --- a/airdef-mod/src/sim/lights.rs +++ b/airdef-mod/src/sim/lights.rs @@ -1,5 +1,5 @@ -//! Night fighting: guns aim by sight, and at night they hit far less unless -//! the target is lit. Searchlights follow the network's tracks with their +//! Night fighting: darkness penalizes aiming without fresh radar fire control, +//! unless the target is lit. Searchlights follow the network's tracks with their //! beams; illumination flares (fired by the guns on their own, or by the //! player) hang over the sky for half a minute and light everything round. use super::*; @@ -24,6 +24,11 @@ pub const DARK_HITS: f64 = 0.4; /// Ticks between a gun's own flares. const FLARE_EVERY: u32 = 500; +/// Night only reduces a gun's hit chance when it has to aim by sight. +pub(super) fn hit_factor(night: bool, lit: bool, radar_fire_control: bool) -> f64 { + if night && !lit && !radar_fire_control { DARK_HITS } else { 1.0 } +} + pub struct Flare { id: u32, pub pos: DVec3, diff --git a/airdef-mod/src/sim/logistics.rs b/airdef-mod/src/sim/logistics.rs index 13a6a77..fb3e945 100644 --- a/airdef-mod/src/sim/logistics.rs +++ b/airdef-mod/src/sim/logistics.rs @@ -4,13 +4,6 @@ //! reloads to the systems that wait, nearest first, and back. use super::*; -/// How near a depot a system reloads straight from it. -const DEPOT_REACH: f64 = 60.0; -/// How near a truck must come to hand a reload over, or to load up at a depot. -const HANDOVER: f64 = 10.0; -/// What a truck carries. -const TRUCK_LOAD: u32 = 2; - impl Sim { /// Whether ammunition is counted: a depot was set up (without one, magazines refill as /// before). A destroyed depot still counts: losing the last one leaves the systems short, @@ -19,6 +12,76 @@ impl Sim { self.sams.iter().any(|s| s.kind == SamKind::Depot) } + /// Prepare one explicitly typed round at a nearby living depot. Any displaced air round is retained there. + pub fn load_ground_round(&mut self, i: usize) -> Result { + let s = self.sams.get(i).ok_or("Нет такого комплекса")?; + if s.kind != SamKind::S400 { return Err("Для этого комплекса нет игрового профиля RM48U".into()); } + if !s.alive() || s.moving() || s.preparing.is_some() || s.reloading > 0 || s.part(Part::Launcher) <= 0.0 { + return Err("Комплекс должен быть исправен и развёрнут".into()); + } + if s.ground_return { return Err("Сначала отмените заявку на вывоз RM48U".into()); } + if s.ground_rounds >= s.cells() { return Err("Нет свободной ячейки для RM48U".into()); } + let depot = self.sams.iter().enumerate().filter(|(_, d)| d.kind == SamKind::Depot && d.alive() && d.ground_stock > 0 && d.pos.distance(s.pos) < DEPOT_REACH) + .min_by(|(_, a), (_, b)| a.pos.distance(s.pos).total_cmp(&b.pos.distance(s.pos)).then(a.id.cmp(&b.id))) + .map(|(j, _)| j); + let Some(depot) = depot else { + if s.ground_request { return Err("Заявка RM48U уже ожидает ТЗМ".into()); } + if !self.sams.iter().any(|d| d.alive() && ((d.kind == SamKind::Depot && d.ground_stock > 0) || (d.kind == SamKind::Tzm && d.ground_stock > 0))) { + if let Some((j, _)) = self.sams.iter().enumerate().find(|(_, d)| d.kind == SamKind::Depot && d.alive() && d.pos.distance(s.pos) < DEPOT_REACH) { + return Err(format!("Склад №{} рядом, но RM48U: 0. Задайте запас: /airdef ground-stock {} <количество>", j + 1, j + 1)); + } + return Err("Нет живого склада с RM48U рядом (60 блоков)".into()); + } + if !self.sams.iter().any(|t| t.kind == SamKind::Tzm && t.alive()) { return Err("Для доставки RM48U нужна исправная ТЗМ".into()); } + self.sams[i].ground_request = true; + return Ok(format!("С-400 №{}: заявка RM48U ожидает доставки ТЗМ", i + 1)); + }; + let displaced = u32::from(self.sams[i].missiles + self.sams[i].ground_rounds >= self.sams[i].cells()); + let air_48n6_stock = self.sams[depot].air_48n6_stock.checked_add(displaced).ok_or("Запас склада переполнен")?; + self.sams[i].missiles -= displaced; + self.sams[i].ground_rounds += 1; + self.sams[i].ground_request = false; + if self.sams[i].ground_rounds >= self.sams[i].cells() && self.sams[i].kind.spec().gun.is_none() { self.sams[i].empty = false; } + self.sams[depot].ground_stock -= 1; + self.sams[depot].air_48n6_stock = air_48n6_stock; + Ok(format!("С-400 №{}: загружена RM48U (игровой профиль), ударных {}, ПВО {}; на складе RM48U {}, снятых ракет ПВО {}", i + 1, self.sams[i].ground_rounds, self.sams[i].missiles, self.sams[depot].ground_stock, self.sams[depot].air_48n6_stock)) + } + + /// Return a loaded ground round to a nearby depot; restore a previously removed air round if available. + pub fn unload_ground_round(&mut self, i: usize) -> Result { + let s = self.sams.get(i).ok_or("Нет такого комплекса")?; + if s.kind != SamKind::S400 || !s.alive() || s.moving() || s.preparing.is_some() || s.reloading > 0 || s.part(Part::Launcher) <= 0.0 || s.ground_rounds == 0 { + return Err("Нужен исправный развёрнутый С-400 с загруженной RM48U".into()); + } + if s.ground_return { return Err("Заявка на вывоз RM48U уже ожидает ТЗМ".into()); } + let depot = self.sams.iter().enumerate().filter(|(_, d)| d.kind == SamKind::Depot && d.alive() && d.pos.distance(s.pos) < DEPOT_REACH) + .min_by(|(_, a), (_, b)| a.pos.distance(s.pos).total_cmp(&b.pos.distance(s.pos)).then(a.id.cmp(&b.id))) + .map(|(j, _)| j); + let Some(depot) = depot else { + if s.ground_request { return Err("Сначала отмените заявку на доставку RM48U".into()); } + if !self.sams.iter().any(|d| d.kind == SamKind::Depot && d.alive() && d.ground_stock < STOCK_LIMIT) { return Err("Нет доступного склада для возврата RM48U".into()); } + if !self.sams.iter().any(|t| t.kind == SamKind::Tzm && t.alive() && t.drive_speed() > 0.0 && t.ground_stock.saturating_add(t.air_48n6_stock) < TRUCK_LOAD) { return Err("Нет исправной ТЗМ со свободным местом для RM48U".into()); } + self.sams[i].ground_return = true; + return Ok(format!("С-400 №{}: RM48U зарезервирована, ожидается вывоз ТЗМ", i + 1)); + }; + let stock = self.sams[depot].ground_stock.checked_add(1).filter(|n| *n <= STOCK_LIMIT).ok_or("Запас склада переполнен")?; + self.sams[i].ground_rounds -= 1; + self.sams[depot].ground_stock = stock; + if self.sams[depot].air_48n6_stock > 0 { + self.sams[depot].air_48n6_stock -= 1; + self.sams[i].missiles += 1; + } + Ok(format!("С-400 №{}: выгружена RM48U; ударных {}, ПВО {}", i + 1, self.sams[i].ground_rounds, self.sams[i].missiles)) + } + + pub fn cancel_ground_request(&mut self, i: usize) -> Result { + let s = self.sams.get_mut(i).ok_or("Нет такого комплекса")?; + if s.kind != SamKind::S400 || (!s.ground_request && !s.ground_return) { return Err("Нет ожидающей заявки RM48U".into()); } + s.ground_request = false; + s.ground_return = false; + Ok(format!("С-400 №{}: заявка RM48U отменена", i + 1)) + } + /// Reloads from depots near the systems, and the trucks' rounds. pub(super) fn step_supply(&mut self, out: &mut Out) { if !self.logistics() { @@ -55,6 +118,124 @@ impl Sim { } let at = truck.pos; let truck_id = truck.id; + // A return is picked up physically; its cargo remains marked until a depot accepts it. + // The return keeps its priority only while it can actually proceed (#128): a carrier + // holding a reserved round needs a depot with room in reach, an empty one a system + // ready to hand a round over (whole, deployed, not claimed by another truck, a + // reachable route). A return that is merely claimed but impossible used to clear the + // job and stop the truck, starving independent supply requests; the claim now stays + // untouched and only real RM48U demand pulls the truck away from its watch. + let return_cargo = truck.return_cargo && truck.ground_stock > 0; + let can_pick = truck.ground_stock == 0 && truck.air_48n6_stock < TRUCK_LOAD; + let claim_pending = return_cargo || (can_pick && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return)); + let taken: Vec = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive() && s.id != truck_id).filter_map(|s| s.job).collect(); + let target = if return_cargo { + self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock < STOCK_LIMIT && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at))) + } else if can_pick { + self.sams.iter().filter(|s| s.kind == SamKind::S400 && s.alive() && s.ground_return && s.ground_rounds > 0 && !s.moving() && s.preparing.is_none() && s.reloading == 0 && s.part(Part::Launcher) > 0.0 && !taken.contains(&s.id) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at))) + } else { + None + }.map(|s| (s.id, s.pos)); + if let Some((id, to)) = target { + self.sams[t].job = Some(id); + if at.distance(to) > HANDOVER { + let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6); + self.order_move_near(t, to + side, 3.5); + } else if let Some(j) = self.sams.iter().position(|s| s.id == id) { + if self.sams[t].return_cargo { + self.sams[j].ground_stock += 1; + self.sams[t].ground_stock -= 1; + self.sams[t].return_cargo = false; + } else { + self.sams[j].ground_rounds -= 1; + self.sams[j].ground_return = false; + self.sams[t].ground_stock += 1; + self.sams[t].return_cargo = true; + if self.sams[t].air_48n6_stock > 0 && self.sams[j].missiles + self.sams[j].ground_rounds < self.sams[j].cells() { + self.sams[t].air_48n6_stock -= 1; + self.sams[j].missiles += 1; + } + } + self.sams[t].job = None; + out.sound("minecraft:block.piston.extend", at, 1.5, 0.8); + } + continue; + } + if claim_pending { + // The impossible return still outranks a mere stock run: without RM48U demand + // the truck holds its watch (and drops a stale job) exactly as before. + let supply_demand = (self.sams[t].ground_stock > 0 && self.sams.iter().any(|s| s.alive() && s.ground_request)) + || (self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) + && self.sams.iter().any(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock > 0)); + if !supply_demand { + self.sams[t].job = None; + continue; + } + } + // Typed strike cargo takes precedence over generic reloads. Jobs are reconstructed + // after loading a save; the request and physical cargo survive independently. + let typed_work = truck.ground_stock > 0 || truck.air_48n6_stock > 0 || + (self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) + && self.sams.iter().any(|s| s.kind == SamKind::Depot && s.alive() && s.ground_stock > 0)); + if typed_work { + if let Some(id) = self.sams[t].job { + let valid = self.sams.iter().find(|s| s.id == id).is_some_and(|s| s.alive() && !self.routes.out_of_reach(truck_id, s.id, now) && (s.kind == SamKind::Depot || (self.sams[t].ground_stock > 0 && s.kind == SamKind::S400 && s.ground_request))); + if !valid { self.sams[t].job = None; } + } + if let Some(id) = self.sams[t].job { + if let Some(j) = self.sams.iter().position(|s| s.id == id && s.alive()) { + if self.sams[j].pos.distance(at) <= HANDOVER { + if self.sams[j].kind == SamKind::Depot { + if let Some(n) = self.sams[j].air_48n6_stock.checked_add(self.sams[t].air_48n6_stock) { + self.sams[j].air_48n6_stock = n; + self.sams[t].air_48n6_stock = 0; + } + if self.sams[t].ground_stock > 0 && !self.sams.iter().any(|s| s.alive() && s.ground_request) && self.sams[j].ground_stock < STOCK_LIMIT { + let take = self.sams[t].ground_stock.min(STOCK_LIMIT - self.sams[j].ground_stock); + self.sams[j].ground_stock += take; + self.sams[t].ground_stock -= take; + // The last reserved round left the truck: nothing marks as return cargo any more. + if self.sams[t].ground_stock == 0 { self.sams[t].return_cargo = false; } + } + if self.sams[t].ground_stock == 0 && self.sams[j].ground_stock > 0 && self.sams[t].air_48n6_stock == 0 && self.sams.iter().any(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request) { + self.sams[j].ground_stock -= 1; + self.sams[t].ground_stock += 1; + } + } else if self.sams[j].kind == SamKind::S400 && self.sams[j].ground_request && !self.sams[j].moving() && self.sams[j].preparing.is_none() && self.sams[j].reloading == 0 && self.sams[j].part(Part::Launcher) > 0.0 && self.sams[j].ground_rounds < self.sams[j].cells() && self.sams[t].ground_stock > 0 { + let displaced = u32::from(self.sams[j].missiles + self.sams[j].ground_rounds >= self.sams[j].cells()); + self.sams[j].missiles -= displaced; + self.sams[j].ground_rounds += 1; + if self.sams[j].ground_rounds >= self.sams[j].cells() && self.sams[j].kind.spec().gun.is_none() { self.sams[j].empty = false; } + self.sams[j].ground_request = false; + self.sams[t].ground_stock -= 1; + // A round reserved for return redirected to a waiting request is no + // longer return cargo once the last one leaves the truck (#128). + if self.sams[t].ground_stock == 0 { self.sams[t].return_cargo = false; } + self.sams[t].air_48n6_stock += displaced; + out.sound("minecraft:block.piston.extend", at, 1.5, 0.8); + } + self.sams[t].job = None; + } else { + let to = self.sams[j].pos; + let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6); + self.order_move_near(t, to + side, 3.5); + continue; + } + } else { self.sams[t].job = None; } + } + let taken: Vec = self.sams.iter().filter(|s| s.kind == SamKind::Tzm && s.alive() && s.id != truck_id).filter_map(|s| s.job).collect(); + let next = if self.sams[t].ground_stock > 0 && self.sams.iter().any(|s| s.alive() && s.ground_request) { + self.sams.iter().filter(|s| s.kind == SamKind::S400 && s.alive() && s.ground_request && !s.moving() && s.preparing.is_none() && s.reloading == 0 && s.part(Part::Launcher) > 0.0 && s.ground_rounds < s.cells() && !taken.contains(&s.id) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at))) + } else { + self.sams.iter().filter(|s| s.kind == SamKind::Depot && s.alive() && (self.sams[t].air_48n6_stock > 0 || (self.sams[t].ground_stock > 0 && s.ground_stock < STOCK_LIMIT) || (self.sams[t].ground_stock == 0 && s.ground_stock > 0)) && !self.routes.out_of_reach(truck_id, s.id, now)).min_by(|a,b| a.pos.distance(at).total_cmp(&b.pos.distance(at))) + }.map(|s| (s.id, s.pos)); + if let Some((id, to)) = next { + self.sams[t].job = Some(id); + let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6); + if to.distance(at) > HANDOVER { self.order_move_near(t, to + side, 3.5); } + } + continue; + } // Where it was going: hand over, or load up. if let Some(job) = truck.job && let Some(j) = self.sams.iter().position(|s| s.id == job) @@ -121,3 +302,299 @@ impl Sim { out.sound("minecraft:block.piston.extend", sam.pos, 2.0, 0.6); } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn an_empty_nearby_depot_explains_the_missing_typed_stock() { + let mut sim = Sim::new(1); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 1.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + let error = sim.load_ground_round(1).unwrap_err(); + assert!(error.contains("RM48U: 0")); + assert!(error.contains("/airdef ground-stock 1")); + assert_eq!((sim.sams[0].ground_stock, sim.sams[1].ground_rounds, sim.sams[1].missiles), (0, 0, 4)); + } + + #[test] + fn return_waits_for_capacity_and_ready_carrier_and_can_be_cancelled() { + let mut sim = Sim::new(3); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 160.0)] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + sim.sams[1].ground_rounds = 1; + sim.sams[1].missiles = 3; + sim.sams[2].air_48n6_stock = 2; + assert!(sim.unload_ground_round(1).is_err()); + sim.sams[2].air_48n6_stock = 1; + sim.unload_ground_round(1).unwrap(); + sim.cancel_ground_request(1).unwrap(); + assert!(!sim.sams[1].ground_return); + sim.unload_ground_round(1).unwrap(); + sim.tick = 2; + sim.sams[1].reloading = 1; + sim.step_supply(&mut Out::default()); + assert_eq!(sim.sams[1].ground_rounds, 1); + sim.sams[1].reloading = 0; + sim.step_supply(&mut Out::default()); + assert_eq!((sim.sams[1].ground_rounds, sim.sams[1].missiles), (0, 4)); + assert_eq!((sim.sams[2].ground_stock, sim.sams[2].air_48n6_stock), (1, 0)); + assert!(sim.sams[2].return_cargo); + sim.direct_hit(sim.sams[2].pos + DVec3::Y, DVec3::ZERO, 1, &mut Out::default()); + sim.repair(); + assert_eq!((sim.sams[0].ground_stock, sim.sams[1].ground_rounds, sim.sams[2].ground_stock), (0, 0, 0)); + assert!(!sim.sams[2].return_cargo); + } + + #[test] + fn return_waits_for_moving_or_damaged_units() { + let mut sim = Sim::new(4); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 160.0)] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + sim.sams[1].ground_rounds = 1; + sim.sams[1].missiles = 3; + sim.unload_ground_round(1).unwrap(); + sim.tick = 2; + sim.sams[1].comps.0[Part::Launcher as usize] = 0.0; + sim.step_supply(&mut Out::default()); + assert_eq!(sim.sams[1].ground_rounds, 1); + sim.sams[1].comps.0[Part::Launcher as usize] = 1.0; + sim.sams[2].comps.0[Part::Tracks as usize] = 0.0; + sim.step_supply(&mut Out::default()); + assert_eq!(sim.sams[1].ground_rounds, 1); + sim.sams[2].comps.0[Part::Tracks as usize] = 1.0; + assert!(sim.order_move(1, DVec3::new(220.0, 70.0, 0.0))); + sim.step_supply(&mut Out::default()); + assert_eq!(sim.sams[1].ground_rounds, 1); + sim.sams[1].state = SamState::Search; + sim.step_supply(&mut Out::default()); + assert_eq!((sim.sams[1].ground_rounds, sim.sams[2].ground_stock), (0, 1)); + } + + #[test] + fn typed_delivery_uses_stock_and_shared_cells_and_survives_interruption() { + let mut sim = Sim::new(1); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 200.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + assert!(sim.load_ground_round(1).is_err()); + sim.sams[0].ground_stock = 1; + assert!(sim.load_ground_round(0).is_err()); + sim.load_ground_round(1).unwrap(); + sim.tick = 2; + sim.step_supply(&mut Out::default()); // assign depot + sim.step_supply(&mut Out::default()); // physically take one + assert_eq!((sim.sams[0].ground_stock, sim.sams[2].ground_stock), (0, 1)); + assert_eq!(sim.sams[1].ground_rounds, 0); + // Interrupted truck retains cargo and request; a restored job is not required. + sim.sams[2].job = None; + sim.sams[2].state = SamState::Search; + sim.sams[2].pos = sim.sams[1].pos; + sim.step_supply(&mut Out::default()); + sim.step_supply(&mut Out::default()); + assert_eq!((sim.sams[1].missiles, sim.sams[1].ground_rounds), (3, 1)); + assert_eq!((sim.sams[2].ground_stock, sim.sams[2].air_48n6_stock), (0, 1)); + assert!(!sim.sams[1].ground_request); + sim.sams[2].job = None; + sim.sams[2].state = SamState::Search; + sim.sams[2].pos = sim.sams[0].pos; + sim.step_supply(&mut Out::default()); + sim.step_supply(&mut Out::default()); + assert_eq!((sim.sams[0].air_48n6_stock, sim.sams[2].air_48n6_stock), (1, 0)); + assert!(sim.load_ground_round(1).is_err()); + } + #[test] + fn pending_delivery_waits_for_compatible_type_and_cell_after_unload() { + let mut sim = Sim::new(2); + for kind in [SamKind::Depot, SamKind::S400, SamKind::Tzm] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::ZERO, 0.0)); + } + sim.sams[1].ground_request = true; + sim.sams[2].ground_stock = 1; + sim.sams[1].kind = SamKind::Patriot; + sim.tick = 2; + for _ in 0..4 { sim.step_supply(&mut Out::default()); } + assert_eq!(sim.sams[2].ground_stock, 1); + assert_eq!(sim.sams[1].ground_rounds, 0); + sim.sams[1].kind = SamKind::S400; + sim.sams[1].missiles = 0; + sim.sams[1].ground_rounds = 4; + for _ in 0..4 { sim.step_supply(&mut Out::default()); } + assert_eq!(sim.sams[2].ground_stock, 1); + sim.unload_ground_round(1).unwrap(); + for _ in 0..4 { sim.step_supply(&mut Out::default()); } + assert_eq!((sim.sams[1].ground_rounds, sim.sams[2].ground_stock, sim.sams[0].ground_stock), (4, 0, 1)); + assert!(!sim.sams[1].ground_request); + } + + /// Runs `step_supply`, jumping the truck beside its current job each tick so handovers + /// resolve without a full drive (movement itself is covered by the lib round-trip tests). + /// Fails after 200 ticks if `done` never holds. + fn serve_until(sim: &mut Sim, truck: usize, mut done: impl FnMut(&Sim) -> bool) { + let mut ok = false; + for _ in 0..200 { + if done(sim) { ok = true; break; } + if let Some(id) = sim.sams[truck].job() + && let Some(j) = sim.sams.iter().position(|s| s.id == id) + { + sim.sams[truck].pos = sim.sams[j].pos; + } + sim.sams[truck].state = SamState::Search; + sim.tick += 1; + sim.step_supply(&mut Out::default()); + } + assert!(ok, "condition not reached within 200 step_supply calls"); + } + + /// #128: a return that is claimed but cannot proceed (here: the returning system is on the + /// move) must not stop the free truck with job=None; an independent typed request is served. + /// The claim stays, the rounds are conserved, and once the system stops the return completes. + #[test] + fn a_moving_return_claim_does_not_starve_an_independent_supply_request() { + let mut sim = Sim::new(5); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 40.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + let (depot, a, b, truck) = (0usize, 1usize, 2usize, 3usize); + let b_id = sim.sams[b].id; + sim.sams[depot].ground_stock = 2; + sim.sams[a].ground_rounds = 1; + sim.sams[a].missiles = 3; + sim.sams[a].ground_return = true; + sim.sams[b].ground_request = true; + assert!(sim.order_move(a, DVec3::new(400.0, 70.0, 0.0))); + // The free truck loads one RM48U at the depot and starts driving to B within 100 ticks; + // before the fix it entered the return branch, found the moving A impossible and idled. + let mut started = false; + for _ in 0..100 { + sim.tick += 1; + sim.step_supply(&mut Out::default()); + if sim.sams[truck].job() == Some(b_id) { started = true; break; } + } + assert!(started, "truck never started the delivery to B"); + // The claim and the rounds are untouched by the fallback. + assert!(sim.sams[a].ground_return); + assert_eq!(sim.sams[a].ground_rounds, 1); + assert_eq!((sim.sams[depot].ground_stock, sim.sams[truck].ground_stock), (1, 1)); + assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3); + // Hand the round over to B; the full drive is exercised in the lib round-trip test. + serve_until(&mut sim, truck, |s| s.sams[b].ground_rounds == 1); + assert!(!sim.sams[b].ground_request); + assert_eq!((sim.sams[b].missiles, sim.sams[truck].air_48n6_stock), (3, 1)); + assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3); + // No request is left, so the truck holds its watch (a stock run aside): the pending + // return still outranks it and the claim survives. + for _ in 0..20 { sim.tick += 1; sim.step_supply(&mut Out::default()); } + assert!(sim.sams[a].ground_return); + assert_eq!(sim.sams[a].ground_rounds, 1); + // A stops: the actual return is picked up and driven back to the depot. + sim.sams[a].state = SamState::Search; + sim.sams[a].dest = None; + serve_until(&mut sim, truck, |s| s.sams[truck].return_cargo); + assert!(!sim.sams[a].ground_return); + assert_eq!((sim.sams[a].ground_rounds, sim.sams[a].missiles), (0, 4)); + assert_eq!((sim.sams[truck].ground_stock, sim.sams[truck].air_48n6_stock), (1, 0)); + serve_until(&mut sim, truck, |s| s.sams[depot].ground_stock == 2); + assert!(!sim.sams[truck].return_cargo); + assert_eq!((sim.sams[depot].ground_stock, sim.sams[truck].ground_stock, sim.sams[b].ground_rounds), (2, 0, 1)); + assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3); + } + + /// #128 variant: the pickup is already claimed by another truck, so for this one the return + /// is impossible; it must turn to the waiting request instead of idling with job=None. A + /// dropped job (as after loading a save) is rebuilt, damaged trucks wait, and the repaired + /// claiming truck keeps the priority of the now executable return over the request. + #[test] + fn a_return_claimed_by_another_truck_leaves_the_supply_to_the_second_one() { + let mut sim = Sim::new(7); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::S400, 200.0), (SamKind::Tzm, 0.0), (SamKind::Tzm, 150.0)] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + let (depot, a, b, t2, t1) = (0usize, 1usize, 2usize, 3usize, 4usize); + let (a_id, b_id) = (sim.sams[a].id, sim.sams[b].id); + sim.sams[depot].ground_stock = 2; + sim.sams[a].ground_rounds = 1; + sim.sams[a].missiles = 3; + sim.sams[a].ground_return = true; + sim.sams[b].ground_request = true; + // The first truck claimed A's pickup earlier; both trucks start damaged: nothing + // moves, no round is lost, the claim waits. + sim.sams[t1].job = Some(a_id); + sim.sams[t1].comps.0[Part::Tracks as usize] = 0.0; + sim.sams[t2].comps.0[Part::Tracks as usize] = 0.0; + for _ in 0..30 { sim.tick += 1; sim.step_supply(&mut Out::default()); } + assert_eq!(sim.sams[t2].job(), None); + assert_eq!((sim.sams[depot].ground_stock, sim.sams[b].ground_rounds), (2, 0)); + assert!(sim.sams[a].ground_return); + assert_eq!(sim.sams[depot].ground_stock + sim.sams[t1].ground_stock + sim.sams[t2].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3); + // Repaired, the second truck must not idle on the claimed return: depot first, then B. + sim.sams[t2].comps.0[Part::Tracks as usize] = 1.0; + let mut started = false; + for _ in 0..100 { + sim.tick += 1; + sim.step_supply(&mut Out::default()); + if sim.sams[t2].job() == Some(b_id) { started = true; break; } + } + assert!(started, "second truck never started the delivery to B"); + assert_eq!((sim.sams[depot].ground_stock, sim.sams[t2].ground_stock), (1, 1)); + assert!(sim.sams[a].ground_return, "claimed return must survive the fallback"); + // A save/load drops the job; the request and the cargo survive and the job is rebuilt. + sim.sams[t2].job = None; + serve_until(&mut sim, t2, |s| s.sams[t2].job() == Some(b_id)); + // The claiming truck is repaired: the executable return has priority over the request, + // and the still-hauling second truck is not diverted from B. + sim.sams[t1].comps.0[Part::Tracks as usize] = 1.0; + serve_until(&mut sim, t1, |s| s.sams[t1].return_cargo); + assert!(!sim.sams[a].ground_return); + assert_eq!(sim.sams[a].ground_rounds, 0); + assert_eq!(sim.sams[t2].job(), Some(b_id), "hauling truck must keep its supply job"); + // B gets its round; the displaced air round rides along. + serve_until(&mut sim, t2, |s| s.sams[b].ground_rounds == 1); + assert_eq!((sim.sams[b].missiles, sim.sams[t2].air_48n6_stock), (3, 1)); + assert_eq!(sim.sams[depot].ground_stock + sim.sams[t1].ground_stock + sim.sams[t2].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3); + // And the picked-up return reaches the depot: everything is back except B's new round. + serve_until(&mut sim, t1, |s| s.sams[depot].ground_stock == 2); + assert!(!sim.sams[t1].return_cargo); + assert_eq!((sim.sams[depot].ground_stock, sim.sams[t1].ground_stock, sim.sams[b].ground_rounds), (2, 0, 1)); + } + + /// #128 variant: the route to the returning system is marked unreachable, so the return is + /// impossible for now; the truck serves the request, and the return completes once the + /// route is passable again and the system is still waiting with its claim. + #[test] + fn an_unreachable_route_falls_back_to_supply_until_it_expires() { + let mut sim = Sim::new(9); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 40.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::new(x, 70.0, 0.0), 0.0)); + } + let (depot, a, b, truck) = (0usize, 1usize, 2usize, 3usize); + let (a_id, b_id) = (sim.sams[a].id, sim.sams[b].id); + sim.sams[depot].ground_stock = 2; + sim.sams[a].ground_rounds = 1; + sim.sams[a].missiles = 3; + sim.sams[a].ground_return = true; + sim.sams[b].ground_request = true; + sim.routes.unreachable.insert((sim.sams[truck].id, a_id), u64::MAX); + let mut started = false; + for _ in 0..100 { + sim.tick += 1; + sim.step_supply(&mut Out::default()); + if sim.sams[truck].job() == Some(b_id) { started = true; break; } + } + assert!(started, "truck never started the delivery to B"); + assert!(sim.sams[a].ground_return, "claim must survive the blocked route"); + serve_until(&mut sim, truck, |s| s.sams[b].ground_rounds == 1); + // The route is passable again: the waiting claim is picked up and returned. + sim.routes.unreachable.clear(); + serve_until(&mut sim, truck, |s| s.sams[truck].return_cargo); + assert!(!sim.sams[a].ground_return); + serve_until(&mut sim, truck, |s| s.sams[depot].ground_stock == 2); + assert!(!sim.sams[truck].return_cargo); + assert_eq!(sim.sams[depot].ground_stock + sim.sams[truck].ground_stock + sim.sams[a].ground_rounds + sim.sams[b].ground_rounds, 3); + } + +} diff --git a/airdef-mod/src/sim/mod.rs b/airdef-mod/src/sim/mod.rs index 2959c26..258f9d2 100644 --- a/airdef-mod/src/sim/mod.rs +++ b/airdef-mod/src/sim/mod.rs @@ -7,8 +7,14 @@ //! (`DroneKind::spec`, `MissileKind::spec`, `SamKind::spec`) plus how its //! parts are drawn (`Sim::draw_sam`). use glam::{DQuat, DVec3}; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; +use std::sync::Arc; +pub mod battery; +pub mod launch; +pub mod catalog; +use catalog::sound; +mod generated_bounds; mod damage; pub mod doctrine; pub mod fires; @@ -19,6 +25,8 @@ pub mod record; mod sight; mod terrain; mod ew; +mod durable; +pub use ew::{AimReport, Confidence, ContactReport, EnemyPictureReport, Intent, PendingReport}; pub mod lights; mod logistics; mod orders; @@ -26,7 +34,7 @@ mod route; mod voices; pub mod outcome; mod units; -mod why; +pub mod why; pub mod world; #[cfg(test)] mod balance; @@ -69,6 +77,25 @@ pub struct Particle { pub scale: f32, } +/// Sound source description produced by the simulation and consumed by the audio adapter. +#[derive(Clone, Debug, PartialEq)] +pub struct EngineDecl { + pub key: u32, + pub event: &'static str, + pub at: DVec3, + /// Blocks per second. + pub vel: DVec3, + pub gain: f32, + pub pitch: f32, + pub damaged: bool, +} + +/// Shared distance text used by existing diagnostics and the HUD. +/// Moving diagnostic text to the presentation adapter is a separate migration. +pub fn distance(m: f64) -> String { + if m >= 1000.0 { format!("{:.1} км", m / 1000.0) } else { format!("{:.0} м", m) } +} + /// A blast the post-processing shader draws as a fireball, flash and shock. #[derive(Clone, Copy)] pub struct Blast { @@ -89,7 +116,9 @@ pub struct Out { pub sounds: Vec<(&'static str, DVec3, f32, f32)>, /// The engines sounding this tick (persistent sources, `crate::engines`), and whether the /// picture cut (a replay seek or a new shot): every engine then starts afresh. - pub engines: Vec, + pub engines: Vec, + /// Sources explicitly stopped this tick (for example, motor burnout). + pub engine_stops: Vec, pub engine_cut: bool, pub commands: Vec, /// Camera jolt (strength, seconds), the strongest this tick. @@ -121,7 +150,7 @@ impl Out { } /// An engine that sounds: `vel` is the simulation's, blocks a tick. pub fn engine(&mut self, key: u32, event: &'static str, at: DVec3, vel: DVec3, gain: f32, pitch: f32, damaged: bool) { - self.engines.push(crate::engines::Decl { key, event, at, vel: vel * 20.0, gain, pitch, damaged }); + self.engines.push(EngineDecl { key, event, at, vel: vel * 20.0, gain, pitch, damaged }); } fn jolt(&mut self, strength: f32, seconds: f32) { if self.shake.is_none_or(|(s, _)| s < strength) { @@ -484,7 +513,13 @@ impl Ground { /// Explosions change the land: forget what was known round them. fn forget(&mut self, at: DVec3, radius: i32) { let (cx, cz) = (at.x.floor() as i32, at.z.floor() as i32); - self.known.retain(|&(x, z), _| (x - cx).abs() > radius || (z - cz).abs() > radius); + // A blast affects a small square. Removing those columns directly avoids scanning the + // entire height cache once per explosion (and again when its confirmation times out). + for x in cx.saturating_sub(radius)..=cx.saturating_add(radius) { + for z in cz.saturating_sub(radius)..=cz.saturating_add(radius) { + self.known.remove(&(x, z)); + } + } self.world.touched(at, f64::from(radius) + 2.0); } } @@ -560,9 +595,6 @@ pub fn roles_text(mask: u8) -> String { Class::ALL.iter().filter(|c| mask & c.bit() != 0).map(|c| c.letter()).collect::>().join("") } -/// Ballistic flight's gravity, blocks a tick². -const GRAVITY: f64 = 0.05; - #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum DroneKind { Shahed, @@ -613,14 +645,21 @@ pub struct DroneSpec { pub decoy: bool, /// How hard it is to hit (1 normal). stealth: f64, + /// Its radar cross-section (a Shahed's 0.3). + rcs: f64, /// Its sound: event, pitch, every n ticks. sound: (&'static str, f32, u32), pub class: Class, /// Ballistic: launched this far away on a raid, flying an arc. pub ballistic: Option, + /// The sound of its launch from the ground. + launch_sound: &'static str, } impl DroneSpec { + /// The registered visual model for this profile. + pub fn model(&self) -> &'static str { self.model } + /// Its size, for how thick its smoke is. pub fn trail_scale(&self) -> f32 { self.scale as f32 @@ -645,280 +684,17 @@ impl DroneKind { ]; /// The sound of a launch from the ground: a light catapult shot, a rocket booster, a heavy ballistic launch. pub fn launch_sound(self) -> &'static str { - match self { - DroneKind::Shahed | DroneKind::Geran3 | DroneKind::Lancet | DroneKind::Gerbera => "airdef:launch.s400_eject", - DroneKind::Kh101 | DroneKind::Kalibr | DroneKind::Kh22 => "airdef:launch.patriot", - DroneKind::Zircon => "airdef:launch.s400", - DroneKind::Iskander | DroneKind::Kinzhal | DroneKind::Oreshnik | DroneKind::OreshnikBlock | DroneKind::OreshnikSub => { - "airdef:launch.s500" - } - } - } - pub fn spec(self) -> &'static DroneSpec { - match self { - DroneKind::Shahed => &DroneSpec { - name: "Шахед-136", - key: "shahed", - model: "airdef:shahed", - speed: 0.6, - turn: 0.035, - cruise: 45.0, - clearance: 18.0, - dive: 32.0, - warhead: 3, - scale: 1.3, - engine: Engine::Prop(1.34, 1.0), - decoy: false, - stealth: 1.0, - sound: ("airdef:drone.shahed", 1.0, 48), - class: Class::Drone, - ballistic: None, - }, - DroneKind::Geran3 => &DroneSpec { - name: "Герань-3", - key: "geran3", - model: "airdef:geran3", - speed: 1.6, - turn: 0.03, - cruise: 60.0, - clearance: 18.0, - dive: 50.0, - warhead: 2, - scale: 1.15, - engine: Engine::Jet(0.34, 1.55, 1.0), - decoy: false, - stealth: 1.1, - sound: ("airdef:drone.geran3", 1.0, 40), - class: Class::Jet, - ballistic: None, - }, - DroneKind::Lancet => &DroneSpec { - name: "Ланцет-3", - key: "lancet", - model: "airdef:lancet", - speed: 1.1, - turn: 0.07, - cruise: 25.0, - clearance: 10.0, - dive: 38.0, - warhead: 1, - scale: 0.9, - engine: Engine::Prop(0.93, 0.55), - decoy: false, - stealth: 1.4, - sound: ("airdef:drone.lancet", 1.0, 32), - class: Class::Drone, - ballistic: None, - }, - DroneKind::Gerbera => &DroneSpec { - name: "Гербера", - key: "gerbera", - model: "airdef:gerbera", - speed: 0.5, - turn: 0.04, - cruise: 40.0, - clearance: 15.0, - dive: 30.0, - warhead: 0, - scale: 1.1, - engine: Engine::Prop(1.03, 0.9), - decoy: true, - stealth: 0.9, - sound: ("airdef:drone.shahed", 1.3, 40), - class: Class::Drone, - ballistic: None, - }, - DroneKind::Kh101 => &DroneSpec { - name: "Х-101", - key: "kh101", - model: "airdef:kh101", - speed: 2.4, - turn: 0.025, - cruise: 0.0, - clearance: 12.0, - dive: 26.0, - warhead: 6, - scale: 1.6, - engine: Engine::Jet(-0.27, 1.4, 0.6), - decoy: false, - stealth: 1.5, - sound: ("airdef:drone.cruise", 1.0, 50), - class: Class::Cruise, - ballistic: None, - }, - DroneKind::Kalibr => &DroneSpec { - name: "Калибр", - key: "kalibr", - model: "airdef:kalibr", - speed: 2.1, - turn: 0.025, - cruise: 0.0, - clearance: 15.0, - dive: 26.0, - warhead: 5, - scale: 1.5, - engine: Engine::Jet(-0.2, 1.5, 0.6), - decoy: false, - stealth: 1.3, - sound: ("airdef:drone.cruise", 1.2, 50), - class: Class::Cruise, - ballistic: None, - }, - DroneKind::Kh22 => &DroneSpec { - name: "Х-22", - key: "kh22", - model: "airdef:kh22", - speed: 4.0, - turn: 0.03, - cruise: 140.0, - clearance: 40.0, - dive: 150.0, - warhead: 7, - scale: 1.8, - engine: Engine::Rocket(1.5, 1.3), - decoy: false, - stealth: 1.4, - sound: ("airdef:drone.rocket", 0.8, 46), - class: Class::Supersonic, - ballistic: None, - }, - DroneKind::Zircon => &DroneSpec { - name: "Циркон", - key: "zircon", - model: "airdef:zircon", - speed: 5.5, - turn: 0.025, - cruise: 90.0, - clearance: 30.0, - dive: 110.0, - warhead: 6, - scale: 1.4, - engine: Engine::Rocket(1.5, 1.1), - decoy: false, - stealth: 2.0, - sound: ("airdef:drone.rocket", 1.35, 46), - class: Class::Supersonic, - ballistic: None, - }, - DroneKind::Iskander => &DroneSpec { - name: "Искандер-М", - key: "iskander", - model: "airdef:iskander", - speed: 5.0, - turn: 0.04, - cruise: 0.0, - clearance: 0.0, - dive: 0.0, - warhead: 8, - scale: 1.8, - engine: Engine::Rocket(1.5, 1.6), - decoy: false, - stealth: 1.6, - sound: ("airdef:drone.rocket", 0.7, 46), - class: Class::Ballistic, - ballistic: Some(1000.0), - }, - DroneKind::Kinzhal => &DroneSpec { - name: "Кинжал", - key: "kinzhal", - model: "airdef:kinzhal", - speed: 7.5, - turn: 0.03, - cruise: 0.0, - clearance: 0.0, - dive: 0.0, - warhead: 8, - scale: 1.7, - engine: Engine::Rocket(1.5, 1.5), - decoy: false, - stealth: 2.2, - sound: ("airdef:drone.rocket", 0.6, 46), - class: Class::Ballistic, - ballistic: Some(1500.0), - }, - DroneKind::Oreshnik => &DroneSpec { - name: "Орешник", - key: "oreshnik", - model: "airdef:iskander", - speed: 9.0, - turn: 0.0, - cruise: 0.0, - clearance: 0.0, - dive: 0.0, - warhead: 0, - scale: 2.6, - engine: Engine::Rocket(1.5, 2.4), - decoy: false, - stealth: 3.0, - sound: ("airdef:drone.rocket", 0.5, 46), - class: Class::Ballistic, - ballistic: Some(2600.0), - }, - DroneKind::OreshnikBlock => &DroneSpec { - name: "Орешник: блок", - key: "oreshnik_block", - model: "airdef:reentry", - speed: 9.0, - turn: 0.0, - cruise: 0.0, - clearance: 0.0, - dive: 0.0, - warhead: 4, - scale: 1.6, - engine: Engine::Rocket(1.0, 1.0), - decoy: false, - stealth: 3.5, - sound: ("airdef:drone.ballistic", 0.8, 9999), - class: Class::Ballistic, - ballistic: None, - }, - DroneKind::OreshnikSub => &DroneSpec { - name: "Орешник: суббоеприпас", - key: "oreshnik_sub", - model: "airdef:reentry", - speed: 9.0, - turn: 0.05, - cruise: 0.0, - clearance: 0.0, - dive: 0.0, - warhead: 3, - scale: 1.1, - engine: Engine::Rocket(1.0, 1.0), - decoy: false, - stealth: 4.0, - sound: ("airdef:drone.ballistic", 1.0, 9999), - class: Class::Ballistic, - ballistic: None, - }, - } + self.spec().launch_sound } pub fn from_key(key: &str) -> Option { Self::ALL.into_iter().find(|k| k.spec().key == key) } /// Its radar cross-section, a Shahed's 0.3: radars see it from as far as its fourth root allows. pub fn rcs(self) -> f64 { - match self { - DroneKind::Shahed => 0.3, - DroneKind::Geran3 => 0.4, - DroneKind::Lancet => 0.08, - // Corner reflectors make the decoy look as big as a Shahed. - DroneKind::Gerbera => 0.4, - DroneKind::Kh101 => 0.05, - DroneKind::Kalibr => 0.15, - DroneKind::Kh22 => 1.0, - DroneKind::Zircon => 0.4, - DroneKind::Iskander => 0.6, - DroneKind::Kinzhal => 0.5, - DroneKind::Oreshnik => 2.0, - DroneKind::OreshnikBlock => 0.4, - DroneKind::OreshnikSub => 0.08, - } + self.spec().rcs } } -/// Height over the target where the Oreshnik's blocks release their submunitions. -const BLOCK_SPLIT: f64 = 300.0; - #[derive(PartialEq, Clone, Copy)] enum Flight { Cruise, @@ -957,6 +733,13 @@ pub struct Drone { /// Its navigation error (#65): `target` is the nominal aim plus this, kept apart so a new aim (a retarget) /// keeps the error this airframe was born with instead of becoming exact. err: DVec3, + /// Why it is aimed where it is (#127): the structured intent a smart drone carries — the plan's + /// objective, a SEAD reaction to a disclosure, or a replacement after the picture killed the aim. + /// Kept apart from any random re-pick, reported by the diagnostics, and saved with the drone. + intent: ew::Intent, + /// What it aims at and how firmly (#127): the unit, when the aim was chosen and how often it has + /// changed. The switch throttle and the priority of its route job read this. + aim: ew::Aim, /// Ticks held at the edge of blocks the game has not told of (#61); reset by a clear step. hold: u16, /// Its progress towards the aim: a flight that stops getting nearer runs out of fuel (see `Pace`). @@ -1017,6 +800,17 @@ impl Drone { pub fn alive(&self) -> bool { self.flight != Flight::Falling } + /// Why it is aimed where it is (#127), and how often that aim has been changed. + pub fn intent(&self) -> ew::Intent { + self.intent + } + pub fn aim_switches(&self) -> u16 { + self.aim.switches + } + /// Sent by the smart enemy: it may be turned on what gives itself away. + pub fn smart(&self) -> bool { + self.smart + } /// How much a system wants to shoot it: fast and heavy first, decoys look real. fn threat(&self, from: DVec3) -> f64 { self.threat_at(from, self.pos) @@ -1031,7 +825,7 @@ impl Drone { // ---------------------------------------------------------------- interceptors -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum MissileKind { Pac3, M57e6, @@ -1050,10 +844,15 @@ struct MissileSpec { speed: f64, accel: f64, turn: f64, - /// Ticks of straight flight before guidance. + /// Departure: ticks from ignition to full guidance (spool-up; a cold launch also captures + /// its course); full guidance starts at `cold + boost`. boost: u32, - /// Thrown out of the tube by gas first; the motor lights this many ticks later. + /// Thrown out of the tube by gas first; the motor lights this many ticks later (#129). cold: u32, + /// Gas-throw speed out of the tube, blocks a tick (cold-launched kinds only). + eject: f64, + /// The ejection's linear drag, blocks a tick² (cold-launched kinds only). + eject_drag: f64, pk: f64, fuse: f64, life: u32, @@ -1061,6 +860,37 @@ struct MissileSpec { flame: f64, /// The nozzle, model units behind the centre (z). rear: f64, + /// How far its own seeker holds a target (blocks); 0: command guidance only. + seeker: f64, + /// The sound of its launch. + launch_sound: &'static str, + /// Its motor's voice: event, gain and pitch. + voice: (&'static str, f32, f32), +} + +impl MissileSpec { + /// The launch choreography is one sequence of stages (#129): the cap opens on the ground + /// (`launch::OPEN_TICKS`), the round is thrown out of the tube unlit and slows under drag + /// (`age <= cold`), the motor lights where it has reached (`age == cold + 1`), it tips onto + /// its course with a growing rate while the motor spools up (`cold < age < cold + boost`), + /// and full guidance — terrain lofting, the seeker, full turn — runs from `age >= cold + boost`. + /// Rail-launched kinds (`cold == 0`) light at once but spool up over the same `boost` ticks: + /// their stages are ignition, departure, guidance. + fn guided(&self, age: u32) -> bool { + age >= self.cold + self.boost + } + /// The departure's course-capture rate as a share of the full turn (0..=1). + fn departure_ramp(&self, age: u32) -> f64 { + (age.saturating_sub(self.cold) as f64 / self.boost.max(1) as f64).clamp(0.0, 1.0) + } + /// The motor's spool-up as a share of the full acceleration (0..=1). A rail launch spools over + /// the same `boost` ticks its departure lasts; an electric kind has no rocket motor to light. + fn spool(&self, age: u32, guided: bool) -> f64 { + if guided || self.flame <= 0.0 { + return 1.0; + } + 0.45 + 0.55 * self.departure_ramp(age) + } } impl MissileKind { @@ -1071,149 +901,12 @@ impl MissileKind { } /// The sound of its launch. pub fn launch_sound(self) -> &'static str { - match self { - MissileKind::Pac3 => "airdef:launch.patriot", - MissileKind::M57e6 => "airdef:launch.pantsir", - MissileKind::M48n6 => "airdef:launch.s400_eject", - MissileKind::M77n6 => "airdef:launch.s500_eject", - MissileKind::IrisT | MissileKind::M9m338 => "airdef:launch.s400_eject", - MissileKind::Amraam | MissileKind::M9m317 => "airdef:launch.patriot", - MissileKind::P1sun => "airdef:drone.lancet", - } - } - fn spec(self) -> &'static MissileSpec { - match self { - MissileKind::Pac3 => &MissileSpec { - model: "airdef:pac3", - speed: 5.5, - accel: 0.35, - turn: 0.3, - boost: 8, - cold: 0, - pk: 0.9, - fuse: 4.0, - life: 240, - scale: 1.0, - flame: 1.0, - rear: 28.0, - }, - MissileKind::M57e6 => &MissileSpec { - model: "airdef:m57e6", - speed: 5.0, - accel: 0.5, - turn: 0.32, - boost: 5, - cold: 0, - pk: 0.82, - fuse: 3.0, - life: 160, - scale: 0.85, - flame: 0.65, - rear: 28.0, - }, - MissileKind::M48n6 => &MissileSpec { - model: "airdef:m48n6", - speed: 7.0, - accel: 0.5, - turn: 0.22, - boost: 8, - cold: 5, - pk: 0.9, - fuse: 5.0, - life: 300, - scale: 1.5, - flame: 1.4, - rear: 30.0, - }, - MissileKind::M77n6 => &MissileSpec { - model: "airdef:m77n6", - speed: 9.0, - accel: 0.6, - turn: 0.22, - boost: 8, - cold: 5, - pk: 0.95, - fuse: 6.0, - life: 320, - scale: 1.7, - flame: 1.6, - rear: 32.0, - }, - MissileKind::IrisT => &MissileSpec { - model: "airdef:irist_missile", - speed: 7.0, - accel: 0.5, - turn: 0.35, - boost: 6, - cold: 4, - pk: 0.9, - fuse: 4.0, - life: 260, - scale: 0.95, - flame: 0.9, - rear: 28.0, - }, - MissileKind::Amraam => &MissileSpec { - model: "airdef:amraam_missile", - speed: 6.0, - accel: 0.45, - turn: 0.3, - boost: 6, - cold: 0, - pk: 0.85, - fuse: 4.0, - life: 260, - scale: 0.9, - flame: 0.9, - rear: 28.0, - }, - MissileKind::M9m317 => &MissileSpec { - model: "airdef:m48n6", - speed: 6.5, - accel: 0.5, - turn: 0.25, - boost: 8, - cold: 0, - pk: 0.85, - fuse: 5.0, - life: 300, - scale: 1.1, - flame: 1.2, - rear: 32.0, - }, - MissileKind::M9m338 => &MissileSpec { - model: "airdef:m57e6", - speed: 5.5, - accel: 0.6, - turn: 0.4, - boost: 4, - cold: 4, - pk: 0.9, - fuse: 3.0, - life: 160, - scale: 1.0, - flame: 0.8, - rear: 28.0, - }, - MissileKind::P1sun => &MissileSpec { - model: "airdef:p1sun", - speed: 2.8, - accel: 0.12, - turn: 0.3, - boost: 4, - cold: 0, - pk: 0.8, - fuse: 3.0, - life: 900, - scale: 0.8, - // Electric: no flame. - flame: 0.0, - rear: 10.0, - }, - } + self.spec().launch_sound } } +include!("specs_generated.rs"); + pub struct Missile { pub id: u32, pub kind: MissileKind, @@ -1254,18 +947,26 @@ pub const MAX_COAST: u64 = 240; /// Ticks the seeker keeps a target it lost sight of (it looks again round the last place). const REACQUIRE: u64 = 12; +impl MissileKind { + /// Fictional game motor duration in ticks, independent of the flight lifetime. + pub fn burn_ticks(self) -> u32 { + let spec = self.spec(); + if self == MissileKind::P1sun { spec.life } else { spec.life * 2 / 3 } + } +} + +impl Missile { + /// Cold ejection and post-burn coast have no powered exhaust. + pub fn engine_lit(&self) -> bool { + let spec = self.kind.spec(); + self.age > spec.cold && self.age <= spec.cold + self.kind.burn_ticks() + } +} + impl MissileKind { /// How far its own seeker holds a target of average size (blocks); 0: command guidance only. pub fn seeker(self) -> f64 { - match self { - MissileKind::M57e6 => 0.0, - MissileKind::Pac3 => 110.0, - MissileKind::M48n6 | MissileKind::M77n6 => 120.0, - MissileKind::IrisT | MissileKind::M9m317 => 90.0, - MissileKind::Amraam => 130.0, - MissileKind::M9m338 => 70.0, - MissileKind::P1sun => 60.0, - } + self.spec().seeker } } @@ -1331,6 +1032,14 @@ pub struct Sam { launched: u32, pub id: u32, pub kind: SamKind, + pub mode: battery::SamMode, + pub radar_role: battery::RadarRole, + /// Player-controlled active radar emission; independent of component health and data link. + pub radar_on: bool, + pub spent_cells: u8, + preparing: Option, + cap_cell: u8, + cap_open: f32, /// The vehicle's centre on the ground. pub pos: DVec3, /// Where its nose points (radians, as `look`'s yaw). @@ -1341,6 +1050,15 @@ pub struct Sam { yaw_vel: f64, pitch_vel: f64, pub missiles: u32, + /// Explicit RM48U game rounds loaded in shared launcher cells. + pub ground_rounds: u32, + /// Pending player request for one delivered ground round. + pub ground_request: bool, + pub ground_return: bool, + pub return_cargo: bool, + /// Separate typed rounds and returned S-400 air rounds held by a depot. + pub ground_stock: u32, + pub air_48n6_stock: u32, pub bursts: u32, cooldown: u32, gun_cooldown: u32, @@ -1412,6 +1130,13 @@ impl Sam { Self { id, kind, + mode: battery::SamMode::Integrated, + radar_role: battery::RadarRole::Multifunction, + radar_on: true, + spent_cells: 0, + preparing: None, + cap_cell: 0, + cap_open: 0.0, pos, heading, yaw: heading, @@ -1419,6 +1144,12 @@ impl Sam { yaw_vel: 0.0, pitch_vel: 0.0, missiles: spec.missile.as_ref().map_or(0, |m| m.magazine), + ground_rounds: 0, + ground_request: false, + ground_return: false, + return_cargo: false, + ground_stock: 0, + air_48n6_stock: 0, bursts: spec.gun.as_ref().map_or(0, |g| g.bursts), cooldown: 40, gun_cooldown: 0, @@ -1499,8 +1230,8 @@ impl Sam { return text + " " + &self.state.name().to_lowercase(); } match self.kind { - SamKind::Depot => return format!("{text}: запас {} перезарядок", self.cargo), - SamKind::Tzm => return format!("{text}: на борту {}/2{}", self.cargo, if self.job.is_some() { ", в пути" } else { "" }), + SamKind::Depot => return format!("{text}: {} перезарядок, RM48U {}, снятых ПВО {}", self.cargo, self.ground_stock, self.air_48n6_stock), + SamKind::Tzm => return format!("{text}: на борту {}/2, RM48U {}, снятых ПВО {}{}", self.cargo, self.ground_stock, self.air_48n6_stock, if self.return_cargo { ", везёт RM48U на склад" } else if self.job.is_some() { ", в пути" } else { "" }), SamKind::Ew => { let state = if self.ew_on { "ГЛУШИТ" } else if self.ew_lock { "остывает" } else { "готова" }; return format!("{text}: {state}, нагрев {:.0}%", self.heat * 100.0); @@ -1512,6 +1243,9 @@ impl Sam { SamKind::Light => return format!("{text}{}", if self.target.is_some() { ": в луче цель" } else { "" }), _ => {} } + if let Some(p) = self.preparing { + text += &format!(" · открытие ТПК №{}", p.cell + 1); + } if self.empty && self.missiles == 0 { text += " ЖДЁТ БК"; } @@ -1525,10 +1259,15 @@ impl Sam { text += &format!(" перезарядка {}с", self.reloading.div_ceil(20)); } else { if let Some(m) = &spec.missile { - text += &format!(" ракеты {}/{}", self.missiles, m.magazine); + if self.kind == SamKind::S400 { + text += &format!(" ПВО {} · RM48U {} / {} ячеек", self.missiles, self.ground_rounds, self.cells()); + if self.ground_return { text += " · ожидает вывоза RM48U"; } + } else { + text += &format!(" ракеты {}/{}", self.missiles, m.magazine); + } } if spec.gun.is_some() { - text += &format!(" снаряды {}", self.bursts); + text += &format!(" очереди {}", self.bursts); } } text @@ -1614,6 +1353,15 @@ pub struct Sim { pub strikes: strike::Strikes, /// A raid being planned. plan: Option, + /// Routes still to work out after a mass retarget (#127): the drones were given a new aim, but the + /// planner's share of a tick ran out. They keep their old route until a new one is ready. + retargets: VecDeque, + /// The known land of the last smart raid's plan: its grid and cell heights (NaN unknown). New retarget + /// jobs take this `Arc` as their version; a later plan replaces the pointer, not a pending job's map (#127). + known_relief: Option>, + /// The next route job's id (#127). A job keeps its id when it is replaced in place; the save writes + /// the counter so restored jobs keep theirs. + job_next: u32, /// The vehicles' ground routes, and the jobs the trucks found out of reach (#94). routes: route::Routes, /// Each marked object's health, 0 to 100 (`targets`' order). @@ -1693,6 +1441,9 @@ impl Sim { sites: sites::Sites::default(), strikes: strike::Strikes::new(), plan: None, + retargets: VecDeque::new(), + known_relief: None, + job_next: 1, routes: Default::default(), target_health: Vec::new(), wave: None, @@ -1793,6 +1544,8 @@ impl Sim { } pub fn clear(&mut self) { + self.strikes.pending.clear(); + for sam in &mut self.sams { sam.cancel_prepare(); } self.drones.clear(); self.missiles.clear(); self.fx.clear(); @@ -1800,6 +1553,8 @@ impl Sim { self.queue.clear(); self.sites.clear(); self.plan = None; + self.retargets.clear(); + self.known_relief = None; self.wave = None; // Ammunition still to cook off, flares in the sky. self.later.clear(); @@ -1845,6 +1600,7 @@ impl Sim { self.adapt(); self.step_settling(ground); self.release_queue(ground, out); + self.begin_recording_if_active(); self.step_drones(ground, player, out); self.step_later(ground, player, out); self.step_moves(ground, out); @@ -1852,6 +1608,7 @@ impl Sim { self.step_ew(out); self.update_sight(ground); self.step_lights(ground, out); + self.prepare_ground_strikes(out); self.step_sams(ground, out); self.step_missiles(ground, out); self.step_strikes(ground, player, out); @@ -1983,13 +1740,13 @@ impl Sim { let v = (aim - d.pos).normalize_or(DVec3::NEG_Y) * 3.0; released.push((piece, d.pos + v.normalize_or_zero() * (i as f64 * 0.3), v, aim, d.sector)); } - out.sound("airdef:boom.sonic", d.pos, 1.0, 0.7); + out.sound(sound::BOOM_SONIC, d.pos, 1.0, 0.7); splits.push((d.id, d.pos)); // Its pieces count as launched instead of it. self.launched += 5; out.particles("minecraft:firework", d.pos, 1.5, DVec3::ZERO, 0.3, 40); if d.kind == DroneKind::OreshnikBlock && self.rng.chance(0.5) { - out.sound("airdef:drone.ballistic", d.pos, 1.0, 1.1); + out.sound(sound::DRONE_BALLISTIC, d.pos, 1.0, 1.1); } removed.push(d.id); continue; @@ -2014,8 +1771,8 @@ impl Sim { if d.vel.y < -1.0 && d.spin == 0.0 && d.pos.distance(d.target) < d.vel.length() * 80.0 { d.spin = 1.0; let p = if d.kind == DroneKind::Kinzhal { 0.85 } else { 1.0 }; - out.sound("airdef:drone.ballistic", d.pos, 1.0, p); - out.sound("airdef:boom.sonic", d.pos, 1.0, p); + out.sound(sound::DRONE_BALLISTIC, d.pos, 1.0, p); + out.sound(sound::BOOM_SONIC, d.pos, 1.0, p); } continue; } @@ -2186,7 +1943,7 @@ impl Sim { } } for (drone, pos) in splits { - self.log(pos, record::EventKind::Split { drone }); + self.emit_combat_event(pos, record::EventKind::Split { drone }); } for (drone, kind, pos, tnt) in crashes { self.crashes += 1; @@ -2197,9 +1954,9 @@ impl Sim { 6 | 7 => 3, _ => 4, }; - self.log(pos, record::EventKind::Crash { drone, kind, power }); + self.emit_combat_event(pos, record::EventKind::Crash { drone, kind, power }); } - for (drone, kind, pos, tnt) in impacts { + for &(drone, kind, pos, tnt) in &impacts { if let Some(sector) = self.drones.iter().find(|x| x.id == drone).map(|x| x.sector as usize) { self.learn(sector, false); } @@ -2212,10 +1969,14 @@ impl Sim { }; let object = self.targets.iter().position(|t| t.distance(pos) < 16.0); let sam = self.sams.iter().enumerate().find(|(_, s)| s.alive() && s.pos.distance(pos) < 12.0).map(|(i, s)| (s.kind, i)); - self.log(pos, record::EventKind::Hit { drone, kind, power, object, sam }); + self.emit_combat_event(pos, record::EventKind::Hit { drone, kind, power, object, sam }); let vel = self.drones.iter().find(|x| x.id == drone).map_or(DVec3::ZERO, |x| x.vel); self.direct_hit(pos, vel, tnt, out); } + // Battle damage assessment: a munition going down watches the fate of whatever it was + // sent at. Truth is read at this instant only — the one honest moment — and written + // into the picture as an observation; a unit missed by everything keeps its secret. + self.observe_outcomes(&impacts); for (pos, warhead, small) in boom { self.explode(pos, warhead, small, ground, player, out); } @@ -2240,6 +2001,8 @@ impl Sim { smart: false, nav: Nav::default(), err: DVec3::ZERO, + intent: ew::Intent::Plan, + aim: ew::Aim::point(0), hold: 0, pace: Pace::default(), }); @@ -2287,6 +2050,18 @@ impl Sim { } } + /// What the enemy's munitions just watched (#127): for every impact, the fate of each known + /// unit near it — dead or alive as the explosion showed, at the place it showed. Units the + /// blasts missed keep their contacts exactly as they were: unobserved is unobserved. + fn observe_outcomes(&mut self, impacts: &[(u32, DroneKind, DVec3, u32)]) { + let now = self.tick; + for &(_, _, pos, _) in impacts { + for s in self.sams.iter().filter(|s| s.pos.distance(pos) < 16.0) { + self.intel.outcome(s.id, s.alive(), s.pos, now); + } + } + } + /// A blast: a fireball, smoke, sound, a jolt for a near player, and (with /// `damage`) primed TNT for the land. fn explode(&mut self, pos: DVec3, tnt: u32, small: bool, ground: &mut Ground, player: Option, out: &mut Out) { @@ -2309,7 +2084,7 @@ impl Sim { out.particles("minecraft:lava", pos, 1.0, DVec3::ZERO, 0.0, 16); out.particles("minecraft:campfire_signal_smoke", pos, 1.2, DVec3::new(0.0, 0.04, 0.0), 0.01, if big { 30 } else { 10 }); let pitch = 0.9 + self.vis.f() as f32 * 0.2; - let event = ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][power]; + let event = [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][power]; out.sound(event, pos, 1.0, if small { pitch * 1.15 } else { pitch }); if power >= 3 { // A heavy warhead throws a column of dirt and a ring of dust. @@ -2324,7 +2099,7 @@ impl Sim { out.jolt((1.0 - d / reach) as f32 * strength, [1.0, 0.8, 1.6, 2.2, 3.0][power]); } } - self.blast_damage(pos, power, out); + self.blast_damage(pos, power, ground, out); if self.damage && tnt > 0 { for i in 0..tnt { out.commands.push(format!( @@ -2354,17 +2129,17 @@ impl Sim { } /// A drone hit by the guns of system `by`. - fn shoot_down(&mut self, idx: usize, by: u32, missile: Option, out: &mut Out) -> Option { + fn shoot_down(&mut self, idx: usize, by: u32, missile: Option, ground: &Ground, out: &mut Out) -> Option { let kind = self.sams.iter().find(|s| s.id == by).map_or(SamKind::Gepard, |s| s.kind); let shell = kind.spec().gun.as_ref().map_or(5.0, |g| g.shell); let at = self.drones[idx].pos; - self.shoot_down_by(idx, by, missile, Impact::gun(kind, at, shell), out) + self.shoot_down_by(idx, by, missile, Impact::gun(kind, at, shell), ground, out) } /// A drone brought down by `imp` from system `by`: how it ends is rolled once (combat randomness, so a /// detonation really means no ground impact), recorded with the kill, and drawn from the visual randomness /// only. The kill is counted here and nowhere else. - fn shoot_down_by(&mut self, idx: usize, by: u32, missile: Option, imp: Impact, out: &mut Out) -> Option { + fn shoot_down_by(&mut self, idx: usize, by: u32, missile: Option, imp: Impact, ground: &Ground, out: &mut Out) -> Option { if !self.drones[idx].alive() { return None; } @@ -2383,7 +2158,7 @@ impl Sim { if let Some((i, s)) = self.sams.iter().enumerate().find(|(_, s)| s.id == by) { let (drone, kind, pos) = (self.drones[idx].id, dkind, self.drones[idx].pos); let kill = record::EventKind::Kill { drone, kind, sam: by, sam_kind: s.kind, sam_index: i, missile, outcome }; - self.log(pos, kill); + self.emit_combat_event(pos, kill); } let d = &mut self.drones[idx]; let before = d.vel; @@ -2426,8 +2201,8 @@ impl Sim { out.particles("minecraft:large_smoke", pos, size * 0.3, DVec3::new(0.0, 0.05, 0.0), 0.07, if self.shader { 20 } else { 40 }); out.particles("minecraft:firework", pos, 0.6, DVec3::ZERO, 0.5, 50); // The shock arrives after the flash (`boom` sounds travel); the warhead's blast hurts what is near. - out.sound("airdef:boom.sonic", pos, 0.8, 1.2); - self.blast_damage(pos, 0, out); + out.sound(sound::BOOM_SONIC, pos, 0.8, 1.2); + self.blast_damage(pos, 0, ground, out); } } out.sound(sound, pos, gain, pitch); @@ -2452,6 +2227,13 @@ impl Sim { // ------------------------------------------------ air defence fn step_sams(&mut self, ground: &mut Ground, out: &mut Out) { + for sam in &mut self.sams { + sam.sync_tube_cells(); + if (self.hold && !sam.preparing.is_some_and(|p| p.ground)) || !sam.alive() || sam.moving() || sam.reloading > 0 || sam.part(Part::Launcher) <= 0.0 { + sam.cancel_prepare(); + } + } + if self.hold { for i in 0..self.sams.len() { if self.sams[i].alive() && !self.sams[i].moving() { @@ -2499,7 +2281,7 @@ impl Sim { if sam.reloading > 0 { sam.reloading -= 1; if sam.reloading == 0 { - sam.missiles = sam.cells(); + sam.missiles = sam.cells().saturating_sub(sam.ground_rounds); sam.bursts = spec.gun.as_ref().map_or(0, |g| g.bursts); out.sound("minecraft:block.piston.extend", sam.pos, 2.0, 0.6); } @@ -2536,10 +2318,10 @@ impl Sim { let opts = why::Opts { last_chance: self.last_chance, far_shots: self.far_shots }; let target = spec.missile.as_ref().map(|load| { // A launcher with nothing to fire (reloading, empty, failed) claims no target: others take it. - if me.missiles == 0 || me.reloading > 0 || me.part(Part::Launcher) <= 0.0 { + if me.missiles == 0 || me.reloading > 0 || me.part(Part::Launcher) <= 0.0 || me.preparing.is_some_and(|p| p.ground) { return None; } - let radars: Vec<&Sam> = if me.kind.far_shooter() && opts.far_shots { self.sams.iter().filter(|s| s.kind == SamKind::Radar).collect() } else { Vec::new() }; + let radars: Vec<&Sam> = if me.mode == battery::SamMode::LauncherOnly || (me.kind.far_shooter() && opts.far_shots) { self.sams.iter().filter(|s| s.kind == SamKind::Radar).collect() } else { Vec::new() }; let cued = |id: u32| !radars.is_empty() && why::radar_cue(me, &radars, id, now); let basis = |tr: &Track, k: f64, role_in_reach: bool| { drone(tr).filter(|_| open(tr.id)).and_then(|d| why::basis(me, load, tr, d, now, claimed(d), k, opts, cued(tr.id), role_in_reach)) @@ -2604,6 +2386,10 @@ impl Sim { self.sams[i].gun_target = gun_target; } let sam = &mut self.sams[i]; + if sam.preparing.is_some_and(|p| !p.ground && Some(p.target) != sam.target) { sam.cancel_prepare(); } + if let Some(p) = sam.preparing.filter(|p| !p.ground) { + sam.cap_open = (now.saturating_sub(p.start) as f32 / launch::OPEN_TICKS as f32).clamp(0.0, 1.0); + } // What the turret or launcher points at: guns first when a target is close. let aim = sam .gun_target @@ -2615,7 +2401,7 @@ impl Sim { let Some(aim) = aim else { // Out of missiles or of shells, and nothing to shoot: reload now (both, as a // combined system's crew would), not only once both are spent. - let spent = (spec.missile.is_some() && sam.missiles == 0 && sam.part(Part::Launcher) > 0.0) || (spec.gun.is_some() && sam.bursts == 0); + let spent = (spec.missile.is_some() && sam.missiles == 0 && sam.part(Part::Launcher) > 0.0 && sam.cells() > sam.ground_rounds) || (spec.gun.is_some() && sam.bursts == 0); if spent && sam.reloading == 0 && !sam.empty { if logistics { sam.empty = true; @@ -2725,11 +2511,14 @@ impl Sim { } // A gun sound holds a few shots: one every other burst. if sam.bursts % 2 == 0 { - let event = if sam.kind == SamKind::Pantsir { "airdef:gun.pantsir" } else { "airdef:gun.gepard" }; + let event = if sam.kind == SamKind::Pantsir { sound::GUN_PANTSIR } else { sound::GUN_GEPARD }; out.sound(event, mount, 1.0, 0.95 + self.vis.f() as f32 * 0.1); } - // At night a gun sees what is lit (a searchlight, a flare) and little else. - let dark = if self.night && !self.lit.contains(&t.id) { lights::DARK_HITS } else { 1.0 }; + // A fresh working own-radar fire-control track does not depend on seeing the target. + // Guns without that radar picture still take the darkness penalty unless illuminated. + let radar_fire_control = sam.radar_up() + && sam.track(gt).is_some_and(|tr| tr.own_within(now, sight::GUN_AGE)); + let dark = lights::hit_factor(self.night, self.lit.contains(&t.id), radar_fire_control); // The aim point is the track's forecast: the farther it is from the truth, the more shells miss. let off = est.distance(t.pos); let p = gun.p * sam.gun_fit() * dark / (t.kind.spec().stealth * (1.0 + (off / 8.0).powi(2))) * (1.0 - dist / (gun.range * 1.3)).max(0.35); @@ -2743,11 +2532,11 @@ impl Sim { } // Missiles. if let (Some(load), Some(mt)) = (&spec.missile, sam.target) { - let ready = why::missile_blocks(sam) == 0; + let ready = why::missile_blocks(sam) == 0 && !sam.preparing.is_some_and(|p| p.ground); // The missile turns after its boost, but the launcher must point near the target. let t = sam.track(mt).filter(|_| drone_at.contains_key(&mt)).map(|tr| tr.est(now)); let aligned = why::missile_aligned(spec.family, sam.yaw, mount, t, aim_id, mt, aim_error); - let n = load.magazine - sam.missiles; + let n = if sam.animated_tubes() { sam.preparing.map_or_else(|| sam.next_tube_cell(), |p| p.cell) } else { load.magazine - sam.missiles }; let (muzzle, dir) = sam.muzzle(n); // The land: a target behind a hill, or a path the hill closes, is not fired at; the system looks for another. let land = if ready && aligned { @@ -2776,9 +2565,11 @@ impl Sim { if land == terrain::WAIT { // The land under the shot is not known yet: it waits. } else if land != 0 { + sam.cancel_prepare(); retarget(&mut missile_on, Some(mt), None); sam.target = None; - } else if ready && aligned { + } else if ready && aligned && sam.prepare_tube(mt, now) { + sam.eject_cell(n); // Past its normal reach: a long shot, and the next waits. if sam.kind.far_shooter() && t.is_some_and(|t| t.distance(sam.pos) >= load.max) { sam.far_cooldown = why::FAR_COOLDOWN; @@ -2811,9 +2602,9 @@ impl Sim { t.ammo_waits += ammo_waits; }); for (kind, muzzle, dir, target, sam) in launches { - // A launch gives the system away. + // A launch gives the system away: the enemy saw it fire, no more than that. if let Some(s) = self.sams.iter().find(|s| s.id == sam) { - self.intel.revealed.push((sam, s.pos, self.tick)); + self.intel.observe(s, self.tick, ew::Seen::Launch); } // It leaves with the track the system fired on (#66). let fix = self @@ -2824,21 +2615,24 @@ impl Sim { .map(|tr| Fix { pos: tr.pos, vel: tr.vel, tick: tr.tick, seeker: false }); let spec = kind.spec(); let id = self.id(); - let v0 = if spec.cold > 0 { 0.7 } else { 0.8 }; + let v0 = if spec.cold > 0 { spec.eject } else { 0.8 }; self.missiles.push(Missile { id, kind, pos: muzzle, vel: dir * v0, target, sam, age: 0, hold: 0, fix, seen: 0 }); if spec.cold > 0 { // Thrown out by gas: a puff, a thump; the motor lights later. out.particles("minecraft:cloud", muzzle, 0.6, dir * 0.05, 0.05, 30); - let event = if kind == MissileKind::M77n6 { "airdef:launch.s500_eject" } else { "airdef:launch.s400_eject" }; + let event = if kind == MissileKind::M77n6 { sound::LAUNCH_S500_EJECT } else { sound::LAUNCH_S400_EJECT }; out.sound(event, muzzle, 1.0, 1.0); + self.scene_event(muzzle, record::SceneKind::MissileLaunch { missile: id, sam, target, kind, sound: event, pitch: 1.0 }); } else { out.particles("minecraft:flash", muzzle, 0.0, DVec3::ZERO, 0.0, 1); if spec.flame > 0.0 { out.particles("minecraft:cloud", muzzle - dir * 2.0, 1.4, DVec3::new(0.0, 0.02, 0.0), 0.06, 70); out.particles("minecraft:campfire_cosy_smoke", muzzle - dir * 3.0, 1.2, DVec3::ZERO, 0.02, 20); } - let event = if kind == MissileKind::M57e6 { "airdef:launch.pantsir" } else { "airdef:launch.patriot" }; - out.sound(event, muzzle, 1.0, 0.95 + self.vis.f() as f32 * 0.1); + let event = if kind == MissileKind::M57e6 { sound::LAUNCH_PANTSIR } else { sound::LAUNCH_PATRIOT }; + let pitch = 0.95 + self.vis.f() as f32 * 0.1; + out.sound(event, muzzle, 1.0, pitch); + self.scene_event(muzzle, record::SceneKind::MissileLaunch { missile: id, sam, target, kind, sound: event, pitch }); } } for (id, pos, vel, life) in tracers { @@ -2846,7 +2640,7 @@ impl Sim { } for (t, by) in gun_hits { if let Some(i) = self.drones.iter().position(|d| d.id == t) { - let _ = self.shoot_down(i, by, None, out); + let _ = self.shoot_down(i, by, None, ground, out); } } for i in 0..self.sams.len() { @@ -2859,8 +2653,12 @@ impl Sim { fn step_missiles(&mut self, ground: &mut Ground, out: &mut Out) { let now = self.tick; let mut done: Vec<(u32, Option, bool, u32)> = Vec::new(); // (missile, target, kill, sam) - let (mut missed, mut expired, mut orphaned) = (0u32, 0u32, 0u32); + let (mut missed, mut expired, mut orphaned, mut energy_lost) = (0u32, 0u32, 0u32, 0u32); let (mut blocked, mut scrubbed) = (0u32, 0u32); + let mut impacts = HashMap::new(); + let mut ignitions = Vec::new(); + let mut burnouts = Vec::new(); + let mut energy_losses = Vec::new(); #[cfg(test)] let mut scrub_log = Vec::new(); let sams_fit: HashMap = self.sams.iter().map(|s| (s.id, 1.0 - 0.3 * (1.0 - s.health.clamp(0.0, 1.0)))).collect(); @@ -2880,11 +2678,23 @@ impl Sim { m.age += 1; let spec = m.kind.spec(); let target = self.drones.iter().find(|d| d.id == m.target && d.alive()); - let lit = m.age > spec.cold; - let speed = if !lit { - (m.vel.length() * 0.97).max(0.4) + let lit = m.engine_lit(); + if !lit { out.engine_stops.push(0x4000_0000 | m.id); } + let coast = m.age > spec.cold + m.kind.burn_ticks(); + if m.age == spec.cold + m.kind.burn_ticks() + 1 { + burnouts.push((m.pos, m.id, m.kind)); + } + // The launch's stages (#129): ejection (unlit, gas drag), ignition (the motor lights + // where the round has reached), departure (spool-up and course capture) and guidance. + // Guidance never runs before ignition: it starts at `cold + boost`, the motor at `cold`. + let guided = spec.guided(m.age); + let speed = if m.age <= spec.cold { + // The gas throw bleeds off linearly; the round is never left hanging still. + (m.vel.length() - spec.eject_drag).max(spec.eject * 0.25) + } else if lit { + (m.vel.length() + spec.accel * spec.spool(m.age, guided)).min(spec.speed) } else { - (m.vel.length() + spec.accel).min(spec.speed) + m.vel.length() * 0.995 }; let dir = m.vel.normalize_or(DVec3::Y); // What it knows: the link's newest track, and its own seeker once the target is in its reach and in view. @@ -2894,8 +2704,7 @@ impl Sim { m.fix = Some(Fix { pos: tr.pos, vel: tr.vel, tick: tr.tick, seeker: false }); } if let Some(t) = target - && lit - && m.age >= spec.boost + && guided && m.kind.seeker() > 0.0 { let range = m.kind.seeker() * t.kind.rcs().powf(0.25).clamp(0.6, 1.6); @@ -2910,14 +2719,14 @@ impl Sim { } } // Nothing new for too long and no seeker on it: the interceptor has lost its target. - if target.is_some() && m.fix.is_none_or(|f| now.saturating_sub(f.tick) > MAX_COAST) && m.age > spec.boost + 20 { + if target.is_some() && m.fix.is_none_or(|f| now.saturating_sub(f.tick) > MAX_COAST) && m.age > spec.cold + spec.boost + 20 { expired += 1; done.push((m.id, None, false, m.sam)); continue; } let aim = m.fix.map(|f| (f.est(now), f.vel)); let new = match aim { - Some((at, vel)) if m.age >= spec.boost => { + Some((at, vel)) if guided => { // Time to go by the closing speed: head-on targets come to meet it. let los = (at - m.pos).normalize_or_zero(); let closing = (speed - vel.dot(los)).max(1.0); @@ -2939,10 +2748,15 @@ impl Sim { done.push((m.id, target.map(|t| t.id), false, m.sam)); continue; } - turn(dir, want, spec.turn) + turn(dir, want, spec.turn * if coast { (speed / spec.speed).clamp(0.15, 1.0) } else { 1.0 }) + } + // Departure: after a cold launch it tips onto the target's course while the motor + // spools up, at a rate that grows into the full one — no snap from the vertical to + // the horizontal (#129). A rail launch already points along its rail; it keeps that + // course until guidance can loft it over the land, as before. + Some((at, _)) if lit && spec.cold > 0 => { + turn(dir, at - m.pos, spec.turn * (0.2 + 0.8 * spec.departure_ramp(m.age))) } - // After a cold launch it tips over towards the target while the motor burns. - Some((at, _)) if lit && spec.cold > 0 => turn(dir, at - m.pos, spec.turn * 0.5), _ => dir, }; let before = m.pos; @@ -2981,10 +2795,15 @@ impl Sim { let delta = rel1 - rel0; let k = (-rel0.dot(delta) / delta.length_squared().max(1e-9)).clamp(0.0, 1.0); if (rel0 + delta * k).length() < spec.fuse && world.map(|w| w.0).or(waiting).is_none_or(|w| k <= w) { - // A target faster than the interceptor is hard to hit. - let ratio = (spec.speed / t.vel.length().max(0.1)).min(1.0).powf(1.5); + // Geometry is already resolved by the swept fuse; only the interceptor's + // remaining speed modifies the single residual kill chance. + let ratio = (speed / spec.speed).clamp(0.0, 1.0).powf(1.5); let fit = sams_fit.get(&m.sam).copied().unwrap_or(1.0) * launchers.get(&m.sam).map_or(1.0, |(at, max)| why::reach_fit(*max, at.distance(m.pos))); - let kill = self.rng.chance(spec.pk * fit * ratio / t.kind.spec().stealth.sqrt()); + let kill = self.rng.chance(spec.pk * fit * ratio); + // The same swept closest approach determines the physical impact, not the + // target's end-of-tick location. Velocities are per simulation tick. + let missile_step = if waiting.is_some() { end - before } else { m.pos - before }; + impacts.insert(m.id, (t.pos - t.vel + t.vel * k, (missile_step - t.vel).length())); missed += u32::from(!kill); done.push((m.id, Some(t.id), kill, m.sam)); continue; @@ -2997,6 +2816,13 @@ impl Sim { done.push((m.id, None, false, m.sam)); continue; } + // A spent motor cannot sustain guidance once it has lost most of its speed. + if coast && speed < spec.speed * 0.25 && target.is_some() { + energy_lost += 1; + energy_losses.push((m.pos, m.id, m.kind, speed)); + done.push((m.id, None, false, m.sam)); + continue; + } if m.age > spec.life || (target.is_none() && m.age > 30) { // Its target gone (shot down by another, or lost), or its life spent chasing one. if target.is_none() { @@ -3007,6 +2833,10 @@ impl Sim { done.push((m.id, None, false, m.sam)); continue; } + if lit && m.age == spec.cold + 1 { + let sound = (spec.cold > 0 && spec.flame > 0.0).then_some(if m.kind == MissileKind::M77n6 { sound::LAUNCH_S500 } else { sound::LAUNCH_S400 }); + ignitions.push((m.pos, m.id, m.kind, sound)); + } if lit { // The motor is one source that follows the missile from ignition on (#95). let (event, gain, pitch) = m.kind.engine_voice(); @@ -3019,17 +2849,27 @@ impl Sim { out.particles("minecraft:cloud", back - new * 0.5, 0.12 * spec.scale as f32, DVec3::ZERO, 0.005, if m.age < 30 { 3 } else { 1 }); } if m.age == spec.cold + 1 && spec.cold > 0 { - let event = if m.kind == MissileKind::M77n6 { "airdef:launch.s500" } else { "airdef:launch.s400" }; + let event = if m.kind == MissileKind::M77n6 { sound::LAUNCH_S500 } else { sound::LAUNCH_S400 }; out.sound(event, m.pos, 1.0, 1.0); out.particles("minecraft:cloud", m.pos, 1.0, DVec3::ZERO, 0.08, 40); } } } + for (pos, missile, kind, sound) in ignitions { + self.scene_event(pos, record::SceneKind::MotorIgnition { missile, kind, sound }); + } + for (pos, missile, kind) in burnouts { + self.scene_event(pos, record::SceneKind::MotorBurnout { missile, kind }); + } + for (pos, missile, kind, speed) in energy_losses { + self.scene_event(pos, record::SceneKind::MotorEnergyLost { missile, kind, speed }); + } #[cfg(test)] self.scrubs.extend(scrub_log); self.note(|t| { t.missed += missed; t.expired += expired; + t.energy_lost += energy_lost; t.orphaned += orphaned; t.blocked += blocked; t.scrubbed += scrubbed; @@ -3039,12 +2879,12 @@ impl Sim { let m = self.missiles.swap_remove(i); self.fireball(m.pos, 2.5 * m.kind.spec().scale, 7); out.particles("minecraft:explosion", m.pos, 0.4, DVec3::ZERO, 0.0, 3); - out.sound("airdef:blast.air", m.pos, 1.0, 1.25); + out.sound(sound::BLAST_AIR, m.pos, 1.0, 1.25); if let Some(t) = target.and_then(|t| self.drones.iter().position(|d| d.id == t)) { self.drones[t].engaged = self.drones[t].engaged.saturating_sub(1); if kill { - let (at, closing) = (self.drones[t].pos, (m.vel - self.drones[t].vel).length()); - let _ = self.shoot_down_by(t, sam, Some(mid), Impact::missile(m.kind, at, closing), out); + let (at, closing) = impacts[&mid]; + let _ = self.shoot_down_by(t, sam, Some(mid), Impact::missile(m.kind, at, closing), ground, out); } } // A missile that lost its target frees it for others. @@ -3059,7 +2899,7 @@ impl Sim { let spec = m.kind.spec(); let rot = look(m.vel, 0.0) * DQuat::from_rotation_z(m.age as f64 * 0.3); out.model(m.id, spec.model, m.pos, rot, spec.scale, false); - if m.age > spec.cold && spec.flame > 0.0 { + if m.engine_lit() && spec.flame > 0.0 { let back = (spec.rear - 8.0) / 16.0 * spec.scale + spec.flame; let size = spec.flame * if m.age < 40 { 1.2 } else { 0.8 } * (0.85 + 0.15 * self.vis.f()); out.model(m.id + 1, "airdef:flame", m.pos + rot * DVec3::new(0.0, 0.0, back), rot, size, true); diff --git a/airdef-mod/src/sim/orders.rs b/airdef-mod/src/sim/orders.rs index a368989..f236f29 100644 --- a/airdef-mod/src/sim/orders.rs +++ b/airdef-mod/src/sim/orders.rs @@ -48,7 +48,7 @@ impl Sim { return false; } sam.dest = Some(to); - let route = route::Route::new(sam.pos, to, slack, sam.kind.climbs(), self.tick); + let route = route::Route::new(sam.pos, to, slack, sam.kind, self.tick); self.routes.map.insert(sam.id, route); sam.target = None; sam.gun_target = None; @@ -102,7 +102,7 @@ impl Sim { sam.march = sam.kind.march_ticks(); continue; }; - let route = routes.map.entry(sam.id).or_insert_with(|| route::Route::new(sam.pos, to, 0.0, sam.kind.climbs(), now)); + let route = routes.map.entry(sam.id).or_insert_with(|| route::Route::new(sam.pos, to, 0.0, sam.kind, now)); let (go, last) = match route.drive(ground, now, sam.pos, share) { route::Drive::Wait(why) => { if route.waited(now) > 40 && !route.told { @@ -145,7 +145,8 @@ impl Sim { let turning = angle_diff(sam.heading, heading_of(flat)).abs(); let step = SPEED * sam.drive_speed() * (1.0 - turning / std::f64::consts::PI).max(0.2); let next = sam.pos + ahead * step; - // The column ahead: a wall or a drop the grid missed turns the vehicle back to the planner. + // The column and chassis clearance ahead: a wall, low ceiling or a change after planning + // turns the vehicle back to the planner. Unknown voxels remain a wait, never a drive. let Some(y) = ground.at(next, now) else { if route.wait_ahead() { out.messages.push(format!("{} №{}: {}", sam.kind.name(), n + 1, route::Why::NoMap.text())); @@ -156,6 +157,23 @@ impl Sim { } continue; }; + match route.clearance(ground, next, now) { + route::Clearance::Clear => {} + route::Clearance::Pending => { + if route.wait_ahead() { + out.messages.push(format!("{} №{}: {}", sam.kind.name(), n + 1, route::Why::NoMap.text())); + routes.map.remove(&sam.id); + sam.dest = None; + sam.state = SamState::Deploying; + sam.march = sam.kind.march_ticks(); + } + continue; + } + route::Clearance::Blocked => { + route.blocked_ahead(next, sam.pos); + continue; + } + } route.ahead_ok(); let rise = y - sam.pos.y; if rise > route::STEP_UP || rise < -route::STEP_DOWN { diff --git a/airdef-mod/src/sim/outcome.rs b/airdef-mod/src/sim/outcome.rs index 9ff36cf..1adc13e 100644 --- a/airdef-mod/src/sim/outcome.rs +++ b/airdef-mod/src/sim/outcome.rs @@ -123,9 +123,9 @@ pub fn look(kind: DroneKind, outcome: Outcome) -> (f32, u32, f32, &'static str) let life = (8 + (w as u32) / 2) * 9; let gain = 1.0 + 0.06 * w; match outcome { - Outcome::DisabledFalling => (size, life, gain, "airdef:blast.air"), - Outcome::Breakup => (size * 0.9, life, gain * 0.9, "airdef:blast.medium"), - Outcome::AirDetonation => (size * 1.9, life * 8 / 5, gain * 1.5, "airdef:blast.big"), + Outcome::DisabledFalling => (size, life, gain, super::catalog::sound::BLAST_AIR), + Outcome::Breakup => (size * 0.9, life, gain * 0.9, super::catalog::sound::BLAST_MEDIUM), + Outcome::AirDetonation => (size * 1.9, life * 8 / 5, gain * 1.5, super::catalog::sound::BLAST_BIG), } } @@ -247,7 +247,7 @@ mod tests { sim.sams.push(Sam::new(sam, SamKind::Patriot, DVec3::new(0.0, 70.0, 0.0), 0.0)); sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 120.0, 300.0), DVec3::X, DVec3::new(900.0, 70.0, 0.0)); let at = sim.drones[0].pos; - let o = sim.shoot_down_by(0, sam, None, imp(at), &mut out).unwrap(); + let o = sim.shoot_down_by(0, sam, None, imp(at), &Ground::default(), &mut out).unwrap(); (o, sim.drones.len(), sim.rng.state()) } @@ -276,7 +276,7 @@ mod tests { sim.sams.push(Sam::new(sam, SamKind::Patriot, DVec3::new(0.0, 70.0, 0.0), 0.0)); sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 120.0, 300.0), DVec3::X, DVec3::new(900.0, 70.0, 0.0)); let at = sim.drones[0].pos; - let o = sim.shoot_down_by(0, sam, Some(5), Impact::missile(MissileKind::M77n6, at, 6.0), &mut out).unwrap(); + let o = sim.shoot_down_by(0, sam, Some(5), Impact::missile(MissileKind::M77n6, at, 6.0), &Ground::default(), &mut out).unwrap(); if o != Outcome::AirDetonation { continue; } @@ -311,7 +311,7 @@ mod tests { sim.add_drone(DroneKind::Shahed, DVec3::new(f64::from(i) * 500.0, 400.0, 300.0), DVec3::X, DVec3::new(9e5, 70.0, 0.0)); let at = sim.drones.last().unwrap().pos; let idx = sim.drones.len() - 1; - let _ = sim.shoot_down_by(idx, sam, None, Impact::missile(MissileKind::M77n6, at, 6.0), &mut out); + let _ = sim.shoot_down_by(idx, sam, None, Impact::missile(MissileKind::M77n6, at, 6.0), &Ground::default(), &mut out); } let pieces = |s: &Sim| s.fx.iter().filter(|(_, f)| matches!(f, Fx::Piece { .. })).count(); let n = pieces(&sim); diff --git a/airdef-mod/src/sim/raid.rs b/airdef-mod/src/sim/raid.rs index 8ded552..5df1120 100644 --- a/airdef-mod/src/sim/raid.rs +++ b/airdef-mod/src/sim/raid.rs @@ -60,6 +60,29 @@ pub struct RaidOrder { pub aim: Aim, } +impl RaidOrder { + /// A combined strike of about `n`, as `/airdef wave mix n` orders it. + pub fn mix(n: u32, dist: f64) -> RaidOrder { + let n = n.max(4); + let mut kinds = vec![ + (DroneKind::Gerbera, n / 4 + 1), + (DroneKind::Shahed, n / 3 + 1), + (DroneKind::Geran3, n / 8 + 1), + (DroneKind::Kh101, n / 8 + 1), + (DroneKind::Kalibr, n / 10 + 1), + (DroneKind::Kh22, n / 16), + (DroneKind::Zircon, n / 20), + (DroneKind::Iskander, n / 12 + 1), + (DroneKind::Kinzhal, n / 16), + ]; + if n >= 30 { + kinds.push((DroneKind::Oreshnik, 1)); + } + kinds.retain(|k| k.1 > 0); + RaidOrder { kinds, approach: Approach::TwoSectors, dist, aim: Aim::Objects } + } +} + /// A drone of a raid being planned. struct Item { d: Drone, @@ -123,6 +146,7 @@ const LONGEST_WAIT: f64 = 12_000.0; pub(super) const WORK_PER_TICK: usize = 40_000; /// The planner's grid over a raid: its corner, its cells' size and how many a side. +#[derive(Clone, Copy)] pub(super) struct Grid { pub(super) min: DVec3, pub(super) cell: f64, @@ -134,22 +158,20 @@ impl Grid { let n = self.size; ((((p.x - self.min.x) / self.cell) as i32).clamp(0, n - 1), (((p.z - self.min.z) / self.cell) as i32).clamp(0, n - 1)) } - fn centre(&self, (i, j): (i32, i32), y: f64) -> DVec3 { + pub(super) fn centre(&self, (i, j): (i32, i32), y: f64) -> DVec3 { DVec3::new(self.min.x + (i as f64 + 0.5) * self.cell, y, self.min.z + (j as f64 + 0.5) * self.cell) } } -/// The cheapest way over `grid` from `from` to `to` by its prices (A*): points along it, `to` last. -pub(super) fn plan_route(grid: &Grid, prices: &[f32], from: DVec3, to: DVec3) -> (Vec, usize) { - plan_route_on(grid, prices, None, from, to) -} - /// The land a route crosses: its height under each cell (NaN unknown), the climb a kind holds for long -/// (rise a block), and the height above which the land forces a drone up from its low altitude. +/// (rise a block), and the height above which the land forces a drone up from its low altitude. Unknown +/// land costs `unknown` per cell on top of the open ground: zero for a plan routed over land it is still +/// mapping, more than free for a replanned retarget (#127). pub(super) struct Relief<'a> { pub heights: &'a [f32], pub slope: f32, pub floor: f32, + pub unknown: f32, } /// [`plan_route`] over relief: a climb steeper than the kind holds is dear (a cliff or a ridge it would have to @@ -196,6 +218,9 @@ pub(super) fn plan_route_on(grid: &Grid, prices: &[f32], relief: Option<&Relief> price += (slope - r.slope) * 20.0 * len; } } + } else if r.unknown > 0.0 { + // Unmapped land is not free: the route prefers what the plan actually knew. + price += len * r.unknown; } } let ng = g + price; @@ -255,6 +280,10 @@ impl Sim { return 0; } let smart = order.aim == Aim::Smart; + // The planner's picture of the layout: one snapshot for the whole order. It is + // read-only while the plan is built, and taking it per drone made a big second order + // quadratic in the force's size (#122); a map of contacts makes it linear in them (#127). + let picture = self.picture_of_defences(); // A smart enemy comes from where its raids got through best. let bearing = if smart { self.best_bearing() } else { self.rng.f() * TAU }; // The raid's sides (1, 2, or 4-5): its launch sites go there (#100). @@ -267,7 +296,7 @@ impl Sim { let grid = Grid { min: centre - DVec3::new(span, 0.0, span), cell, size: (2.0 * span / cell) as i32 + 1 }; let mut items = Vec::new(); // A smart raid goes for the radars too (a few strikers each); the others pick as they always did. - let radar_jobs = if smart { self.radar_strikes(&order.kinds) } else { Vec::new() }; + let radar_jobs = if smart { self.radar_strikes(&order.kinds, &picture) } else { Vec::new() }; let mut index = 0usize; for (group, &(kind, count)) in order.kinds.iter().enumerate() { let spec = kind.spec(); @@ -283,7 +312,7 @@ impl Sim { index += 1; let (target, at_sam) = match job { Some((at, id)) => (at, Some(id)), - None => self.choose_target(kind, object, order.aim), + None => self.choose_target(kind, object, order.aim, &picture), }; let aim = target + DVec3::new(self.rng.s() * 4.0, 0.0, self.rng.s() * 4.0); let dist = spec.ballistic.map_or(order.dist, |far| far.max(order.dist)) + self.rng.f() * 50.0; @@ -291,6 +320,16 @@ impl Sim { // Where it starts is provisional: the site's pad, once the site is placed. let from = centre + DVec3::new(a.sin() * dist, height + self.rng.s() * 6.0, a.cos() * dist); let mut d = self.make_drone(kind, from, horizontal(aim - from), aim); + // The plan's own decision, recorded as such (#127): a radar job is SEAD, a unit + // chosen for the raid is the plan's objective, anything else is a marked object. + if let Some(id) = at_sam { + let confidence = picture.iter().find(|c| c.id == id).map(|c| c.confidence(self.tick)); + d.intent = if job.is_some() { ew::Intent::Sead } else { ew::Intent::Plan }; + d.aim = confidence.map_or_else(|| ew::Aim::point(self.tick), |c| ew::Aim::unit(id, self.tick, c)); + } else { + d.intent = ew::Intent::Plan; + d.aim = ew::Aim::point(self.tick); + } let range = spec.ballistic.map_or(order.dist, |far| far.max(order.dist)); let want = sites::want_index(&mut wants, centre, side, a, sites::SiteClass::of(kind), range, order.dist.max(120.0)); let pad = wants[want].jobs; @@ -422,6 +461,7 @@ impl Sim { heights: &plan.heights, slope: (kind.climb_pitch().tan() * 0.5) as f32, floor: (aim.y + spec.clearance) as f32, + unknown: 0.0, }); let (r, cost) = plan_route_on(&plan.grid, &plan.prices[&kind], relief.as_ref(), from, aim); work += cost; @@ -457,6 +497,11 @@ impl Sim { } self.queue.push((now + wait.clamp(0.0, LONGEST_WAIT) as u64, d)); } + // Keep the land this plan was worked out over (#127): a retarget later is planned over the same + // known relief, not over ground mapped since. + if plan.smart && !plan.heights.is_empty() { + self.known_relief = Some(std::sync::Arc::new(ew::KnownRelief::new(plan.grid, std::mem::take(&mut plan.heights)))); + } self.wave = Some(WaveStats { kills: self.kills, hits: self.hits, @@ -477,6 +522,18 @@ impl Sim { self.plan.is_some() } + /// The plan's drones and the structured intent each was given (#127; tests and diagnostics). + #[cfg(test)] + pub(super) fn planned_intents(&self) -> Vec<(u32, ew::Intent)> { + self.plan.as_ref().map(|p| p.items.iter().map(|i| (i.d.id, i.d.intent)).collect()).unwrap_or_default() + } + + /// The plan's unit aims and their intent: the same list, keyed by what the drone goes for. + #[cfg(test)] + pub(super) fn planned_aims(&self) -> Vec<(u32, ew::Intent)> { + self.plan.as_ref().map(|p| p.items.iter().map(|i| (i.d.aim.unit, i.d.intent)).collect()).unwrap_or_default() + } + /// A drone ready to fly from `from`, not yet in the sky. pub(super) fn make_drone(&mut self, kind: DroneKind, from: DVec3, dir: DVec3, target: DVec3) -> Drone { let id = self.id(); @@ -511,17 +568,24 @@ impl Sim { smart: false, nav: Nav::default(), err, + intent: ew::Intent::Plan, + aim: ew::Aim::point(0), hold: 0, pace: Pace::default(), } } - /// The strikes a smart raid sends at the radars (they emit, so the enemy knows them whether or not they - /// fired): for the drones of `kinds` in order, the radar's place and id for those that go for one. Each radar + /// The strikes a smart raid sends at observed radars: for drones of `kinds` in order, + /// the last observed place and id for those that go for one. Each radar /// gets two or three, the fast strikers first, and all together no more than about a quarter of the raid. - pub(super) fn radar_strikes(&self, kinds: &[(DroneKind, u32)]) -> Vec> { - let mut radars: Vec<&Sam> = self.sams.iter().filter(|s| s.alive() && s.kind == SamKind::Radar).collect(); - radars.sort_by_key(|s| s.id); + /// Only contacts the picture still trusts: a doubtful one (aged, never re-seen) is not worth a missile. + pub(super) fn radar_strikes(&self, kinds: &[(DroneKind, u32)], picture: &[ew::Contact]) -> Vec> { + let mut radars: Vec<(u32, DVec3)> = picture + .iter() + .filter(|c| c.kind == SamKind::Radar && c.confidence(self.tick) >= ew::Confidence::Probable) + .map(|c| (c.id, c.at)) + .collect(); + radars.sort_by_key(|(id, _)| *id); let flat: Vec = kinds.iter().flat_map(|&(k, n)| std::iter::repeat_n(k, n as usize)).collect(); let mut jobs = vec![None; flat.len()]; if radars.is_empty() { @@ -536,50 +600,45 @@ impl Sim { let cap = (flat.len() / 4).max(1); for (n, &(_, i)) in ranked.iter().take((radars.len() * want).min(cap)).enumerate() { let radar = radars[n % radars.len()]; - jobs[i] = Some((radar.pos, radar.id)); + jobs[i] = Some((radar.1, radar.0)); } jobs } - /// What a drone goes for: an object, or (by its kind and the raid's aim) a system. Returns the - /// point and the system's id. - fn choose_target(&mut self, kind: DroneKind, object: DVec3, aim: Aim) -> (DVec3, Option) { - let alive: Vec<&Sam> = self.sams.iter().filter(|s| s.alive()).collect(); - if alive.is_empty() || kind.spec().decoy || aim == Aim::Objects { + /// What a drone goes for: an object, or (by its kind and the raid's aim) a unit. Returns the + /// point and the unit's id. The planner has only the picture (#93, #127): what it knows of a + /// unit is the last observation — the place seen then, the type identified then — never where + /// the unit actually is or what it has become since. + fn choose_target(&mut self, kind: DroneKind, object: DVec3, aim: Aim, picture: &[ew::Contact]) -> (DVec3, Option) { + if picture.is_empty() || kind.spec().decoy || aim == Aim::Objects { return (object, None); } - let pick = |list: &[&Sam], r: f64| -> Option<(DVec3, u32)> { - (!list.is_empty()).then(|| { - let s = list[(r * list.len() as f64) as usize % list.len()]; - (s.pos, s.id) - }) + let pick = |list: &[ew::Contact], r: f64| -> Option<(DVec3, u32)> { + let c = list.get((r * list.len() as f64) as usize)?; + Some((c.at, c.id)) }; let r = self.rng.f(); let roll = self.rng.f(); if aim == Aim::ObjectsAndSams { - return if roll < 0.33 { pick(&alive, r).map_or((object, None), |(p, id)| (p, Some(id))) } else { (object, None) }; + return if roll < 0.33 { pick(picture, r).map_or((object, None), |(p, id)| (p, Some(id))) } else { (object, None) }; } // Smart: each kind at what it is good against; what gave itself away first (a jamming // station, a radar, a system that fired). - // The planner has only what the enemy knows (#93): the radars (they emit) and the units that gave - // themselves away; the rest of the player's layout is not in its hands. - let known = self.known_units(); - let alive: Vec<&Sam> = alive.iter().copied().filter(|s| Self::enemy_knows(s, &known)).collect(); - let known_ew: Vec<&Sam> = alive.iter().copied().filter(|s| s.kind == SamKind::Ew).collect(); + let known_ew: Vec = picture.iter().copied().filter(|c| c.kind == SamKind::Ew).collect(); if matches!(kind, DroneKind::Lancet | DroneKind::Kh22) && !known_ew.is_empty() && roll < 0.6 { - let s = known_ew[(r * known_ew.len() as f64) as usize % known_ew.len()]; - return (s.pos, Some(s.id)); + let c = known_ew[(r * known_ew.len() as f64) as usize % known_ew.len()]; + return (c.at, Some(c.id)); } // Short-range systems (guns, Tor, P1-SUN) for the Lancets; long-range ones and the radars // that feed them for the fast missiles. - let guns: Vec<&Sam> = alive.iter().copied().filter(|s| s.kind.armed() && s.kind.range() <= 300.0).collect(); - let long: Vec<&Sam> = - alive.iter().copied().filter(|s| s.kind.range() >= 450.0 || s.kind == SamKind::Radar).collect(); + let guns: Vec = picture.iter().copied().filter(|c| c.kind.armed() && c.kind.range() <= 300.0).collect(); + let long: Vec = + picture.iter().copied().filter(|c| c.kind.range() >= 450.0 || c.kind == SamKind::Radar).collect(); let choice = match kind { - DroneKind::Lancet if roll < 0.75 => pick(&guns, r).or_else(|| pick(&alive, r)), + DroneKind::Lancet if roll < 0.75 => pick(&guns, r).or_else(|| pick(picture, r)), DroneKind::Kh22 | DroneKind::Zircon if roll < 0.45 => pick(&long, r), DroneKind::Iskander | DroneKind::Kinzhal if roll < 0.3 => pick(&long, r), - DroneKind::Shahed | DroneKind::Geran3 if roll < 0.2 => pick(&alive, r), + DroneKind::Shahed | DroneKind::Geran3 if roll < 0.2 => pick(picture, r), _ => None, }; choice.map_or((object, None), |(p, id)| (p, Some(id))) @@ -587,60 +646,68 @@ impl Sim { /// The price of each cell of `grid` for this kind flying low: 1, and much more where a /// system sees it there (its radar's reach for it, the land) and can shoot it. + #[cfg(test)] pub(super) fn price_grid(&self, grid: &Grid, kind: DroneKind) -> (Vec, usize) { self.price_grid_on(grid, kind, None) } - /// The same over known land: a cell's drone flies `height` over the land there, not over the system's feet, - /// so what a radar sees and what a hill hides follow the relief. + /// The same over the picture this moment holds: a smart plan's own prices are priced on the + /// picture the plan was ordered with (#127). Later observations change nothing here. pub(super) fn price_grid_on(&self, grid: &Grid, kind: DroneKind, land: Option<&[f32]>) -> (Vec, usize) { + self.price_grid_known(grid, kind, land, &self.picture_of_defences()) + } + + /// The same over a picture of the defences: a retarget is priced on the picture its target was + /// chosen from, so the jobs of one adaptive tick do not each see a different world (#127). Every + /// capability read here is the contact's published kind and the contact's own observations — + /// never the unit's actual switch settings, damage, roles, link health or magazine. + pub(super) fn price_grid_known(&self, grid: &Grid, kind: DroneKind, land: Option<&[f32]>, picture: &[ew::Contact]) -> (Vec, usize) { let mut cost = 0; let spec = kind.spec(); let height = spec.clearance.max(6.0); - let known_now = self.known_units(); let n = grid.size; + let now = self.tick; let mut prices = vec![1.0f32; (n * n) as usize]; // Known jamming stations: drones that navigate by satellite keep away. if kind.jammable() { - let known = self.known_units(); - for s in self.sams.iter().filter(|s| s.kind == SamKind::Ew && s.alive() && known.iter().any(|k| k.0 == s.id)) { - let (ci, cj) = grid.index(s.pos); - let r = (EW_RADIUS / grid.cell).ceil() as i32; + for c in picture.iter().filter(|c| c.kind == SamKind::Ew) { + let (ci, cj) = grid.index(c.at); + let r = (ew::EW_RADIUS / grid.cell).ceil() as i32; for i in (ci - r).max(0)..=(ci + r).min(n - 1) { for j in (cj - r).max(0)..=(cj + r).min(n - 1) { - if horizontal(grid.centre((i, j), s.pos.y) - s.pos).length() < EW_RADIUS { + if horizontal(grid.centre((i, j), c.at.y) - c.at).length() < ew::EW_RADIUS { prices[(i * n + j) as usize] += 12.0; } } } } } - // Only the systems the enemy knows of are priced in (#93): a hidden one is flown into. - for s in self.sams.iter().filter(|s| s.alive() && s.roles & spec.class.bit() != 0 && Self::enemy_knows(s, &known_now)) { - let sp = s.kind.spec(); + // Only the systems the picture holds are priced in (#93): a hidden one is flown into. + for c in picture.iter().filter(|c| c.kind.spec().roles & spec.class.bit() != 0) { + let pos = c.at; + let sp = c.kind.spec(); let (lo, hi) = sp.missile.as_ref().map_or((0.0, 0.0), |m| (m.min, m.max)); let gun = sp.gun.as_ref().map_or(0.0, |g| g.range); - // A gun needs its own radar; a missile also flies on the tracks of any radar within data-link range. - let own = sight::reach(s, kind, height); - let net = self - .sams + // A gun needs its own radar; a missile also flies on the tracks of any radar the picture + // holds within data-link range of the unit's observed place. What those radars are set to + // (role, switch, damage) is not observable: a live contact of one is assumed to feed. + let own = if c.believes_own_sensor(now) { ew::published_reach(c.kind, kind, height) } else { 0.0 }; + let net = picture .iter() - .filter(|r| r.alive() && r.id != s.id && r.pos.distance(s.pos) <= sight::LINK) - .map(|r| sight::reach(r, kind, height)) + .filter(|d| d.kind == SamKind::Radar && d.id != c.id && d.at.distance(pos) <= sight::LINK) + .map(|d| ew::published_reach(d.kind, kind, height)) .fold(0.0, f64::max); let (hi, gun) = (hi.min(own.max(net)), gun.min(own)); let far = hi.max(gun); - let at = sight::antenna(s); - let horizon = self.horizons.get(&s.id); // Only the cells round it. - let (ci, cj) = grid.index(s.pos); + let (ci, cj) = grid.index(pos); let r = (far / grid.cell).ceil() as i32 + 1; for i in (ci - r).max(0)..=(ci + r).min(n - 1) { for j in (cj - r).max(0)..=(cj + r).min(n - 1) { cost += 1; let ground = land.map(|l| l[(i * n + j) as usize]).filter(|g| g.is_finite()); - let p = grid.centre((i, j), ground.map_or(s.pos.y + height, |g| f64::from(g) + height)); - let d = horizontal(p - s.pos).length(); + let p = grid.centre((i, j), ground.map_or(pos.y + height, |g| f64::from(g) + height)); + let d = horizontal(p - pos).length(); if d > far { continue; } @@ -648,9 +715,6 @@ impl Sim { if !(missile || d < gun) { continue; } - if horizon.is_some_and(|h| h.hides(at, p)) { - continue; - } prices[(i * n + j) as usize] += if missile { 30.0 } else { 18.0 }; } } @@ -756,7 +820,8 @@ const AIR_START_WAIT: u32 = 120; // A side that found nothing is not given up (no raid is cancelled for it): its site is off the map. // The pads, and the sites themselves; the old raid's sites go unless they still have jobs to fly. let queued: Vec = self.queue.iter().map(|q| q.1.id).collect(); - self.sites.retire(&queued); + let refs = self.site_refs(); + self.sites.retire(&queued, &refs); for w in plan.wants.iter_mut() { w.jobs = 0; } @@ -941,6 +1006,33 @@ mod tests { sim.drones.iter().chain(sim.queue.iter().map(|q| &q.1)).filter(|d| d.alive() && (d.target - d.err).distance(p) < 6.0).map(|d| d.kind).collect() } + #[test] + fn sead_uses_only_unexpired_observations_at_the_last_known_position() { + let mut sim = Sim::new(121); + let old = DVec3::new(100.0, 70.0, 200.0); + let radar = Sam::new(1, SamKind::Radar, old, 0.0); + sim.sams.push(radar); + let kinds = [(DroneKind::Kh22, 8)]; + let none = Vec::new(); + assert!(sim.radar_strikes(&kinds, &none).iter().all(Option::is_none)); + sim.tick = 20; + sim.intel.observe(&sim.sams[0], sim.tick, ew::Seen::Emission); + // The truth moving after the fix is not an observation: the strike goes to the seen place. + sim.sams[0].pos = DVec3::new(900.0, 70.0, 900.0); + sim.tick = 40; + let picture = sim.picture_of_defences(); + let jobs = sim.radar_strikes(&kinds, &picture); + assert_eq!(jobs.iter().flatten().count(), 2); + assert!(jobs.iter().flatten().all(|job| *job == (old, 1))); + // An old contact is not worth a strike either. + sim.tick = 3021; + let picture = sim.picture_of_defences(); + assert!(sim.radar_strikes(&kinds, &picture).iter().all(Option::is_none), "a doubtful contact was struck"); + sim.tick = 6020; + let picture = sim.picture_of_defences(); + assert!(sim.radar_strikes(&kinds, &picture).iter().all(Option::is_none)); + } + #[test] fn a_smart_raid_goes_for_every_radar_and_the_enemys_adapting_does_not_turn_it_away() { let mut sim = Sim::new(21); @@ -948,7 +1040,9 @@ mod tests { for (i, at) in [(-300.0, -200.0), (250.0, -250.0), (50.0, 300.0)].into_iter().enumerate() { let id = sim.id(); let p = DVec3::new(at.0, 70.0, at.1); - sim.sams.push(Sam::new(id, SamKind::Radar, p, i as f64)); + let sam = Sam::new(id, SamKind::Radar, p, i as f64); + sim.intel.observe(&sam, sim.tick, ew::Seen::Emission); + sim.sams.push(sam); radars.push((id, p)); } let ew = sim.id(); @@ -962,7 +1056,7 @@ mod tests { serve(&mut ground, &land, sim.tick); sim.step(&mut ground, None, &mut out); } - let mut order = crate::wave::WaveConfig::mix(36, 500.0); + let mut order = RaidOrder::mix(36, 500.0); order.aim = Aim::Smart; order.approach = Approach::AllSides; let total: u32 = order.kinds.iter().map(|k| k.1).sum(); @@ -985,7 +1079,7 @@ mod tests { // The first strikers are the fast ones. assert!(radars.iter().any(|(_, p)| aimed_at(&sim, *p).iter().any(|k| matches!(k, DroneKind::Kh22 | DroneKind::Zircon | DroneKind::Iskander | DroneKind::Kinzhal)))); // The enemy has learnt of the jamming station and re-aims what it can: not these. - sim.intel.revealed.push((ew, DVec3::new(0.0, 70.0, 0.0), sim.tick)); + sim.intel.observe(&sim.sams.iter().find(|s| s.id == ew).unwrap(), sim.tick, ew::Seen::Jamming); for _ in 0..160 { serve(&mut ground, &land, sim.tick); sim.step(&mut ground, None, &mut out); @@ -993,6 +1087,97 @@ mod tests { check(&sim, "after adapting"); } + /// The picture the planner works from: one contact per unit, the newest observation + /// winning, the forgotten and the watched-dead out, the order deterministic — and it + /// never reads the force itself, so a contact of a unit that no longer exists stays + /// until its observations age out. + #[test] + fn picture_is_newest_per_unit_and_reads_no_truth() { + let mut sim = Sim::new(9); + let mut ids = Vec::new(); + for (i, kind) in [SamKind::S400, SamKind::Ew, SamKind::Radar, SamKind::Tor, SamKind::Patriot].into_iter().enumerate() { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(i as f64 * 100.0, 70.0, -50.0), 0.0)); + ids.push(id); + } + sim.tick = 7000; + let (a, b, c, d, e) = (ids[0], ids[1], ids[2], ids[3], ids[4]); + // A long fight: repeated observations (newest wins), one unit watched dying, one + // never seen, one id the force does not have, and one observation that aged out. + for t in 0..200u64 { + sim.intel.observe(&sim.sams[0], 1000 + t, ew::Seen::Launch); + sim.intel.observe(&sim.sams[2], 1000 + t, ew::Seen::Emission); + if t % 7 == 0 { sim.intel.observe(&sim.sams[1], 1000 + t, ew::Seen::Jamming); } + if t % 11 == 0 { sim.intel.observe(&sim.sams[3], 1000 + t, ew::Seen::Launch); } + if t % 13 == 0 { + sim.intel.note(999_999, SamKind::Radar, DVec3::new(1.0, 70.0, 0.0), true, 1000 + t, ew::Seen::Emission); + } + } + sim.intel.note(e, SamKind::Patriot, DVec3::new(15.0, 70.0, 0.0), true, 2, ew::Seen::Launch); // aged out at tick 7000 + // The EW station is watched dying by a strike: observed, and so out of the targets. + sim.intel.outcome(b, false, sim.sams[1].pos, 1349); + let picture = sim.picture_of_defences(); + let got: Vec = picture.iter().map(|c| c.id).collect(); + // a and c were both seen last at tick 1199 (tie: the newer id first); d at 1198; the + // fake at 1195. The watched-dead b, the never-seen e and the aged-out are out. + assert_eq!(got, vec![c, a, d, 999_999]); + assert_eq!(picture[0].at, DVec3::new(200.0, 70.0, -50.0), "the observed place is the unit's own"); + assert_eq!(picture[0].seen, ew::Seen::Emission); + assert_eq!(picture[1].kind, SamKind::S400); + assert!(picture.iter().all(|c| c.id != b && c.id != e)); + assert_eq!(sim.picture_of_defences(), picture, "the picture is not deterministic"); + } + + /// The reported Windows stop (#122): the second big order from the UI path, with a fat + /// intel log and a large surviving force, must be accepted and launched like the first. + /// (The old per-drone planner snapshot was correct but quadratic; its cost is pinned by + /// the actual-WASM fuel measurements in docs/measurements/issue-122-ui-fuel.) + #[test] + fn a_second_smart_order_with_a_fat_intel_log_is_accepted_and_launched() { + let mut sim = Sim::new(31); + let mut ids = Vec::new(); + for i in 0..48 { + let id = sim.id(); + sim.sams.push(Sam::new(id, [SamKind::S400, SamKind::Patriot, SamKind::Nasams, SamKind::Pantsir][i % 4], DVec3::new((i % 8 * 40) as f64, 70.0, (i / 8 * 40) as f64), 0.0)); + ids.push(id); + } + // A fought-off first raid leaves a long observation history: launches, emitting + // radars, stations. Its entries sit in the past of the sim clock, like real ones. + sim.tick = 4000; + for t in 0..3000u64 { + let sam = &sim.sams[(t as usize) % ids.len()]; + sim.intel.observe(sam, 1001 + t, if t % 3 == 0 { ew::Seen::Emission } else { ew::Seen::Launch }); + } + sim.targets.push(DVec3::new(160.0, 70.0, 160.0)); + sim.target_health.push(100.0); + let mut ground = Ground::default(); + let land = Land(|_, _| 64); + let mut out = Out::default(); + for _ in 0..20 { + serve(&mut ground, &land, sim.tick); + sim.step(&mut ground, None, &mut out); + } + let mut order = RaidOrder::mix(36, 300.0); + order.aim = Aim::Smart; + order.approach = Approach::AllSides; + let total: u32 = order.kinds.iter().map(|k| k.1).sum(); + let first = sim.order_raid(&order, &sim.targets.clone(), &mut out); + assert_eq!(first, total); + while sim.planning() { + serve(&mut ground, &land, sim.tick); + sim.step(&mut ground, None, &mut out); + } + assert!(sim.queue.len() + sim.launched as usize >= total as usize, "the first order queued or launched every drone (queue {}, launched {})", sim.queue.len(), sim.launched); + // The second order — the path that stopped the mod on Windows — is accepted too. + let second = sim.order_raid(&order, &sim.targets.clone(), &mut out); + assert_eq!(second, total); + while sim.planning() { + serve(&mut ground, &land, sim.tick); + sim.step(&mut ground, None, &mut out); + } + assert!(sim.queue.len() + sim.launched as usize >= 2 * total as usize, "both orders queued or launched their drones (queue {}, launched {})", sim.queue.len(), sim.launched); + } + #[test] fn systems_ready_together_send_one_missile_at_a_drone() { let mut sim = Sim::new(3); @@ -1043,13 +1228,15 @@ mod big { serve(&mut ground, &land, sim.tick); sim.step(&mut ground, None, &mut out); } - let mut w = crate::wave::WaveConfig::default(); - w.counts = [10, 12, 6, 6, 6, 6, 4, 4, 6, 4, 1]; - w.aim = Aim::Smart; - w.approach = Approach::AllSides; - w.dist = 800.0; + let order = RaidOrder { + kinds: [DroneKind::Gerbera, DroneKind::Shahed, DroneKind::Geran3, DroneKind::Lancet, + DroneKind::Kh101, DroneKind::Kalibr, DroneKind::Kh22, DroneKind::Zircon, + DroneKind::Iskander, DroneKind::Kinzhal, DroneKind::Oreshnik] + .into_iter().zip([10, 12, 6, 6, 6, 6, 4, 4, 6, 4, 1]).collect(), + aim: Aim::Smart, approach: Approach::AllSides, dist: 800.0, + }; let t = std::time::Instant::now(); - sim.order_raid(&w.order(), &sim.targets.clone(), &mut out); + sim.order_raid(&order, &sim.targets.clone(), &mut out); let mut ticks = 0; let mut slowest = std::time::Duration::ZERO; while sim.planning() { diff --git a/airdef-mod/src/sim/record.rs b/airdef-mod/src/sim/record.rs index 289ff9f..30d0bfe 100644 --- a/airdef-mod/src/sim/record.rs +++ b/airdef-mod/src/sim/record.rs @@ -20,6 +20,15 @@ pub struct SamKey { dish: f32, /// A searchlight's beam: how far it runs free (to 8 blocks), 0 when it is off. beam: f32, + /// Hull and component health at the sampled tick. + health: f32, + parts: [f32; 6], + mode: battery::SamMode, + radar_role: battery::RadarRole, + radar_on: bool, + spent_cells: u8, + cap_cell: u8, + cap_open: f32, } impl SamKey { @@ -33,6 +42,14 @@ impl SamKey { pitch: sam.pitch as f32, dish: sam.dish as f32, beam: if sam.beam_on { ((sam.beam_free / 8.0).round() * 8.0).max(8.0) as f32 } else { 0.0 }, + health: sam.health as f32, + parts: sam.comps.0.map(|v| v as f32), + mode: sam.mode, + radar_role: sam.radar_role, + radar_on: sam.radar_on, + spent_cells: sam.spent_cells, + cap_cell: sam.cap_cell, + cap_open: sam.cap_open, } } /// The same pose (to within what could be seen). @@ -44,6 +61,11 @@ impl SamKey { && near(self.pitch, o.pitch, 0.002) && near(self.dish, o.dish, 0.002) && self.beam == o.beam + && self.health == o.health + && self.parts == o.parts + && self.mode == o.mode && self.radar_role == o.radar_role && self.radar_on == o.radar_on + && self.spent_cells == o.spent_cells && self.cap_cell == o.cap_cell + && near(self.cap_open, o.cap_open, 0.001) } } @@ -84,6 +106,14 @@ impl SamTrack { pitch: l(a.pitch, b.pitch), dish: turn(a.dish, b.dish, f), beam: a.beam, + health: a.health, + parts: a.parts, + mode: a.mode, + radar_role: a.radar_role, + radar_on: a.radar_on, + spent_cells: a.spent_cells, + cap_cell: a.cap_cell, + cap_open: if a.cap_cell == b.cap_cell && a.spent_cells == b.spent_cells { l(a.cap_open, b.cap_open) } else { a.cap_open }, }) } /// The system as it stood at `t`, as a ghost to draw with the ids from `id`. @@ -96,15 +126,22 @@ impl SamTrack { sam.dish = k.dish as f64; sam.beam_on = k.beam > 0.0; sam.beam_free = k.beam as f64; - sam.health = if self.lost.is_some_and(|l| t >= l as f64) { 0.0 } else { 1.0 }; + sam.health = if self.lost.is_some_and(|l| t >= l as f64) { 0.0 } else { k.health as f64 }; + sam.comps = Comps(k.parts.map(|v| v as f64)); + sam.mode = k.mode; + sam.radar_role = k.radar_role; + sam.radar_on = k.radar_on; + sam.spent_cells = k.spent_cells; + sam.cap_cell = k.cap_cell; + sam.cap_open = k.cap_open; Some(sam) } } -#[cfg(test)] +#[cfg(any(test, feature = "test-fixtures"))] impl Recording { /// A recording of `len` ticks with nothing in it (for the planner's tests). - pub(crate) fn blank(len: u32) -> Self { + pub fn blank(len: u32) -> Self { Recording { number: 1, origin: DVec3::ZERO, @@ -112,6 +149,7 @@ impl Recording { tracks: Vec::new(), index: HashMap::new(), events: Vec::new(), + scene: None, tally: Tally::default(), sams: Vec::new(), objects: Vec::new(), @@ -123,7 +161,7 @@ impl Recording { } } /// A straight track from `from` to `to` over the ticks `first..=last` (every `STEP`). - pub(crate) fn straight(&mut self, id: u32, what: What, first: u32, last: u32, from: DVec3, to: DVec3) { + pub fn straight(&mut self, id: u32, what: What, first: u32, last: u32, from: DVec3, to: DVec3) { let n = ((last - first) / STEP).max(1) as usize; let points = (0..=n).map(|k| { let p = from.lerp(to, k as f64 / n as f64); @@ -153,6 +191,10 @@ pub enum What { Drone(DroneKind), /// An interceptor and the system that fired it. Missile(MissileKind, u32), + /// A counter-strike missile on its way to a launch site (#109). + Strike, + /// An explicitly profiled ground strike and its physical carrier. + StrikeCarrier(u32), } pub struct Track { @@ -195,6 +237,8 @@ pub enum StrikeResult { Intercepted, /// Off the map: its launches ended, nothing to see. Halted, + /// It met the land or a building short of its aim (#106): it went off there. + Blocked, } #[derive(Clone, Copy, Debug)] @@ -204,6 +248,65 @@ pub struct Event { pub kind: EventKind, } +/// Measured scene transitions. These are facts, not inferred cinematic beats. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SceneKind { + MissileLaunch { missile: u32, sam: u32, target: u32, kind: MissileKind, sound: &'static str, pitch: f32 }, + MotorIgnition { missile: u32, kind: MissileKind, sound: Option<&'static str> }, + MotorBurnout { missile: u32, kind: MissileKind }, + MotorEnergyLost { missile: u32, kind: MissileKind, speed: f64 }, + TrackAcquired { sam: u32, target: u32, ever_own: bool }, + TrackLost { sam: u32, target: u32 }, + TrackSource { sam: u32, target: u32, ever_own: bool }, + TrackLock { sam: u32, target: u32, locked: bool }, + FireStarted { fire: u32, age: u32, life: u32, size: f32, quiet: bool, shown: bool, water: Option }, + FireChanged { fire: u32, age: u32, life: u32, size: f32, quiet: bool, shown: bool, water: Option }, + FireEnded { fire: u32 }, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct SceneEvent { + pub at: u32, + pub pos: DVec3, + pub kind: SceneKind, +} + +/// At most this many scene transitions per recording; overflow is explicitly marked. +const MAX_SCENE_EVENTS: usize = 65_536; + +#[derive(Default)] +pub struct SceneJournal { + events: Vec, + pub truncated: bool, + tracks: HashMap<(u32, u32), (bool, bool, DVec3)>, + fires: HashMap)>, + fire_events: Vec, + launches: HashMap, + ignitions: HashMap, + burnouts: HashMap, +} + +impl SceneJournal { + /// Read-only transitions in chronological order. + pub fn events(&self) -> &[SceneEvent] { &self.events } + + #[cfg(feature = "test-fixtures")] + pub fn fixture_push(&mut self, event: SceneEvent) { self.push(event); } + + fn push(&mut self, event: SceneEvent) { + if self.events.len() >= MAX_SCENE_EVENTS { self.truncated = true; return; } + let index = self.events.len(); + match event.kind { + SceneKind::MissileLaunch { missile, .. } => { self.launches.insert(missile, index); } + SceneKind::MotorIgnition { missile, .. } => { self.ignitions.insert(missile, index); } + SceneKind::MotorBurnout { missile, .. } => { self.burnouts.insert(missile, index); } + SceneKind::FireStarted { .. } | SceneKind::FireChanged { .. } | SceneKind::FireEnded { .. } => self.fire_events.push(index), + _ => {} + } + self.events.push(event); + } +} + /// Why interceptors and systems failed, as far as it was counted: only these are known. #[derive(Clone, Copy, Default, PartialEq, Debug)] pub struct Tally { @@ -211,6 +314,8 @@ pub struct Tally { pub missed: u32, /// Interceptors whose life ran out with the target still flying. pub expired: u32, + /// Interceptors whose post-burn speed fell below their guidance threshold. + pub energy_lost: u32, /// Interceptors whose target was gone (shot down by another, or lost) before they arrived. pub orphaned: u32, /// Times a system lost the track of the target it held, which was still in the sky. @@ -228,6 +333,7 @@ impl Tally { Tally { missed: self.missed.saturating_sub(start.missed), expired: self.expired.saturating_sub(start.expired), + energy_lost: self.energy_lost.saturating_sub(start.energy_lost), orphaned: self.orphaned.saturating_sub(start.orphaned), tracks_lost: self.tracks_lost.saturating_sub(start.tracks_lost), ammo_waits: self.ammo_waits.saturating_sub(start.ammo_waits), @@ -240,6 +346,7 @@ impl Tally { let parts: Vec = [ (self.missed, "промахов по взрывателю"), (self.expired, "ракет выработали ресурс"), + (self.energy_lost, "ракет потеряли скорость после выгорания"), (self.orphaned, "ракет остались без цели"), (self.tracks_lost, "потерь трека"), (self.ammo_waits, "ожиданий БК"), @@ -263,6 +370,8 @@ pub struct Recording { pub tracks: Vec, index: HashMap, pub events: Vec, + /// None is the legacy event/track-only format. + pub scene: Option, /// The failures counted during the raid. pub tally: Tally, /// The systems and objects as they stood: (id, kind, where). @@ -341,6 +450,11 @@ pub struct ReplayFire { pub start: u32, pub life: u32, pub size: f32, + /// Age already accumulated before recording began. + pub age_offset: u32, + pub quiet: bool, + pub shown: bool, + pub water: Option, /// The event that lit it (the models' ids). pub key: usize, } @@ -357,7 +471,7 @@ pub fn strike_power(result: StrikeResult) -> u32 { match result { StrikeResult::Destroyed => 3, StrikeResult::Stunned => 2, - StrikeResult::Missed => 1, + StrikeResult::Missed | StrikeResult::Blocked => 1, StrikeResult::Intercepted | StrikeResult::Halted => 0, } } @@ -379,7 +493,7 @@ pub fn blast_spec(kind: EventKind) -> (f32, u32) { EventKind::Split { .. } => (2.0, 7 * 9), EventKind::Strike { result, .. } => match result { StrikeResult::Destroyed => (SIZE[4], LIFE[4] * 9), - StrikeResult::Stunned | StrikeResult::Missed => (SIZE[3], LIFE[3] * 9), + StrikeResult::Stunned | StrikeResult::Missed | StrikeResult::Blocked => (SIZE[3], LIFE[3] * 9), StrikeResult::Intercepted => (SIZE[0], LIFE[0] * 9), StrikeResult::Halted => (0.0, 0), }, @@ -390,13 +504,24 @@ pub fn blast_spec(kind: EventKind) -> (f32, u32) { /// `Blast`s the live simulation keeps, with `start` in the recording's ticks and the event's /// own id range. A pure function of `t`, so a seek or a pause shows the same frame. pub fn replay_blasts(rec: &Recording, t: f64) -> Vec { + replay_blasts_from_events(rec, t, false) +} + +/// Recorded smoke billows persist after the short visual blast expires. Keep the same event +/// classification and geometry within the bounded 900-tick smoke window. +pub fn replay_smoke_blasts(rec: &Recording, t: f64) -> Vec { + replay_blasts_from_events(rec, t, true) +} + +fn replay_blasts_from_events(rec: &Recording, t: f64, keep_expired: bool) -> Vec { rec.events .iter() .enumerate() .filter_map(|(k, e)| { let (size, life) = blast_spec(e.kind); let age = t - e.at as f64; - (0.0..life as f64).contains(&age).then_some(Blast { id: 0x4000_0000 | k as u32, at: e.pos, size, start: e.at as u64, life }) + (if keep_expired { (0.0..=900.0).contains(&age) } else { (0.0..life as f64).contains(&age) }) + .then_some(Blast { id: 0x4000_0000 | k as u32, at: e.pos, size, start: e.at as u64, life }) }) .collect() } @@ -405,6 +530,24 @@ pub fn replay_blasts(rec: &Recording, t: f64) -> Vec { /// feed it (the greater size, the longer life), as `Sim::ignite` does; those after it died light /// a new one. A pure function of `t`. pub fn replay_fires(rec: &Recording, t: f64) -> Vec { + if let Some(scene) = rec.scene.as_ref().filter(|s| !s.truncated) { + let mut active: HashMap = HashMap::new(); + for &index in &scene.fire_events { + let e = &scene.events[index]; + if e.at as f64 > t { break; } + match e.kind { + SceneKind::FireStarted { fire, age, life, size, quiet, shown, water } + | SceneKind::FireChanged { fire, age, life, size, quiet, shown, water } => { + active.insert(fire, ReplayFire { at: e.pos, start: e.at.saturating_sub(age), age_offset: age.saturating_sub(e.at), life, size, quiet, shown, water, key: fire as usize }); + } + SceneKind::FireEnded { fire } => { active.remove(&fire); } + _ => {} + } + } + let mut fires: Vec<_> = active.into_values().filter(|f| t - f.start as f64 + (f.age_offset as f64) < f.life as f64).collect(); + fires.sort_by_key(|f| f.key); + return fires; + } let mut fires: Vec = Vec::new(); for (k, e) in rec.events.iter().enumerate() { if e.at as f64 > t { @@ -423,7 +566,7 @@ pub fn replay_fires(rec: &Recording, t: f64) -> Vec { f.size = f.size.max(size); f.life = f.life.max(e.at - f.start + life); } else { - fires.push(ReplayFire { at: e.pos, start: e.at, life, size, key: k }); + fires.push(ReplayFire { at: e.pos, start: e.at, life, size, age_offset: 0, quiet: false, shown: true, water: None, key: k }); } } fires.retain(|f| t < (f.start + f.life) as f64); @@ -440,6 +583,7 @@ impl Recording { tracks: Vec::new(), index: HashMap::new(), events: Vec::new(), + scene: Some(SceneJournal::default()), tally: Tally::default(), sams: sim.sams.iter().map(|s| (s.id, s.kind, s.pos)).collect(), objects: sim.targets.clone(), @@ -472,6 +616,82 @@ impl Recording { } } } + /// Actual launch, if observed during this recording. Missing is not proof of no launch: + /// the recording may have started late or the bounded journal may have filled. + pub fn missile_launch(&self, missile: u32) -> Option<&SceneEvent> { + let scene = self.scene.as_ref()?; + scene.events.get(*scene.launches.get(&missile)?) + } + + /// Actual motor ignition, when it was recorded. + pub fn missile_ignition(&self, missile: u32) -> Option<&SceneEvent> { + let scene = self.scene.as_ref()?; + scene.events.get(*scene.ignitions.get(&missile)?) + } + + /// Recorded ignition takes precedence; older/incomplete tracks retain their original fallback. + pub fn missile_is_lit(&self, tr: &Track, t: f64, cold: u32) -> bool { + if let Some(scene) = &self.scene + && let Some(index) = scene.burnouts.get(&tr.id) + && t >= scene.events[*index].at as f64 { return false; } + if let Some(event) = self.missile_ignition(tr.id) { + let burn = match tr.what { What::Missile(kind, _) => kind.burn_ticks(), _ => return false }; + return t >= event.at as f64 && t < event.at as f64 + burn as f64; + } + if self.missile_launch(tr.id).is_some() && self.scene.as_ref().is_some_and(|s| !s.truncated) { return false; } + let age = t - tr.first as f64; + let burn = match tr.what { What::Missile(kind, _) => kind.burn_ticks(), _ => return false }; + age > cold as f64 && age <= (cold + burn) as f64 + } + + fn note_tracks(&mut self, sams: &[Sam]) { + let Some(journal) = &mut self.scene else { return }; + let mut current = HashMap::new(); + for sam in sams { + for track in &sam.tracks { + let key = (sam.id, track.id); + let state = (track.own_tick > 0, track.locked, track.pos); + let old = journal.tracks.get(&key).copied(); + if old.is_none() { + journal.push(SceneEvent { at: self.len, pos: track.pos, kind: SceneKind::TrackAcquired { sam: sam.id, target: track.id, ever_own: state.0 } }); + } else if old.is_some_and(|old| old.0 != state.0) { + journal.push(SceneEvent { at: self.len, pos: track.pos, kind: SceneKind::TrackSource { sam: sam.id, target: track.id, ever_own: state.0 } }); + } + if (old.is_none() && state.1) || old.is_some_and(|old| old.1 != state.1) { + journal.push(SceneEvent { at: self.len, pos: track.pos, kind: SceneKind::TrackLock { sam: sam.id, target: track.id, locked: state.1 } }); + } + current.insert(key, state); + } + } + let mut lost: Vec<_> = journal.tracks.iter().filter(|(key, _)| !current.contains_key(key)).map(|(key, state)| (*key, state.2)).collect(); + lost.sort_by_key(|(key, _)| *key); + for ((sam, target), pos) in lost { + journal.push(SceneEvent { at: self.len, pos, kind: SceneKind::TrackLost { sam, target } }); + } + journal.tracks = current; + } + + /// Sample actual fire state after the simulation update, including existing fires at start. + fn note_fires(&mut self, fires: &[fires::Fire], now: u64) { + let Some(journal) = &mut self.scene else { return }; + let mut current = HashMap::new(); + for fire in fires { + let state = (fire.at, fire.life, fire.size, fire.quiet, fire.shown, fire.surface_is_water()); + let age = now.saturating_sub(fire.start).min(u32::MAX as u64) as u32; + let kind = match journal.fires.get(&fire.id) { + None => Some(SceneKind::FireStarted { fire: fire.id, age, life: fire.life, size: fire.size, quiet: fire.quiet, shown: fire.shown, water: state.5 }), + Some(old) if *old != state => Some(SceneKind::FireChanged { fire: fire.id, age, life: fire.life, size: fire.size, quiet: fire.quiet, shown: fire.shown, water: state.5 }), + _ => None, + }; + if let Some(kind) = kind { journal.push(SceneEvent { at: self.len, pos: fire.at, kind }); } + current.insert(fire.id, state); + } + let mut ended: Vec<_> = journal.fires.iter().filter(|(id, _)| !current.contains_key(id)).map(|(id, state)| (*id, state.0)).collect(); + ended.sort_by_key(|(id, _)| *id); + for (fire, pos) in ended { journal.push(SceneEvent { at: self.len, pos, kind: SceneKind::FireEnded { fire } }); } + journal.fires = current; + } + /// The game's tick at the recording's tick `t` (fractional), if it was known: the samples run /// on at the pace of the ticks between them (a pause or a gap is a step). pub fn world_tick(&self, t: f64) -> Option { @@ -520,6 +740,15 @@ impl Recording { pub fn track(&self, id: u32) -> Option<&Track> { self.index.get(&id).map(|&i| &self.tracks[i]) } + /// Sampled pose at the replay time, falling back to the old static snapshot. + pub fn sam_at(&self, id: u32, t: f64) -> Option<(SamKind, DVec3)> { + if let Some(track) = self.sam_tracks.iter().find(|s| s.id == id) { + let pose = track.pose(t)?; + return Some((track.kind, self.origin + DVec3::new(pose.pos[0] as f64, pose.pos[1] as f64, pose.pos[2] as f64))); + } + self.sam(id) + } + /// The system with this id as it stood. pub fn sam(&self, id: u32) -> Option<(SamKind, DVec3)> { self.sams.iter().find(|s| s.0 == id).map(|s| (s.1, s.2)) @@ -542,18 +771,17 @@ impl Sim { } } - /// Something worth a replay happened. - pub(super) fn log(&mut self, pos: DVec3, kind: EventKind) { - match kind { - EventKind::Crash { power, .. } => self.ignite(pos, power, None), - EventKind::Hit { power, sam, .. } => { - let host = sam.and_then(|(_, i)| self.sams.get(i)).map(|s| s.id); - self.ignite(pos, power.max(1), host) + /// Record an actual transition without modifying gameplay or random state. + pub(super) fn scene_event(&mut self, pos: DVec3, kind: SceneKind) { + if let Some(r) = &mut self.rec { + if let Some(journal) = &mut r.scene { + journal.push(SceneEvent { at: r.len, pos, kind }); } - EventKind::SamLost { sam, .. } => self.ignite(pos, 2, Some(sam)), - EventKind::Strike { result, .. } if strike_power(result) > 0 => self.ignite(pos, strike_power(result), None), - _ => {} } + } + + /// Append an already-applied fact. This never changes the live world. + pub(super) fn record_event(&mut self, pos: DVec3, kind: EventKind) { if let Some(r) = &mut self.rec { let at = r.len; if let EventKind::Kill { drone, .. } = kind @@ -570,20 +798,31 @@ impl Sim { } } - /// Once a tick: starts a recording with the first drone, adds the tracks' points, ends it - /// when the sky has been clear 5 s. - pub(super) fn record(&mut self) { - // Wrecks still falling (far off they fall a long while) don't keep it going. - let busy = self.drones.iter().any(|d| d.alive()) || !self.missiles.is_empty() || !self.queue.is_empty(); - if self.rec.is_none() { - // It starts with the first drone in the sky (not with a raid's drones still waiting). - if !self.drones.iter().any(|d| d.alive()) { - return; - } + /// Open before the combat update so transitions on the first active tick are not lost. + pub(super) fn begin_recording_if_active(&mut self) { + if self.rec.is_none() && (self.drones.iter().any(|d| d.alive()) || !self.strikes.flights.is_empty() || !self.strikes.pending.is_empty()) { self.recordings += 1; self.rec = Some(Recording::new(self.recordings, self)); } + } + + pub(super) fn record_strike_launch(&mut self, id: u32, carrier: u32, at: DVec3) { + self.begin_recording_if_active(); + if let Some(r) = &mut self.rec { + r.point(0x7100_0000 | id, What::StrikeCarrier(carrier), at); + } + } + + /// Once a tick: starts a recording with the first drone or counter-strike in the air, adds the tracks' points, + /// ends it when the sky has been clear 5 s. + pub(super) fn record(&mut self) { + // Wrecks still falling (far off they fall a long while) don't keep it going. A counter-strike is a recorded + // object in its own right (#109): it holds the recording open until it lands, whatever the sky is doing. + let busy = self.drones.iter().any(|d| d.alive()) || !self.missiles.is_empty() || !self.queue.is_empty() || !self.strikes.flights.is_empty() || !self.strikes.pending.is_empty(); + self.begin_recording_if_active(); let Some(r) = &mut self.rec else { return }; + r.note_tracks(&self.sams); + r.note_fires(&self.fires.list, self.tick); if r.len % TIME_STEP == 0 { if let Some(dt) = self.day_time { r.times.push((r.len, dt)); @@ -599,13 +838,29 @@ impl Sim { for m in &self.missiles { r.point(m.id, What::Missile(m.kind, m.sam), m.pos); } + for f in self.strikes.flights.iter().filter(|f| f.carrier.is_none()) { + r.point(f.track(), What::Strike, f.pos); + } + } + // Physical strikes start with an exact muzzle point at launch. Continue that track on a + // flight-relative STEP grid; global recording parity can be offset by the launch tick. + for f in &self.strikes.flights { + let Some(carrier) = f.carrier else { continue }; + let age = self.tick.saturating_sub(f.start); + if age > 0 && age % STEP as u64 == 0 { + r.point(f.track(), What::StrikeCarrier(carrier), f.pos); + } } r.len += 1; r.quiet = if busy { 0 } else { r.quiet + 1 }; if r.quiet >= 100 || r.len >= LONGEST { let mut done = self.rec.take().unwrap(); + if let Some(scene) = &mut done.scene { + scene.tracks = HashMap::new(); + scene.fires = HashMap::new(); + } done.len = done.len.saturating_sub(done.quiet.saturating_sub(40)); - if !done.events.is_empty() { + if !done.events.is_empty() || done.scene.as_ref().is_some_and(|s| !s.events.is_empty()) { self.replays.push(done); if self.replays.len() > KEPT { self.replays.remove(0); @@ -660,11 +915,28 @@ impl Sim { let spec = kind.spec(); let rot = look(dir, 0.0) * DQuat::from_rotation_z(t * 0.3); out.model(id, spec.model, pos, rot, spec.scale, false); - if t - tr.first as f64 > spec.cold as f64 && spec.flame > 0.0 { + if rec.missile_is_lit(tr, t, spec.cold) && spec.flame > 0.0 { let back = (spec.rear - 8.0) / 16.0 * spec.scale + spec.flame; out.model(id + 1, "airdef:flame", pos + rot * DVec3::new(0.0, 0.0, back), rot, spec.flame, true); } } + What::StrikeCarrier(_) => super::strike::round_visual( + out, + id, + pos, + look(dir, 0.0), + t - tr.first as f64 >= super::strike::EJECT_TICKS as f64, + ), + What::Strike => { + // The same model, flame and turn the live flight drew (#106, #109). + let spec = DroneKind::Kalibr.spec(); + let rot = look(dir, 0.0); + out.model(id, spec.model, pos, rot, spec.scale, false); + if let Engine::Jet(up, back, size) = spec.engine { + let flicker = 0.7 + 0.3 * (t * 1.7).sin().abs(); + out.model(id + 1, "airdef:geran3_flame", pos + rot * DVec3::new(0.0, up * spec.scale, back * spec.scale), rot, spec.scale * size * flicker, true); + } + } } } // The air-defence systems as they stood: where, which way, the launcher or turret where it @@ -685,8 +957,10 @@ impl Sim { // Fires: what the events left burning (the flames dim and the smoke thickens with time), // out when their life ends. for f in replay_fires(rec, t) { - let age = t - f.start as f64; - let (flame, smoke) = fires::replay_fire_of(f.size, f.life, age); + let age = t - f.start as f64 + f.age_offset as f64; + let (mut flame, mut smoke) = fires::replay_fire_of(f.size, f.life, age); + if f.water == Some(true) { flame = 0.0; smoke *= 0.5; } + if f.quiet || !f.shown { flame = 0.0; } let id = base + 0x00c0_0000 + f.key as u32 * PARTS; if flame > 0.05 { let wob = 1.0 + 0.15 * (age * 0.9 + f.key as f64).sin(); @@ -763,6 +1037,7 @@ mod tests { tracks: Vec::new(), index: HashMap::new(), events: Vec::new(), + scene: None, tally: Tally::default(), sams: Vec::new(), objects: Vec::new(), @@ -774,6 +1049,126 @@ mod tests { } } + #[test] + fn replay_seeks_radar_emission_stepwise() { + let mut r = recording(); + let sam = Sam::new(1, SamKind::Radar, DVec3::ZERO, 0.0); + r.note_sams(&[sam], 0); + let mut off = Sam::new(1, SamKind::Radar, DVec3::ZERO, 0.0); + off.radar_on = false; + r.note_sams(&[off], 10); + assert!(r.sam_tracks[0].ghost(r.origin, 9.0, 1).unwrap().radar_on); + assert!(!r.sam_tracks[0].ghost(r.origin, 10.0, 1).unwrap().radar_on); + assert!(r.sam_tracks[0].ghost(r.origin, 0.0, 1).unwrap().radar_on); + } + + #[test] + fn replay_preserves_launcher_configuration_and_individual_cover_poses() { + let mut r = recording(); + let mut sam = sam_at(0.0, 0.0, 0.0); + sam.set_mode(battery::SamMode::LauncherOnly); + sam.cap_cell = 2; + sam.cap_open = 0.5; + sam.spent_cells = 1; + r.note_sams(&[sam], 10); + for t in [10.0, 40.0, 10.0] { + let ghost = r.sam_tracks[0].ghost(r.origin, t, 160).unwrap(); + assert_eq!(ghost.mode, battery::SamMode::LauncherOnly); + assert_eq!((ghost.spent_cells, ghost.cap_cell, ghost.cap_open), (1, 2, 0.5)); + let mut out = Out::default(); + units::draw_sam_model(&ghost, &mut Rng::new(1), &mut out); + assert_eq!(out.models.iter().filter(|m| m.name == "airdef:tpk_cap").count(), 4); + assert!(!out.models.iter().any(|m| m.name == "airdef:radar_panel")); + } + } + + fn physical_strike(record_len: Option) -> (Sim, u32, DVec3) { + use crate::sim::sites::{LaunchSite, SiteClass}; + use crate::sim::strike::{Estimate, Known, SiteState}; + use glam::DVec2; + + let mut sim = Sim::new(11); + sim.tick = 100 + u64::from(record_len.unwrap_or(0)); + if let Some(len) = record_len { + let mut rec = Recording::new(1, &sim); + rec.len = len; + sim.rec = Some(rec); + } + let site = 1; + sim.sites.list.push(LaunchSite { + id: site, + side: 0, + sector: 0, + bearing: 0.0, + class: SiteClass::Ramp, + pos: DVec3::new(0.0, 64.0, 300.0), + range: 300.0, + water: false, + stock: 0, + planned: 0, + alive: true, + next_free: 0, + offmap: false, + }); + sim.strikes.known.push(Known { + site, + class: SiteClass::Ramp, + obs: Vec::new(), + est: Some(Estimate { + pos: DVec2::new(0.0, 300.0), + radius: 5.0, + fix: true, + bearing: 0.0, + bearing_err: 0.01, + origin: DVec2::ZERO, + }), + launches: 1, + last: 0, + state: SiteState::Active, + aimed: false, + }); + sim.order_test_strike(site, DVec3::new(0.0, 64.0, 0.0), &mut Out::default()) + .unwrap(); + let flight = &mut sim.strikes.flights[0]; + let (id, muzzle) = (flight.id, flight.pos); + let carrier = 77; + flight.carrier = Some(carrier); + sim.record_strike_launch(id, carrier, muzzle); + (sim, id, muzzle) + } + + #[test] + fn physical_strike_track_keeps_muzzle_and_samples_on_flight_relative_grid() { + // A fresh recording starts at launch; the other cases launch into odd/even positions on + // an existing recording's global grid. + for record_len in [None, Some(1), Some(2)] { + let (mut sim, id, muzzle) = physical_strike(record_len); + let track_id = 0x7100_0000 | id; + { + let rec = sim.rec.as_ref().unwrap(); + let track = rec.track(track_id).unwrap(); + assert_eq!(track.what, What::StrikeCarrier(77)); + assert_eq!(track.points.len(), 1, "the launch records exactly one muzzle point"); + assert_eq!(track.at(rec.origin, track.first as f64), Some(muzzle)); + } + + for age in 1..=4u64 { + sim.tick += 1; + let pos = muzzle + DVec3::Z * (age as f64 * 8.0); + sim.strikes.flights[0].pos = pos; + sim.record(); + let rec = sim.rec.as_ref().unwrap(); + let track = rec.track(track_id).unwrap(); + let expected_samples = 1 + (age / STEP as u64) as usize; + assert_eq!(track.points.len(), expected_samples, "flight age {age}, recording len {}", record_len.unwrap_or(0)); + assert_eq!(track.at(rec.origin, track.first as f64), Some(muzzle)); + if age % STEP as u64 == 0 { + assert_eq!(track.at(rec.origin, (track.first + age as u32) as f64), Some(pos)); + } + } + } + } + #[test] fn a_system_keeps_its_pose_through_stillness_and_moves_when_it_moved() { let mut r = recording(); @@ -793,6 +1188,155 @@ mod tests { assert!((pose(100.0).pos[0] - 4.0).abs() < 1e-6); } + #[test] + fn recorder_is_passive_and_combat_effects_do_not_depend_on_it() { + let kind = EventKind::Crash { drone: 1, kind: DroneKind::Shahed, power: 2 }; + let mut recording = Sim::new(4); + recording.rec = Some(Recording::new(1, &recording)); + recording.record_event(DVec3::ZERO, kind); + assert!(recording.fires.list.is_empty(), "recording a fact must not ignite the live world"); + assert_eq!(recording.rec.as_ref().unwrap().events.len(), 1); + let mut without = Sim::new(4); + without.emit_combat_event(DVec3::ZERO, kind); + assert_eq!(without.fires.list.len(), 1, "the domain effect must run without a recorder"); + let mut with = Sim::new(4); + with.rec = Some(Recording::new(1, &with)); + with.emit_combat_event(DVec3::ZERO, kind); + assert_eq!(with.fires.list.len(), 1); + assert_eq!(with.rec.as_ref().unwrap().events.len(), 1); + assert_eq!(with.fires.list[0].size, without.fires.list[0].size); + } + + #[test] + fn recorded_sam_health_and_parts_are_stepwise_and_seekable() { + let mut r = recording(); + let mut sam = Sam::new(7, SamKind::Patriot, DVec3::ZERO, 0.0); + r.note_sams(std::slice::from_ref(&sam), 0); + sam.health = 0.4; + sam.comps.0[Part::Sensor as usize] = 0.25; + r.note_sams(std::slice::from_ref(&sam), 10); + let st = &r.sam_tracks[0]; + let before = st.ghost(r.origin, 9.0, 7).unwrap(); + let damaged = st.ghost(r.origin, 10.0, 7).unwrap(); + assert_eq!(before.health, 1.0); + assert!((damaged.health - 0.4).abs() < 1e-6); + assert_eq!(damaged.comps.get(Part::Sensor), 0.25); + assert_eq!(st.ghost(r.origin, 10.0, 7).unwrap().comps, damaged.comps); + } + + #[test] + fn sampled_track_and_fire_transitions_follow_real_changes() { + let mut sim = Sim::new(3); + let id = sim.id(); + sim.sams.push(Sam::new(id, SamKind::Patriot, DVec3::ZERO, 0.0)); + let mut r = Recording::new(1, &sim); + let tr = sight::Track { id: 77, kind: DroneKind::Shahed, pos: DVec3::Z * 100.0, vel: -DVec3::Z, tick: 1, own_tick: 1, ttl: 10, hits: 1, locked: false }; + sim.sams[0].tracks.push(tr); + r.note_tracks(&sim.sams); + r.len = 1; + sim.sams[0].tracks[0].locked = true; + r.note_tracks(&sim.sams); + r.len = 2; + sim.sams[0].tracks.clear(); + r.note_tracks(&sim.sams); + let events = &r.scene.as_ref().unwrap().events; + assert!(matches!(events[0].kind, SceneKind::TrackAcquired { sam, target: 77, ever_own: true } if sam == id)); + assert!(matches!(events[1].kind, SceneKind::TrackLock { sam, target: 77, locked: true } if sam == id)); + assert!(matches!(events[2].kind, SceneKind::TrackLost { sam, target: 77 } if sam == id)); + + sim.ignite(DVec3::ZERO, 1, None); + let fire = sim.fires.list[0].id; + r.note_fires(&sim.fires.list, sim.tick); + assert_eq!(replay_fires(&r, 2.0)[0].size, 1.0); + sim.ignite(DVec3::ZERO, 4, None); + r.len += 1; + r.note_fires(&sim.fires.list, sim.tick); + assert_eq!(replay_fires(&r, 3.0)[0].size, 3.0); + assert_eq!(replay_fires(&r, 2.0)[0].size, 1.0, "seeking back uses old fire state"); + sim.fires.extinguish(); + r.len += 1; + r.note_fires(&sim.fires.list, sim.tick); + assert!(replay_fires(&r, 4.0).is_empty()); + let events = &r.scene.as_ref().unwrap().events; + assert!(events.iter().any(|e| matches!(e.kind, SceneKind::FireStarted { fire: id, .. } if id == fire))); + assert!(events.iter().any(|e| matches!(e.kind, SceneKind::FireChanged { fire: id, size, .. } if id == fire && size == 3.0))); + assert!(events.iter().any(|e| matches!(e.kind, SceneKind::FireEnded { fire: id } if id == fire))); + } + + #[test] + fn replay_fire_uses_sampled_existing_age_water_and_end() { + let mut r = recording(); + let mut scene = SceneJournal::default(); + scene.push(SceneEvent { at: 0, pos: DVec3::X, kind: SceneKind::FireStarted { fire: 7, age: 50, life: 100, size: 1.0, quiet: false, shown: true, water: Some(true) } }); + scene.push(SceneEvent { at: 10, pos: DVec3::X, kind: SceneKind::FireEnded { fire: 7 } }); + r.scene = Some(scene); + let a = replay_fires(&r, 5.0); + assert_eq!(a.len(), 1); + assert_eq!((a[0].start, a[0].age_offset, a[0].water), (0, 50, Some(true))); + assert!(replay_fires(&r, 11.0).is_empty()); + let b = replay_fires(&r, 5.0); + assert_eq!((a[0].start, a[0].age_offset, a[0].water), (b[0].start, b[0].age_offset, b[0].water)); + } + + #[test] + fn scene_journal_is_bounded_and_incomplete_is_marked() { + let mut j = SceneJournal::default(); + for i in 0..=MAX_SCENE_EVENTS { + j.push(SceneEvent { at: i as u32, pos: DVec3::ZERO, kind: SceneKind::FireEnded { fire: i as u32 } }); + } + assert_eq!(j.events.len(), MAX_SCENE_EVENTS); + assert!(j.truncated); + } + + #[test] + fn ten_minute_transition_stream_is_bounded() { + let started = std::time::Instant::now(); + let mut sim = Sim::new(5); + for _ in 0..4 { + let id = sim.id(); + let mut sam = Sam::new(id, SamKind::Patriot, DVec3::ZERO, 0.0); + for target in 1..=4 { + sam.tracks.push(sight::Track { id: target, kind: DroneKind::Shahed, pos: DVec3::Z, vel: DVec3::ZERO, tick: 1, own_tick: 1, ttl: 1, hits: 1, locked: false }); + } + sim.sams.push(sam); + } + let mut r = Recording::new(1, &sim); + for t in 0..12_000 { + r.len = t; + for sam in &mut sim.sams { for tr in &mut sam.tracks { tr.locked = t % 2 == 0; } } + r.note_tracks(&sim.sams); + } + let scene = r.scene.as_ref().unwrap(); + assert_eq!(scene.events.len(), MAX_SCENE_EVENTS); + assert!(scene.truncated); + eprintln!("scene stress: 12000 ticks, {} events, event storage {:.2} MiB, elapsed {:?}", scene.events.len(), scene.events.capacity() * std::mem::size_of::() / (1024 * 1024), started.elapsed()); + } + + #[test] + fn real_launch_and_ignition_are_recorded_with_ids_and_sound() { + let mut sim = Sim::new(0x87); + let sam = sim.id(); + sim.sams.push(Sam::new(sam, SamKind::Patriot, DVec3::new(0.0, 64.0, 0.0), 0.0)); + let mut out = Out::default(); + for (i, kind) in [DroneKind::Shahed, DroneKind::Kh101, DroneKind::Iskander].into_iter().enumerate() { + sim.launch(kind, DVec3::new(i as f64 * 100.0, 110.0, 800.0), -DVec3::Z, DVec3::new(0.0, 64.0, 0.0), &mut out); + } + let mut ground = Ground::default(); + for _ in 0..600 { + let wanted = ground.take_wanted(); + ground.answer(wanted.into_iter().map(|(x, z)| (x, z, 64)).collect(), sim.tick); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + if sim.rec.as_ref().and_then(|r| r.scene.as_ref()).is_some_and(|j| j.events.iter().any(|e| matches!(e.kind, SceneKind::MotorIgnition { .. }))) { break; } + } + let r = sim.rec.as_ref().expect("recording"); + let launch = r.scene.as_ref().unwrap().events.iter().find(|e| matches!(e.kind, SceneKind::MissileLaunch { .. })).expect("actual launch"); + let missile = match launch.kind { SceneKind::MissileLaunch { missile, sam: source, sound, .. } => { assert_eq!(source, sam); assert_eq!(sound, "airdef:launch.patriot"); missile }, _ => unreachable!() }; + let ignition = r.missile_ignition(missile).expect("actual ignition"); + assert!(ignition.at >= launch.at); + assert_eq!(r.missile_launch(missile).unwrap().pos, launch.pos); + } + // #76: a replay's searchlights point where the recording had them, from the recorded pose. #[test] fn a_replay_has_the_recorded_searchlight_beams() { @@ -865,7 +1409,7 @@ mod tests { sim.sams.push(Sam::new(sam, SamKind::Patriot, DVec3::new(0.0, 70.0, 0.0), 0.0)); sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 120.0, 300.0), DVec3::X, DVec3::new(900.0, 70.0, 0.0)); let at = sim.drones[0].pos; - let live = sim.shoot_down_by(0, sam, None, Impact::missile(MissileKind::M77n6, at, 6.0), &mut Out::default()).unwrap(); + let live = sim.shoot_down_by(0, sam, None, Impact::missile(MissileKind::M77n6, at, 6.0), &Ground::default(), &mut Out::default()).unwrap(); let rec = sim.rec.take().unwrap(); let recorded = rec.events.iter().find_map(|e| if let EventKind::Kill { outcome, .. } = e.kind { Some(outcome) } else { None }); assert_eq!(recorded, Some(live), "the live outcome is what was recorded"); diff --git a/airdef-mod/src/sim/regress.rs b/airdef-mod/src/sim/regress.rs index f21afd7..b1894ad 100644 --- a/airdef-mod/src/sim/regress.rs +++ b/airdef-mod/src/sim/regress.rs @@ -51,10 +51,12 @@ fn network_launcher_must_contribute_to_route_risk() { let id=sim.id();sim.sams.push(Sam::new(id,SamKind::P1sun,DVec3::new(0.,70.,0.),0.)); let id=sim.id();sim.sams.push(Sam::new(id,SamKind::Radar,DVec3::new(0.,70.,0.),0.)); let grid=Grid{min:DVec3::new(-160.,0.,-160.),cell:16.,size:20}; - // #93: the planner prices only what the enemy knows; the radar emits, the launcher is not known until it gives itself away. + // The planner prices only observed units; neither unit is known initially. let (prices,_)=sim.price_grid(&grid,DroneKind::Shahed); assert!(prices.iter().all(|p|*p==1.),"an unknown launcher must not be priced"); - sim.intel.revealed.push((sim.sams[0].id,DVec3::new(0.,70.,0.),0)); + // The launcher is seen firing without a sensor of its own; the radar is fixed emitting. + sim.intel.observe(&sim.sams[0],0,ew::Seen::Launch); + sim.intel.observe(&sim.sams[1],0,ew::Seen::Emission); let (prices,_)=sim.price_grid(&grid,DroneKind::Shahed); assert!(prices.iter().any(|p|*p>1.),"armed launcher with a network radar is invisible to route risk"); } @@ -64,7 +66,7 @@ fn network_launcher_must_contribute_to_route_risk() { fn repair_must_cancel_pending_magazine_explosions() { let mut sim=Sim::new(1); let id=sim.id();sim.sams.push(Sam::new(id,SamKind::Patriot,DVec3::new(0.,70.,0.),0.)); - sim.blast_damage(DVec3::new(0.,70.,0.),4,&mut Out::default()); + sim.blast_damage(DVec3::new(0.,70.,0.),4,&Ground::default(),&mut Out::default()); assert!(!sim.later.is_empty());sim.repair(); sim.tick=3; sim.step_later(&mut Ground::default(),None,&mut Out::default()); assert!(sim.sams[0].alive(),"a fully repaired launcher was destroyed again by its own pre-repair cook-off"); @@ -129,11 +131,14 @@ fn retargeted_smart_drone_gets_a_route_round_the_defences() { sim.sams.push(Sam::new(id, SamKind::Radar, DVec3::new(0.0, 70.0, 0.0), 0.0)); let goal = sim.id(); sim.sams.push(Sam::new(goal, SamKind::Ew, DVec3::new(0.0, 70.0, 1000.0), 0.0)); - sim.intel.revealed.push((goal, DVec3::new(0.0, 70.0, 1000.0), 0)); - // Its first target, a system that is destroyed by now. + sim.intel.observe(&sim.sams[2], 0, ew::Seen::Jamming); + // Its first target: a system the picture watched die — seen firing, then the enemy's + // munition watched the wreck. A unit destroyed unobserved would keep the drone coming. let id = sim.id(); let mut wreck = Sam::new(id, SamKind::Patriot, DVec3::new(3000.0, 70.0, -1000.0), 0.0); + sim.intel.observe(&wreck, 0, ew::Seen::Launch); wreck.health = 0.0; + sim.intel.outcome(wreck.id, false, wreck.pos, 0); sim.sams.push(wreck); sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 100.0, -1000.0), DVec3::Z, DVec3::new(3000.0, 70.0, -1000.0)); sim.drones[0].pos = DVec3::new(0.0, 100.0, -1000.0); @@ -145,6 +150,276 @@ fn retargeted_smart_drone_gets_a_route_round_the_defences() { assert!(d.route.len() > 1, "no route after the new target: it flies straight through the defences"); } +// #127: a mass retarget works its route jobs out over the following ticks. No drone is left without a +// route by the budget, old safe routes are kept until their replacements are ready, and the queue never +// grows or repeats a drone. +#[cfg(test)] +fn mass_retarget_sim(seed: u64) -> (Sim, HashMap>, u32) { + let mut sim = Sim::new(seed); + for i in 0..8 { + for j in 0..5 { + let at = DVec3::new(-560.0 + i as f64 * 160.0, 70.0, -320.0 + j as f64 * 160.0); + let id = sim.id(); + let sam = Sam::new(id, SamKind::S400, at, 0.0); + // Every system is on the picture, its radar fixed emitting once. + sim.intel.observe(&sam, 0, ew::Seen::Emission); + sim.sams.push(sam); + } + } + let mut old = HashMap::new(); + let mut empty = 0; + for i in 0..40 { + let from = DVec3::new(-1800.0, 100.0, -800.0 + i as f64 * 40.0); + let mut d = sim.make_drone(DroneKind::Kh22, from, DVec3::X, DVec3::new(1800.0, 70.0, 1800.0)); + d.smart = true; + d.route = vec![from, DVec3::new(1800.0, 70.0, 1800.0)]; + // The last drone starts with no route at all (a drone whose route was lost): its job must wait its + // budget, not be dropped, and must give it a route in the end. + if i == 39 { + d.route.clear(); + empty = d.id; + } + old.insert(d.id, d.route.clone()); + sim.queue.push((0, d)); + } + (sim, old, empty) +} + +#[cfg(test)] +fn flight_state(sim: &Sim) -> Vec<(u32, u64, u64, u64, Vec<(u64, u64, u64)>)> { + sim.queue + .iter() + .map(|(_, d)| { + let route = d.route.iter().map(|p| (p.x.to_bits(), p.y.to_bits(), p.z.to_bits())).collect(); + (d.id, d.target.x.to_bits(), d.target.y.to_bits(), d.target.z.to_bits(), route) + }) + .collect() +} + +#[test] +fn mass_retarget_finishes_its_route_jobs_over_the_budget() { + let (mut sim, old, empty) = mass_retarget_sim(7); + sim.tick = 40; + sim.adapt(); + + let pending = sim.retarget_ids(); + assert!(!pending.is_empty(), "the budget planned all 40 routes: the test no longer covers the queue"); + assert_eq!(pending.len(), pending.iter().collect::>().len(), "duplicate jobs"); + // Assignments are throttled too: one adaptive tick hands out a bounded number of new aims, + // and the rest of the force is sent on the following adaptive ticks (#127). + assert!(pending.len() <= super::ew::ASSIGN_PER_TICK, "one adaptive tick assigned {} drones", pending.len()); + for id in &pending { + let d = &sim.queue.iter().find(|(_, d)| d.id == *id).unwrap().1; + assert_eq!(&d.route, old.get(id).unwrap(), "an old safe route was replaced before the new one was ready"); + } + + let mut left = pending.len(); + for _ in 0..400 { + sim.tick += 1; + sim.adapt(); + if sim.tick % 40 == 0 { + // A new adaptive tick: more drones may be sent, still within the throttle. + let now = sim.retarget_ids().len(); + assert!(now <= left + super::ew::ASSIGN_PER_TICK, "an adaptive tick assigned more than {} drones", super::ew::ASSIGN_PER_TICK); + left = now; + } else if left > 0 { + assert!(sim.retargets.len() < left, "a budget tick made no progress: {left} jobs left"); + left = sim.retargets.len(); + } + let still = sim.retarget_ids(); + assert!( + // A drone the throttle has not sent yet is still on its own plan's route or has none to lose; + // one already sent at a unit whose route is empty must have a job that will fill it. + sim.queue.iter().all(|(_, d)| !d.route.is_empty() || still.contains(&d.id) || d.aim.unit == 0), + "a route is still empty with no job left to fill it" + ); + } + assert_eq!(sim.retargets.len(), 0, "route jobs were never finished"); + // No drone is left with an empty route for ever, and every new route ends at its aim. The drone + // that started with no route at all got one too: its job waited for the budget, not for ever. + for (_, d) in &sim.queue { + assert!(!d.route.is_empty(), "a drone was left with no route"); + assert!(d.route.last().is_some_and(|p| p.distance(d.target) < 1e-6), "the new route does not end at the new aim"); + assert!(d.aim.unit != 0, "a drone was never sent at a unit"); + } + let empty = sim.queue.iter().find(|(_, d)| d.id == empty).unwrap().1.clone(); + assert!(!empty.route.is_empty(), "the drone that started with no route never got one"); +} + +#[test] +fn mass_retarget_is_deterministic_and_never_duplicates_a_drone() { + let (mut a, _, _) = mass_retarget_sim(11); + let (mut b, _, _) = mass_retarget_sim(11); + a.tick = 40; + b.tick = 40; + a.adapt(); + b.adapt(); + assert_eq!(a.retarget_ids(), b.retarget_ids(), "the same seed left a different queue"); + assert_eq!(flight_state(&a), flight_state(&b), "the same seed planned different routes"); + for _ in 0..80 { + a.adapt(); + b.adapt(); + let ids = a.retarget_ids(); + assert_eq!(ids.len(), ids.iter().collect::>().len(), "a drone has two jobs"); + assert_eq!(a.retarget_ids(), b.retarget_ids(), "the queues drifted apart"); + assert_eq!(flight_state(&a), flight_state(&b), "the routes drifted apart"); + if ids.is_empty() { + break; + } + a.tick += 1; + b.tick += 1; + // Another adaptive tick while the queue is still being worked: a known aim must not enqueue again. + if a.tick % 40 == 0 { + a.adapt(); + b.adapt(); + let ids = a.retarget_ids(); + assert_eq!(ids.len(), ids.iter().collect::>().len(), "a repeat adapt duplicated a job"); + } + } +} + +#[cfg(test)] +fn relief_grid() -> Grid { + Grid { min: DVec3::new(-2560.0, 0.0, -2560.0), cell: 32.0, size: 160 } +} + +#[cfg(test)] +fn flat_relief() -> std::sync::Arc { + let g = relief_grid(); + std::sync::Arc::new(super::ew::KnownRelief::new(g, vec![70.0; (g.size * g.size) as usize])) +} + +#[cfg(test)] +fn wall_relief() -> std::sync::Arc { + let g = relief_grid(); + let mut h = vec![70.0; (g.size * g.size) as usize]; + for i in 0..g.size { + for j in 0..g.size { + let x = g.min.x + (f64::from(i) + 0.5) * g.cell; + let z = g.min.z + (f64::from(j) + 0.5) * g.cell; + if (x + 1000.0).abs() < 80.0 && z.abs() < 420.0 { + h[(i * g.size + j) as usize] = 1000.0; + } + } + } + std::sync::Arc::new(super::ew::KnownRelief::new(g, h)) +} + +#[cfg(test)] +fn finish_retargets(sim: &mut Sim) { + for _ in 0..200 { + if sim.retargets.is_empty() { + return; + } + sim.tick += 1; + sim.adapt(); + } + panic!("the retarget queue did not finish"); +} + +#[cfg(test)] +fn retarget_route(sim: &Sim, id: u32) -> Vec { + sim.queue.iter().find(|(_, d)| d.id == id).unwrap().1.route.clone() +} + +#[test] +fn pending_retarget_keeps_its_relief_version_after_a_new_plan() { + let flat = flat_relief(); + let wall = wall_relief(); + let outside = Grid { min: DVec3::new(3000.0, 0.0, 3000.0), cell: 32.0, size: 8 }; + assert!(flat.sampled_on(&outside).is_none(), "land outside the plan must be Unknown"); + let edge = Grid { min: DVec3::new(2500.0, 0.0, 2500.0), cell: 32.0, size: 8 }; + let edge_flat = flat.sampled_on(&edge).unwrap(); + assert!(edge_flat.iter().any(|h| h.is_nan()) && edge_flat.iter().any(|h| *h == 70.0)); + + let prepare = |relief| { + let (mut sim, _, _) = mass_retarget_sim(7); + sim.known_relief = Some(relief); + sim.tick = 40; + sim.adapt(); + sim + }; + let mut control = prepare(flat.clone()); + let mut test = prepare(flat.clone()); + let id = *test.retarget_ids().last().unwrap(); + assert!(test.pending_relief(id).is_some(), "the chosen job is not waiting"); + // Another smart plan finishes while the job waits: the global snapshot is the wall now. + test.known_relief = Some(wall.clone()); + let bits = |v: Option>| v.map(|x| x.into_iter().map(f32::to_bits).collect::>()); + assert_eq!( + bits(test.pending_relief(id).unwrap().sampled_on(&edge)), + bits(flat.sampled_on(&edge)), + "a pending job's relief changed with the global snapshot" + ); + + finish_retargets(&mut control); + finish_retargets(&mut test); + assert_eq!(retarget_route(&control, id), retarget_route(&test, id), "the pending route used the newer relief"); + + let mut walled = prepare(wall); + finish_retargets(&mut walled); + assert_ne!(retarget_route(&control, id), retarget_route(&walled, id), "the test does not distinguish the relief versions"); +} + +#[test] +fn same_relief_different_known_snapshots_do_not_share_prices_or_routes() { + let flat = flat_relief(); + let mut sim = Sim::new(3); + let start = DVec3::new(-1000.0, 100.0, 0.0); + let aim = DVec3::new(1000.0, 70.0, 0.0); + let threat = sim.id(); + let threat_at = DVec3::new(0.0, 70.0, 0.0); + sim.sams.push(Sam::new(threat, SamKind::S400, threat_at, 0.0)); + let target = sim.id(); + sim.sams.push(Sam::new(target, SamKind::S400, aim, 0.0)); + let contact = |id: u32, at: DVec3| super::ew::Contact { + id, + kind: SamKind::S400, + at, + tick: 0, + seen: super::ew::Seen::Emission, + emission: Some(0), + destroyed: None, + launches: 0, + }; + let known_all = std::sync::Arc::new(vec![contact(threat, threat_at), contact(target, aim)]); + let known_target = std::sync::Arc::new(vec![contact(target, aim)]); + // The picture holds both units: a job's unit is alive only while the picture believes in it. + for c in known_all.iter() { + sim.intel.note(c.id, c.kind, c.at, true, 0, super::ew::Seen::Emission); + } + + let mut add = |known: std::sync::Arc>, version: u64| -> u32 { + let mut d = sim.make_drone(DroneKind::Kh22, start, DVec3::X, aim); + d.smart = true; + d.err = DVec3::ZERO; + d.target = aim; + d.route = vec![start, aim]; + let id = d.id; + sim.queue.push((0, d)); + sim.retargets.push_back(super::ew::RouteJob::for_test(id, aim, target, version, known, Some(flat.clone()))); + id + }; + // Same relief, same start and aim. Version 1 knows the threat on the direct line; version 2 does not. + let first = add(known_all.clone(), 1); + let other = add(known_target, 2); + // A second job of version 1: it may reuse the first one's price grid and route. + let same = add(known_all, 1); + sim.tick = 1; + for _ in 0..10 { + if sim.retargets.is_empty() { + break; + } + sim.adapt(); + sim.tick += 1; + } + assert!(sim.retargets.is_empty(), "the jobs were not finished"); + let route = |id| sim.queue.iter().find(|(_, d)| d.id == id).unwrap().1.route.clone(); + let (r1, r2, r3) = (route(first), route(other), route(same)); + assert_ne!(r1, r2, "jobs with different known snapshots shared a route"); + assert_eq!(r1, r3, "jobs of one knowledge version did not reuse their route cache"); +} + // #31: a big cache of fresh columns is not scanned on every answer #[test] fn big_ground_cache_is_swept_rarely() { @@ -624,6 +899,8 @@ fn replay_fires_and_blasts_follow_the_recording() { let mut out = Out::default(); sim.step(&mut g, None, &mut out); let mut rec = sim.rec.take().unwrap(); + // Legacy recordings reconstruct fire from combat events; new journals record actual fire state. + rec.scene = None; let at = DVec3::new(5.0, 70.0, 5.0); let crash = |power| EventKind::Crash { drone: 1, kind: DroneKind::Geran3, power }; let life = fires::life_for(1); @@ -663,45 +940,6 @@ fn replay_fires_and_blasts_follow_the_recording() { // #78: a replay's smoke is rebuilt from the recording as a function of its time: trails behind // what flew (clearing the fog), a column over the fire, billows that outlive the fireball; a seek // shows what playing through shows, and the live Sim is not touched. -#[test] -fn replay_smoke_has_trails_and_fire_columns_and_is_a_function_of_time() { - use record::{Event, EventKind}; - let mut sim = Sim::new(1); - sim.add_drone(DroneKind::Geran3, DVec3::new(0.0, 80.0, 0.0), DVec3::Z, DVec3::new(0.0, 70.0, 2000.0)); - sim.add_drone(DroneKind::Geran3, DVec3::new(30.0, 80.0, 0.0), DVec3::Z, DVec3::new(30.0, 70.0, 2000.0)); - let mut g = Ground::default(); - let mut out = Out::default(); - for _ in 0..160 { - sim.step(&mut g, None, &mut out); - } - let mut rec = sim.rec.take().unwrap(); - assert_eq!(rec.tracks.len(), 2); - rec.events = vec![Event { at: 40, pos: DVec3::new(5.0, 70.0, 5.0), kind: EventKind::Hit { drone: 1, kind: DroneKind::Geran3, power: 2, object: None, sam: None } }]; - let live_tick = sim.tick; - let live_fires = sim.fires.list.len(); - let t = 130.0; - let a = crate::smoke::replay_smoke(&rec, t); - let trails = a.iter().filter(|s| s.clear > 0.0).count(); - let columns = a.iter().filter(|s| s.soot > 0.8).count(); - let billows = a.iter().filter(|s| s.soot == 0.55).count(); - assert!(trails > 0, "trails of both tracks: {trails}"); - assert!(columns > 0, "a column over the fire: {columns}"); - assert!(billows > 0, "the blast's billows"); - // Seeking away and back, or forward from nothing, gives the same smoke. - let _ = crate::smoke::replay_smoke(&rec, 10.0); - let _ = crate::smoke::replay_smoke(&rec, 155.0); - let b = crate::smoke::replay_smoke(&rec, t); - assert_eq!(a.iter().map(|s| (s.id, s.born as u64)).collect::>(), b.iter().map(|s| (s.id, s.born as u64)).collect::>()); - // Nothing from the future; before the raid, nothing. - assert!(a.iter().all(|s| s.born <= t)); - assert!(crate::smoke::replay_smoke(&rec, -5.0).is_empty()); - // The billows go on long after the short fireball is out. - let late = crate::smoke::replay_smoke(&rec, 400.0); - assert!(late.iter().any(|s| s.soot == 0.55)); - // The live state is not touched. - assert_eq!((sim.tick, sim.fires.list.len()), (live_tick, live_fires)); -} - // #76: the protected objects of a replay are the recording's, not the live marks. #[test] fn a_replay_draws_the_recorded_objects_not_the_live_ones() { @@ -902,7 +1140,7 @@ fn a_system_does_not_fire_at_a_target_masked_by_a_hill() { seen |= sim.sams[0].seen.len() == 1; masked += sim.sams[0].terrain.values().filter(|(_, b)| *b == terrain::MASKED).count() as u32; // `why` names the reason while the verdict stands. - told |= sim.why(0).iter().any(|c| !c.ok && c.text.contains("за рельефом")); + told |= sim.why(0).iter().any(|c| !c.ok && c.legacy_text().contains("за рельефом")); } (sim, seen, masked, told) }; @@ -930,7 +1168,7 @@ fn a_long_range_system_lofts_over_a_ridge_or_waits() { let sim = behind_ridge(&tall, 150, SamKind::IrisT, 230.0, ticks); assert_eq!(sim.sams[0].fired, 0, "no missile can climb a 400-block mountain at this range"); assert_eq!(sim.tally.blocked + sim.tally.scrubbed, 0); - told |= sim.why(0).iter().any(|c| !c.ok && c.text.contains("рельеф")); + told |= sim.why(0).iter().any(|c| !c.ok && c.legacy_text().contains("рельеф")); } assert!(told, "`why` never named the land as the reason"); } @@ -1351,7 +1589,7 @@ fn raids_have_the_same_strike_accuracy_as_manual_launches() { let mut sim = Sim::new(3); let id = sim.id(); sim.sams.push(Sam::new(id, SamKind::Ew, DVec3::new(0.0, 70.0, 900.0), 0.0)); - sim.intel.revealed.push((id, DVec3::new(0.0, 70.0, 900.0), 0)); + sim.intel.observe(&sim.sams[0], 0, ew::Seen::Jamming); let order = RaidOrder { kinds: vec![(DroneKind::Zircon, 20)], approach: Approach::Sector, dist: 400.0, aim: Aim::Objects }; sim.order_raid(&order, &[DVec3::new(0.0, 70.0, 0.0)], &mut Out::default()); // (The raid's launch sites need land the game has told of: a flat one is served.) @@ -1516,7 +1754,7 @@ fn graphics_settings_do_not_change_combat_randomness_or_ids() { sim.fireball(DVec3::new(f64::from(i), 80.0, 0.0), 4.0, 11); } sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 80.0, 0.0), DVec3::X, DVec3::new(900.0, 80.0, 0.0)); - sim.shoot_down(0, 0, None, &mut out); + sim.shoot_down(0, 0, None, &Ground::default(), &mut out); let id = sim.id(); (sim.drones[0].id, id, sim.aim_error(DroneKind::Shahed)) }; @@ -1804,3 +2042,502 @@ fn a_blast_in_the_blocks_waits_for_the_games_word() { let s = ground.world.stats; assert_eq!((s.blasts_asked, s.blasts_confirmed, s.blasts_unchanged, s.blasts_unloaded), (3, 1, 0, 2)); } + +#[test] +fn strike_round_truck_drives_from_depot_to_carrier_and_returns_air_round() { + let mut sim = Sim::new(5); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + sim.sams[0].ground_stock = 1; + sim.load_ground_round(1).unwrap(); + let (mut ground, land) = (Ground::default(), sites::testland::Land(|_, _| 64)); + let mut out = Out::default(); + let mut seen_loaded = false; + let mut furthest: f64 = 0.0; + for _ in 0..6000 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + seen_loaded |= sim.sams[2].ground_stock == 1; + furthest = furthest.max(sim.sams[2].pos.x); + if sim.sams[0].air_48n6_stock == 1 { break; } + out = Out::default(); + } + assert!(seen_loaded, "truck never loaded RM48U"); + assert!(furthest > 140.0, "truck did not drive to carrier: {furthest}"); + assert_eq!((sim.sams[0].ground_stock, sim.sams[0].air_48n6_stock), (0, 1)); + assert_eq!((sim.sams[1].missiles, sim.sams[1].ground_rounds), (3, 1)); + assert_eq!((sim.sams[2].ground_stock, sim.sams[2].air_48n6_stock), (0, 0)); + assert!(!sim.sams[1].ground_request); + assert!(sim.load_ground_round(1).is_err()); +} + +#[test] +fn strike_delivery_waits_while_carrier_moves_and_resumes_after_repair() { + let mut sim = Sim::new(8); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + sim.sams[0].ground_stock = 1; + sim.load_ground_round(1).unwrap(); + let (mut ground, land) = (Ground::default(), sites::testland::Land(|_, _| 64)); + let mut out = Out::default(); + for _ in 0..300 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + if sim.sams[2].ground_stock == 1 { break; } + out = Out::default(); + } + assert_eq!(sim.sams[2].ground_stock, 1); + assert!(sim.order_move(1, DVec3::new(220.0, 64.0, 0.0))); + // Repair cancels both marches but preserves the outstanding request and truck's load. + sim.repair(); + assert!(sim.sams[1].ground_request); + assert_eq!(sim.sams[2].ground_stock, 1); + assert!(sim.order_move(1, DVec3::new(220.0, 64.0, 0.0))); + for _ in 0..100 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + out = Out::default(); + } + assert_eq!(sim.sams[1].ground_rounds, 0); + for _ in 0..6000 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + if sim.sams[1].ground_rounds == 1 { break; } + out = Out::default(); + } + assert_eq!((sim.sams[1].ground_rounds, sim.sams[1].missiles), (1, 3)); + assert!(!sim.sams[1].ground_request); +} + +#[test] +fn destroying_a_pending_carrier_and_loaded_truck_does_not_restore_stock_on_repair() { + let mut sim = Sim::new(8); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 80.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + sim.sams[1].ground_request = true; + sim.sams[2].ground_stock = 1; + let mut out = Out::default(); + sim.direct_hit(sim.sams[1].pos + DVec3::Y, DVec3::ZERO, 1, &mut out); + sim.direct_hit(sim.sams[2].pos + DVec3::Y, DVec3::ZERO, 1, &mut out); + assert!(!sim.sams[1].alive()); + assert!(!sim.sams[2].alive()); + sim.repair(); + assert!(!sim.sams[1].ground_request); + assert_eq!((sim.sams[0].ground_stock, sim.sams[2].ground_stock), (0, 0)); +} + +#[test] +fn motor_burnout_reduces_speed_and_stops_powered_effects() { + let kind = MissileKind::Pac3; + let spec = kind.spec(); + let mut sim = Sim::new(120); + sim.add_drone(DroneKind::Shahed, DVec3::new(0.0, 100.0, 2000.0), DVec3::ZERO, DVec3::new(0.0, 100.0, 5000.0)); + let (target, id) = (sim.drones[0].id, sim.id()); + sim.tick = 100; + let fix = Some(Fix { pos: sim.drones[0].pos, vel: DVec3::ZERO, tick: 100, seeker: false }); + sim.missiles.push(Missile { id, kind, pos: DVec3::new(0.0, 100.0, 0.0), vel: DVec3::Z * (spec.speed * 0.8), target, sam: 999, age: spec.cold + kind.burn_ticks() - 2, hold: 0, fix, seen: 0 }); + let mut out = Out::default(); + sim.step_missiles(&mut Ground::default(), &mut out); + let powered = sim.missiles[0].vel.length(); + assert!(sim.missiles[0].engine_lit()); + assert!(out.engines.iter().any(|e| e.key == 0x4000_0000 | id)); + sim.step_missiles(&mut Ground::default(), &mut Out::default()); + let last = sim.missiles[0].vel.length(); + let mut out = Out::default(); + sim.step_missiles(&mut Ground::default(), &mut out); + assert!(powered <= last && sim.missiles[0].vel.length() < last); + assert!(!sim.missiles[0].engine_lit()); + assert!(!out.engines.iter().any(|e| e.key == 0x4000_0000 | id)); + assert!(out.engine_stops.contains(&(0x4000_0000 | id))); +} + +#[test] +fn interceptor_lead_depends_on_meeting_geometry() { + let fly = |velocity: DVec3| { + let mut sim = Sim::new(120); + let pos = DVec3::new(0.0, 100.0, 900.0); + sim.add_drone(DroneKind::Shahed, pos, DVec3::ZERO, DVec3::new(0.0, 100.0, 5000.0)); + sim.drones[0].vel = velocity; + let (target, id) = (sim.drones[0].id, sim.id()); + sim.tick = 100; + let fix = Some(Fix { pos, vel: velocity, tick: 100, seeker: false }); + sim.missiles.push(Missile { id, kind: MissileKind::Pac3, pos: DVec3::new(0.0, 100.0, 0.0), vel: DVec3::Z * 3.0, target, sam: 999, age: 100, hold: 0, fix, seen: 0 }); + sim.step_missiles(&mut Ground::default(), &mut Out::default()); + sim.missiles[0].vel + }; + let crossing = fly(DVec3::X); + let approaching = fly(-DVec3::Z); + let receding = fly(DVec3::Z); + assert!(crossing.x > 0.0, "crossing target must be led"); + assert!(approaching.x.abs() < 1e-9 && receding.x.abs() < 1e-9); +} + +#[test] +fn energy_loss_is_recorded_separately_from_expiry() { + let mut sim = Sim::new(120); + let kind = MissileKind::Pac3; + let pos = DVec3::new(0.0, 100.0, 900.0); + sim.add_drone(DroneKind::Shahed, pos, DVec3::ZERO, DVec3::new(0.0, 100.0, 5000.0)); + sim.begin_recording_if_active(); + let (target, id) = (sim.drones[0].id, sim.id()); + sim.tick = 100; + let fix = Some(Fix { pos, vel: DVec3::ZERO, tick: 100, seeker: false }); + sim.missiles.push(Missile { id, kind, pos: DVec3::new(0.0, 100.0, 0.0), vel: DVec3::Z * kind.spec().speed * 0.2, target, sam: 999, age: kind.burn_ticks() + 1, hold: 0, fix, seen: 0 }); + sim.step_missiles(&mut Ground::default(), &mut Out::default()); + assert!(sim.missiles.is_empty()); + assert_eq!(sim.tally.energy_lost, 1); + assert_eq!(sim.tally.expired, 0); + let events = sim.rec.as_ref().unwrap().scene.as_ref().unwrap().events(); + assert!(events.iter().any(|e| matches!(e.kind, record::SceneKind::MotorEnergyLost { missile, speed, .. } if missile == id && speed > 0.0))); +} + +#[test] +fn equal_speed_head_on_crossing_and_chase_have_distinct_reach_times() { + let fly = |vel: DVec3| { + let mut sim = Sim::new(120); + let mut ground = Ground::default(); + let land = Land(|_, _| 64); + let pos = DVec3::new(0.0, 200.0, 400.0); + sim.add_drone(DroneKind::Shahed, pos, DVec3::ZERO, DVec3::new(0.0, 200.0, 5000.0)); + sim.drones[0].vel = vel; + let (target, id) = (sim.drones[0].id, sim.id()); + sim.missiles.push(Missile { id, kind: MissileKind::Pac3, pos: DVec3::new(0.0, 200.0, 0.0), vel: DVec3::Z * 3.0, target, sam: 999, age: 20, hold: 0, fix: None, seen: 0 }); + for step in 1..200 { + sim.tick = step; + sim.drones[0].pos += vel; + sim.missiles[0].fix = Some(Fix { pos: sim.drones[0].pos, vel, tick: sim.tick, seeker: false }); + sim.step_missiles(&mut ground, &mut Out::default()); + answer_land(&mut ground, &land, sim.tick); + if sim.missiles.is_empty() { + assert_eq!(sim.tally.blocked + sim.tally.scrubbed + sim.tally.expired + sim.tally.energy_lost, 0, "lost before reaching target"); + return step; + } + } + panic!("interceptor did not reach the target") + }; + let head_on = fly(-DVec3::Z); + let crossing = fly(DVec3::X); + let chase = fly(DVec3::Z); + assert!(head_on < crossing && crossing < chase, "{head_on}, {crossing}, {chase}"); +} + +#[test] +fn spent_motor_has_less_maneuver_at_low_speed() { + let fly = |age: u32| { + let mut sim = Sim::new(120); + let kind = MissileKind::Pac3; + let at = DVec3::new(500.0, 200.0, 500.0); + sim.add_drone(DroneKind::Shahed, at, DVec3::ZERO, at); + sim.tick = 100; + let (target, id) = (sim.drones[0].id, sim.id()); + let fix = Some(Fix { pos: at, vel: DVec3::ZERO, tick: 100, seeker: false }); + sim.missiles.push(Missile { id, kind, pos: DVec3::new(0.0, 200.0, 0.0), vel: DVec3::Z * kind.spec().speed * 0.4, target, sam: 999, age, hold: 0, fix, seen: 0 }); + sim.step_missiles(&mut Ground::default(), &mut Out::default()); + sim.missiles[0].vel.normalize() + }; + let powered = fly(30); + let coast = fly(MissileKind::Pac3.burn_ticks() + 1); + assert!(powered.x > coast.x && coast.x > 0.0, "{powered:?} {coast:?}"); +} + +// #129: the staged launch's exact speed rules: gas-drag ejection with a floor, a spooled +// departure that grows into full acceleration, full power at guidance, the coast drag after. +#[test] +fn cold_ejection_power_and_coast_use_their_own_speed_rules() { + let kind = MissileKind::M48n6; + let spec = kind.spec(); + let fly = |age: u32, initial: f64| { + let mut sim = Sim::new(120); + let target_pos = DVec3::new(0.0, 100.0, 2000.0); + sim.add_drone(DroneKind::Shahed, target_pos, DVec3::ZERO, target_pos); + let (target, id) = (sim.drones[0].id, sim.id()); + sim.tick = 100; + let fix = Some(Fix { pos: target_pos, vel: DVec3::ZERO, tick: 100, seeker: false }); + sim.missiles.push(Missile { id, kind, pos: DVec3::new(0.0, 100.0, 0.0), vel: DVec3::Z * initial, target, sam: 999, age, hold: 0, fix, seen: 0 }); + sim.step_missiles(&mut Ground::default(), &mut Out::default()); + sim.missiles[0].vel.length() + }; + assert!((fly(0, spec.eject) - (spec.eject - spec.eject_drag)).abs() < 1e-9, "cold ejection drag"); + assert!((fly(spec.cold - 1, 0.1) - spec.eject * 0.25).abs() < 1e-9, "cold ejection floor near ignition"); + assert!((fly(0, 0.2) - spec.eject * 0.25).abs() < 1e-9, "cold ejection floor"); + let spool0 = 0.45 + 0.55 / f64::from(spec.boost); + assert!((fly(spec.cold, 2.0) - (2.0 + spec.accel * spool0)).abs() < 1e-9, "ignition spool-up"); + assert!((fly(spec.cold + spec.boost - 1, 2.0) - (2.0 + spec.accel)).abs() < 1e-9, "powered acceleration"); + assert!((fly(spec.cold + kind.burn_ticks(), 2.0) - 2.0 * 0.995).abs() < 1e-9, "coast drag"); +} + +// ---- #129: the staged cold launch. Ejection (unlit gas throw) → ignition where the round has +// reached → departure (spool-up and course capture at a growing rate) → guidance at cold + boost. + +/// Every cold-launched kind walks its stages in order; before ignition there is no motor voice, +/// no flame and no steering, the speed is continuous across ignition, and the turn each tick +/// never exceeds what the stage allows. +#[test] +fn cold_launch_stages_run_in_order_and_guidance_follows_ignition() { + for &kind in &[MissileKind::M48n6, MissileKind::M77n6, MissileKind::IrisT, MissileKind::M9m338] { + let spec = kind.spec(); + let mut sim = Sim::new(120); + // A still target well off the vertical: the tip-over has somewhere to turn. + let target_pos = DVec3::new(300.0, 120.0, 300.0); + sim.add_drone(DroneKind::Shahed, target_pos, DVec3::ZERO, target_pos); + let (target, id) = (sim.drones[0].id, sim.id()); + sim.missiles.push(Missile { id, kind, pos: DVec3::new(0.0, 64.0, 0.0), vel: DVec3::Y * spec.eject, target, sam: 999, age: 0, hold: 0, + fix: Some(Fix { pos: target_pos, vel: DVec3::ZERO, tick: 0, seeker: false }), seen: 0 }); + sim.begin_recording_if_active(); + let mut previous_speed = spec.eject; + let mut previous_dir = DVec3::Y; + let mut saw_ignition = false; + for age in 1..=(spec.cold + spec.boost + 2) { + sim.tick += 1; + let mut out = Out::default(); + sim.step_missiles(&mut Ground::default(), &mut out); + let m = sim.missiles.iter().find(|m| m.id == id).unwrap_or_else(|| panic!("{kind:?} age {age}")); + let speed = m.vel.length(); + let dir = m.vel.normalize_or(DVec3::Y); + let voice = out.engines.iter().any(|e| e.key == (0x4000_0000 | id)); + let flame = out.particles.iter().any(|p| p.kind == "minecraft:flame"); + assert_eq!(voice, m.engine_lit(), "{kind:?} age {age}: the motor voice runs only when lit"); + if !m.engine_lit() { + assert!(!voice && !flame, "{kind:?} age {age}: powered effects before ignition"); + assert!((speed - (previous_speed - spec.eject_drag)).abs() < 1e-9, "{kind:?} age {age}: ejection drag"); + assert!(dir.angle_between(DVec3::Y) < 1e-9, "{kind:?} age {age}: steering before ignition"); + } else if !saw_ignition { + saw_ignition = true; + assert_eq!(m.age, spec.cold + 1, "{kind:?}: ignition one tick after the ejection ends"); + assert!(speed > previous_speed - 1e-9, "{kind:?}: speed jumps down across ignition ({previous_speed} -> {speed})"); + assert!(out.sounds.iter().any(|s| s.0 == sound::LAUNCH_S400 || s.0 == sound::LAUNCH_S500), "{kind:?}: ignition sound"); + let events = sim.rec.as_ref().unwrap().scene.as_ref().unwrap().events(); + assert!(events.iter().any(|e| matches!(e.kind, record::SceneKind::MotorIgnition { missile, .. } if missile == id)), "{kind:?}: ignition recorded"); + } + let allowed = if spec.guided(m.age) { + spec.turn + } else if m.engine_lit() { + // Departure: the rate grows into the full one; no snap at the boundary. + spec.turn * (0.2 + 0.8 * spec.departure_ramp(m.age)) + } else { + 0.0 + }; + assert!(previous_dir.angle_between(dir) <= allowed + 1e-9, "{kind:?} age {age}: turned past the stage's rate"); + previous_speed = speed; + previous_dir = dir; + } + assert!(saw_ignition, "{kind:?}: never lit"); + let m = sim.missiles.iter().find(|m| m.id == id).unwrap(); + assert!(m.vel.normalize().angle_between(DVec3::Y) > 0.3, "{kind:?}: still vertical after guidance started"); + } +} + +/// The invariant the issue is about: with a longer ignition delay the old `age >= boost` guidance +/// would have switched on before the motor existed. Now guidance implies ignition, for every +/// profile and every age, and the seeker opens with guidance, not with the raw boost counter. +#[test] +fn guidance_never_starts_before_ignition_for_any_profile() { + for kind in [ + MissileKind::Pac3, MissileKind::M57e6, MissileKind::M48n6, MissileKind::M77n6, MissileKind::IrisT, + MissileKind::Amraam, MissileKind::M9m317, MissileKind::M9m338, MissileKind::P1sun, + ] { + let spec = kind.spec(); + for age in 0..=spec.life.min(2000) { + assert!(!spec.guided(age) || age > spec.cold, "{kind:?} age {age}: guidance before ignition (cold {})", spec.cold); + } + } +} + +/// Across the whole staging the speed changes smoothly (no jumps beyond the stage's own drag or +/// full acceleration) and the per-tick turn never exceeds the profile's rate, for every kind. +/// A rocket's first powered tick is a throttled one for rail and cold launches alike (#129). +#[test] +fn launch_accel_and_turn_stay_continuous_for_every_interceptor() { + for kind in [ + MissileKind::Pac3, MissileKind::M57e6, MissileKind::M48n6, MissileKind::M77n6, MissileKind::IrisT, + MissileKind::Amraam, MissileKind::M9m317, MissileKind::M9m338, MissileKind::P1sun, + ] { + let spec = kind.spec(); + let mut sim = Sim::new(120); + let target_pos = DVec3::new(400.0, 140.0, 400.0); + sim.add_drone(DroneKind::Shahed, target_pos, DVec3::ZERO, target_pos); + let (target, id) = (sim.drones[0].id, sim.id()); + let v0 = if spec.cold > 0 { spec.eject } else { 0.8 }; + sim.missiles.push(Missile { id, kind, pos: DVec3::new(0.0, 64.0, 0.0), vel: DVec3::Y * v0, target, sam: 999, age: 0, hold: 0, + fix: Some(Fix { pos: target_pos, vel: DVec3::ZERO, tick: 0, seeker: false }), seen: 0 }); + let mut previous_speed = v0; + let mut previous_dir = DVec3::Y; + let mut first_power: Option = None; + for _ in 0..120 { + sim.tick += 1; + sim.step_missiles(&mut Ground::default(), &mut Out::default()); + let Some(m) = sim.missiles.iter().find(|m| m.id == id) else { break }; + let speed = m.vel.length(); + let dir = m.vel.normalize_or(DVec3::Y); + assert!(speed - previous_speed <= spec.accel + 1e-9, "{kind:?}: speed jumped up {previous_speed} -> {speed}"); + let allowed_down = spec.eject_drag + previous_speed * 0.005; + assert!(speed >= previous_speed - allowed_down - 1e-9, "{kind:?}: speed jumped down {previous_speed} -> {speed}"); + assert!(previous_dir.angle_between(dir) <= spec.turn + 1e-9, "{kind:?}: turned past the profile's rate"); + if m.engine_lit() && first_power.is_none() { + first_power = Some(speed - previous_speed); + } + previous_speed = speed; + previous_dir = dir; + } + if spec.flame > 0.0 { + let gain = first_power.unwrap_or_else(|| panic!("{kind:?}: the motor never lit")); + let spooled = spec.accel * (0.45 + 0.55 / f64::from(spec.boost.max(1))); + assert!(gain > 0.0 && gain <= spooled + 1e-9, "{kind:?}: the motor reached full thrust at once ({gain} > {spooled})"); + } + } +} + +/// The electric P1-SUN is not a rocket: it keeps no flame, no powered smoke and its own voice. +#[test] +fn p1sun_stays_a_ramming_drone_without_a_plume() { + let kind = MissileKind::P1sun; + let spec = kind.spec(); + assert_eq!(spec.flame, 0.0); + assert_eq!(spec.cold, 0); + let mut sim = Sim::new(120); + let target_pos = DVec3::new(200.0, 64.0, 0.0); + sim.add_drone(DroneKind::Shahed, target_pos, DVec3::ZERO, target_pos); + let (target, id) = (sim.drones[0].id, sim.id()); + sim.missiles.push(Missile { id, kind, pos: DVec3::ZERO, vel: DVec3::X * 0.8, target, sam: 999, age: 0, hold: 0, + fix: Some(Fix { pos: target_pos, vel: DVec3::ZERO, tick: 0, seeker: false }), seen: 0 }); + for _ in 0..40 { + sim.tick += 1; + let mut out = Out::default(); + sim.step_missiles(&mut Ground::default(), &mut out); + assert!(!out.particles.iter().any(|p| p.kind == "minecraft:flame"), "P1-SUN grew a rocket plume"); + if let Some(m) = sim.missiles.iter().find(|m| m.id == id) { + assert!(m.engine_lit(), "the electric interceptor's motor state is wrong"); + let (event, _, _) = kind.engine_voice(); + assert!(out.engines.iter().any(|e| e.key == (0x4000_0000 | id) && e.event == event)); + } else { + break; + } + } +} + +/// A real S-400 launch records the ejection and the ignition `cold` ticks apart; the replay's lit +/// state follows the recorded ignition, so a seek across it shows one flame and one voice sequence. +#[test] +fn recorded_ignition_is_cold_ticks_after_ejection_and_replay_follows_it() { + let mut sim = Sim::new(0x129); + let sam = sim.id(); + sim.sams.push(Sam::new(sam, SamKind::S400, DVec3::new(0.0, 64.0, 0.0), 0.0)); + sim.sams[0].cooldown = 0; + let mut out = Out::default(); + sim.launch(DroneKind::Kh101, DVec3::new(0.0, 160.0, -600.0), DVec3::Z, DVec3::new(0.0, 64.0, 0.0), &mut out); + let mut ground = Ground::default(); + for _ in 0..200 { + // The firing system's own measurement, fresh this tick (as `why::radar_sees` feeds it): + // the test is about the launch stages, not about the scan's chance. + let now = sim.tick; + let tracks: Vec = sim.drones.iter().filter(|d| d.alive()) + .map(|d| sight::Track { id: d.id, kind: d.kind, pos: d.pos, vel: d.vel, tick: now, own_tick: now, ttl: 400, hits: 3, locked: false }) + .collect(); + sim.sams[0].tracks.retain(|t| !tracks.iter().any(|n| n.id == t.id)); + sim.sams[0].tracks.extend(tracks); + let wanted = ground.take_wanted(); + ground.answer(wanted.into_iter().map(|(x, z)| (x, z, 64)).collect(), sim.tick); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + if sim.replays.iter().chain(sim.rec.iter()).flat_map(|r| r.scene.iter().flat_map(|s| s.events())).any(|e| matches!(e.kind, record::SceneKind::MotorIgnition { .. })) { + break; + } + } + let rec = sim.rec.as_ref().or_else(|| sim.replays.last()).expect("a recording"); + let scene = rec.scene.as_ref().unwrap(); + let launch = scene.events().iter().find(|e| matches!(e.kind, record::SceneKind::MissileLaunch { .. })).expect("the ejection is recorded"); + let missile = match launch.kind { record::SceneKind::MissileLaunch { missile, .. } => missile, _ => unreachable!() }; + let spec = MissileKind::M48n6.spec(); + let ignition = rec.missile_ignition(missile).expect("the ignition is recorded"); + assert_eq!(ignition.at - launch.at, spec.cold, "ignition {} ticks after the ejection", spec.cold); + let track = rec.track(missile).expect("the interceptor's track"); + for t in [ignition.at as f64 - 1.0, ignition.at as f64 - 0.1] { + assert!(!rec.missile_is_lit(track, t, spec.cold), "lit before the recorded ignition at {t}"); + } + for t in [ignition.at as f64, ignition.at as f64 + 5.0] { + assert!(rec.missile_is_lit(track, t, spec.cold), "not lit at the recorded ignition ({t})"); + } + assert!(!rec.missile_is_lit(track, ignition.at as f64 + spec.cold as f64 + f64::from(MissileKind::M48n6.burn_ticks()) + 2.0, spec.cold), "lit past the recorded burnout"); +} + +/// The physical RM48U strike ejects unlit and slow, then lights at `EJECT_TICKS` where it has +/// reached: no motor voice or flame before, one ignition sound at the hand-over. +#[test] +fn rm48u_strike_ejects_before_ignition_on_its_own_path() { + use super::sites::testland::{Land, serve}; + use super::sites::{LaunchSite, SiteClass}; + use super::strike::{Estimate, Known, SiteState}; + use glam::DVec2; + let land = Land(|_, _| 64); + let mut sim = Sim::new(11); + // A fixed site the strike needs no scouting for; its launches are queued far ahead, so a wave + // boundary does not retire it while the test runs. + let id = sim.sites.add(LaunchSite { + id: 0, side: 0, sector: 0, bearing: 0.0, class: SiteClass::Ramp, + pos: DVec3::new(0.0, 64.0, 400.0), range: 400.0, water: false, + stock: 4, planned: 4, alive: true, next_free: 0, offmap: false, + }); + for i in 0..4 { + let site = sim.sites.get(id).unwrap().clone(); + let d = sim.make_drone(DroneKind::Shahed, site.muzzle(i), -DVec3::Z, DVec3::new(0.0, 70.0, 0.0)); + sim.sites.job(d.id, id); + sim.queue.push((20_000 + u64::from(i), d)); + } + sim.strikes.known.push(Known { + site: id, class: SiteClass::Ramp, obs: Vec::new(), + est: Some(Estimate { pos: DVec2::new(0.0, 400.0), radius: 5.0, fix: true, bearing: 0.0, bearing_err: 0.01, origin: DVec2::ZERO }), + launches: 1, last: 0, state: SiteState::Active, aimed: false, + }); + let carrier = sim.id(); + let mut sam = Sam::new(carrier, SamKind::S400, DVec3::new(0.0, 64.0, 0.0), 0.0); + sam.missiles = 2; + sam.ground_rounds = 2; + sam.cooldown = 0; + sim.sams.push(sam); + let mut out = Out::default(); + sim.order_strike(id, DVec3::ZERO, &mut out).unwrap(); + let (mut ground, mut heard_ignition) = (Ground::default(), 0); + let mut saw_voice_before = false; + let mut saw_flame_before = false; + let mut rise = 0.0f64; + let mut all_messages = Vec::new(); + for _ in 0..(launch::OPEN_TICKS as usize + strike::EJECT_TICKS as usize + 6) { + serve(&mut ground, &land, sim.tick); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + all_messages.append(&mut out.messages); + if let Some(f) = sim.strikes.flights.first() { + let age = sim.tick - f.start; + if age < strike::EJECT_TICKS { + saw_voice_before |= out.engines.iter().any(|e| e.key == 0x7500_0000 + f.id * PARTS); + saw_flame_before |= out.models.iter().any(|m| m.name == "airdef:flame" && m.id == 0x7500_0000 + f.id * PARTS + 1); + rise = rise.max(f.pos.y - f.from.y); + } + if age == strike::EJECT_TICKS { + heard_ignition = out.sounds.iter().filter(|s| s.0 == sound::LAUNCH_S400).count(); + } + } + } + assert!(rise > 10.0, "the ejection rose only {rise} blocks"); + assert!(!saw_voice_before && !saw_flame_before, "powered effects during the unlit ejection"); + assert_eq!(heard_ignition, 1, "exactly one ignition sound at the hand-over: {all_messages:?}"); +} + +#[test] +fn blast_forget_removes_only_columns_in_the_same_square() { + let mut ground = Ground::default(); + for x in -25..=25 { + for z in -25..=25 { + ground.known.insert((x, z), (x * 100 + z, 123)); + } + } + let before = ground.known.clone(); + let at = DVec3::new(-3.2, 70.0, 4.9); + ground.forget(at, 8); + let expected: std::collections::HashMap<_, _> = before.into_iter() + .filter(|&((x, z), _)| (x + 4_i32).abs() > 8 || (z - 4_i32).abs() > 8) + .collect(); + assert_eq!(ground.known, expected); +} diff --git a/airdef-mod/src/sim/route.rs b/airdef-mod/src/sim/route.rs index abfe5e6..04d0585 100644 --- a/airdef-mod/src/sim/route.rs +++ b/airdef-mod/src/sim/route.rs @@ -64,6 +64,57 @@ pub(super) enum Drive { Fail(Why), } +/// Result of checking the vehicle's clearance at its next position. +pub(super) enum Clearance { + Clear, + Blocked, + Pending, +} + +/// Conservative chassis envelope. The circular footprint is the circumscribed plan-view of the +/// folded vehicle; it makes turns safe without adding heading to the bounded A* state. +#[derive(Clone, Copy)] +struct Chassis { + radius: f64, + height: f64, +} + +impl Chassis { + fn for_kind(kind: SamKind) -> Self { + let (radius, height): (f64, f64) = match kind.spec().family { + Family::Trainable => (2.25, 3.0), + Family::Gun => (1.75, 3.0), + Family::Turret => (2.0, 3.25), + Family::Vertical => (2.75, 3.5), + Family::Static => (2.0, 3.0), + Family::Truck => (1.75, 2.75), + Family::Light => (1.25, 2.5), + }; + // Rendered geometry may extend past the nominal family envelope (rotated wheels too). + // Read the generated bounds of the exact body rather than letting art silently shrink clearance. + let (radius, height) = match super::generated_bounds::bounds(kind.spec().parts.body) { + Some((lo, hi)) => { + let scale = kind.spec().scale / 16.0; + let half_x = (lo[0] - 8.0).abs().max((hi[0] - 8.0).abs()) * scale; + let half_z = (lo[2] - 8.0).abs().max((hi[2] - 8.0).abs()) * scale; + (radius.max(half_x.hypot(half_z) + 0.125), height.max(hi[1] * scale)) + } + None => (radius, height), // Content validation rejects missing models before packaging. + }; + Self { radius, height } + } + + /// Every voxel whose square intersects the conservative footprint, including grazing corners. + fn samples(self) -> Vec<(i32, i32)> { + let r = self.radius.ceil() as i32; + (-r..=r).flat_map(|x| (-r..=r).filter_map(move |z| { + let dx = (f64::from(x.abs()) - 0.5).max(0.0); + let dz = (f64::from(z.abs()) - 0.5).max(0.0); + (dx * dx + dz * dz <= self.radius * self.radius).then_some((x, z)) + })).collect() + } +} + #[derive(Clone, Copy)] struct Info { h: f32, @@ -103,6 +154,7 @@ pub(super) struct Route { slack: f64, cell: i32, slope: f64, + chassis: Chassis, start: (i32, i32), lo: (i32, i32), hi: (i32, i32), @@ -131,14 +183,15 @@ pub(super) struct Route { } impl Route { - pub(super) fn new(pos: DVec3, dest: DVec3, slack: f64, slope: f64, now: u64) -> Self { + pub(super) fn new(pos: DVec3, dest: DVec3, slack: f64, kind: SamKind, now: u64) -> Self { let dist = horizontal(dest - pos).length(); let cell = if dist <= 400.0 { 4 } else if dist <= 900.0 { 6 } else { 8 }; let mut r = Self { dest, slack, cell, - slope, + slope: kind.climbs(), + chassis: Chassis::for_kind(kind), start: (0, 0), lo: (0, 0), hi: (0, 0), @@ -232,10 +285,50 @@ impl Route { } } }; + if verdict.ok { + match self.clearance_at(ground, DVec3::new(f64::from(x) + 0.5, f64::from(verdict.h), f64::from(z) + 0.5), now) { + Clearance::Clear => {} + Clearance::Blocked => { + let blocked = Info { h: verdict.h, ok: false }; + self.cells.insert(c, blocked); + return Eval::Bad; + } + Clearance::Pending => return Eval::Pending, + } + } self.cells.insert(c, verdict); if verdict.ok { Eval::Ok(verdict.h) } else { Eval::Bad } } + /// Check a chassis-sized footprint against sampled support columns and voxel headroom. + /// Unknown columns or sections are pending so neither search nor motion assumes they are open. + fn clearance_at(&self, ground: &mut Ground, pos: DVec3, now: u64) -> Clearance { + let base = pos.y; + let mut pending = false; + for (dx, dz) in self.chassis.samples() { + let x = pos.x.floor() as i32 + dx; + let z = pos.z.floor() as i32 + dz; + let Some(column) = ground.column(x, z, 1024) else { pending = true; continue }; + let Some(h) = column else { return Clearance::Blocked }; + let dh = h - base; + if dh > STEP_UP || dh < -STEP_DOWN { + return Clearance::Blocked; + } + let top = DVec3::new(f64::from(x) + 0.5, h + 0.05, f64::from(z) + 0.5); + match ground.world.sweep(top, top + DVec3::Y * self.chassis.height, false, now) { + world::Sweep::Clear => {} + world::Sweep::Unknown { pending: true, .. } | world::Sweep::Wait { .. } => pending = true, + world::Sweep::Unknown { .. } | world::Sweep::Hit { .. } => return Clearance::Blocked, + } + } + if pending { Clearance::Pending } else { Clearance::Clear } + } + + /// Recheck the envelope at the live position, since obstacles may change after planning. + pub(super) fn clearance(&self, ground: &mut Ground, pos: DVec3, now: u64) -> Clearance { + self.clearance_at(ground, pos, now) + } + /// Relaxes the edge from the opened cell `c` to `nb`; parks it when the land between isn't known yet. fn relax(&mut self, ground: &mut Ground, now: u64, c: (i32, i32), nb: (i32, i32)) { if nb.0 < self.lo.0 || nb.1 < self.lo.1 || nb.0 > self.hi.0 || nb.1 > self.hi.1 { @@ -305,7 +398,7 @@ impl Route { } } if now >= self.retry_at && !self.parked.is_empty() { - self.retry_at = now + 6; + self.retry_at = now + 1; let parked = std::mem::take(&mut self.parked); for (k, &(c, nb)) in parked.iter().enumerate() { // The game loads the chunk of the first few, if it loads on request. @@ -336,10 +429,13 @@ impl Route { for (di, dj) in [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)] { self.relax(ground, now, c, (c.0 + di, c.1 + dj)); } + // Let pending footprint queries settle before opening a broad, less useful frontier. + // Otherwise three trucks can saturate the shared world budget without ever moving. if self.expanded >= MAX_NODES { self.settle(now); return; } + if !self.parked.is_empty() { break; } } if self.expanded > before { self.stuck_since = None; @@ -486,6 +582,23 @@ impl SamKind { mod tests { use super::*; + #[test] + fn gun_chassis_cannot_use_the_old_one_point_seven_five_block_radius() { + // The rendered Gepard hull alone is wider/longer than that nominal family circle. + assert!(Chassis::for_kind(SamKind::Gepard).radius > 2.5); + for kind in SamKind::ALL { + assert!(super::super::generated_bounds::bounds(kind.spec().parts.body).is_some(), "{kind:?}"); + } + } + + #[test] + fn footprint_checks_voxels_grazed_between_their_centres() { + let cells = Chassis { radius: 2.75, height: 3.0 }.samples(); + assert!(cells.contains(&(3, 1)), "the body reaches the near corner of this voxel"); + assert!(cells.contains(&(-3, -1))); + assert!(!cells.contains(&(3, 3))); + } + /// The land as the game would tell it: the height map, and under it the blocks that are solid. struct Src i32, B: Fn(i32, i32) -> i32> { height: H, @@ -505,13 +618,13 @@ mod tests { } /// Drives a Patriot from the origin to `to` over land of the given heights (water: a height over a lower floor). - fn march i32, B: Fn(i32, i32) -> i32>(height: H, floor: B, to: DVec3, ticks: u32) -> (Sim, Out, u32) { + fn march_kind i32, B: Fn(i32, i32) -> i32>(kind: SamKind, height: H, floor: B, to: DVec3, ticks: u32) -> (Sim, Out, u32) { let src = Src { height, floor }; let mut sim = Sim::new(3); sim.damage = false; let mut ground = Ground::default(); let id = sim.id(); - sim.sams.push(Sam::new(id, SamKind::Patriot, DVec3::new(0.5, 64.0, 0.5), 0.0)); + sim.sams.push(Sam::new(id, kind, DVec3::new(0.5, 64.0, 0.5), 0.0)); let mut out = Out::default(); for _ in 0..3 { sim.tick += 1; @@ -538,6 +651,10 @@ mod tests { (sim, out, used) } + fn march i32, B: Fn(i32, i32) -> i32>(height: H, floor: B, to: DVec3, ticks: u32) -> (Sim, Out, u32) { + march_kind(SamKind::Patriot, height, floor, to, ticks) + } + // A wall across the way, with a gap far to one side: the vehicle goes round and arrives. #[test] fn a_wall_with_a_gap_is_driven_round() { @@ -572,8 +689,27 @@ mod tests { // Open ground: straight on, arrives. #[test] fn open_ground_is_driven_straight() { - let (sim, _, _) = march(|_, _| 64, |_, _| 64, DVec3::new(100.0, 64.0, 30.5), 1500); + let (sim, out, _) = march(|_, _| 64, |_, _| 64, DVec3::new(100.0, 64.0, 30.5), 1500); let s = &sim.sams[0]; - assert!(!s.moving() && s.pos.distance(DVec3::new(100.0, 64.0, 30.5)) < 3.0, "{:?}", s.pos); + let route = sim.routes.map.get(&s.id).map(|r| (r.expanded, r.parked.len(), r.open.len(), r.cells.len())); + assert!(!s.moving() && s.pos.distance(DVec3::new(100.0, 64.0, 30.5)) < 3.0, "pos={:?} state={:?} messages={:?} route={route:?}", s.pos, s.state, out.messages); + } + + // A point-column route fits through this one-block slit, but the S-400 chassis does not. + #[test] + fn s400_rejects_a_one_block_corridor() { + let walls = |x: i32, z: i32| if (-500..500).contains(&x) && z != 0 { 96 } else { 64 }; + let (sim, out, _) = march_kind(SamKind::S400, walls, walls, DVec3::new(60.5, 64.0, 0.5), 4000); + assert!(!sim.sams[0].moving(), "{:?}", sim.sams[0].state); + assert!(sim.sams[0].pos.x < 20.0, "narrow passage was traversed: {:?}", sim.sams[0].pos); + assert!(out.messages.iter().any(|m| m.contains("прохода нет") || m.contains("не проехать")), "{:?}", out.messages); + } + + // A broad, level lane remains driveable for the same large vehicle. + #[test] + fn s400_uses_a_wide_corridor() { + let walls = |x: i32, z: i32| if (-500..500).contains(&x) && !(-6..=6).contains(&z) { 96 } else { 64 }; + let (sim, _, _) = march_kind(SamKind::S400, walls, walls, DVec3::new(60.5, 64.0, 0.5), 5000); + assert!(!sim.sams[0].moving() && sim.sams[0].pos.distance(DVec3::new(60.5, 64.0, 0.5)) < 3.0, "pos={:?} state={:?}", sim.sams[0].pos, sim.sams[0].state); } } diff --git a/airdef-mod/src/sim/sensor_tests.rs b/airdef-mod/src/sim/sensor_tests.rs index aa56ef1..55854b1 100644 --- a/airdef-mod/src/sim/sensor_tests.rs +++ b/airdef-mod/src/sim/sensor_tests.rs @@ -147,7 +147,7 @@ fn a_blast_from_behind_hurts_the_link_a_blast_from_the_front_the_drive() { let id = sim.id(); sim.sams.push(Sam::new(id, SamKind::Patriot, at, 0.0)); let mut out = Out::default(); - sim.blast_damage(at + DVec3::new(0.0, 3.0, dz), 2, &mut out); + sim.blast_damage(at + DVec3::new(0.0, 3.0, dz), 2, &Ground::default(), &mut out); assert!(sim.sams[0].alive()); let c = sim.sams[0].comps; (1.0 - c.get(Part::Comms), 1.0 - c.get(Part::Mount), 1.0 - c.get(Part::Sensor)) @@ -168,12 +168,12 @@ fn a_close_blast_can_leave_a_firing_but_immobile_system() { sim.sams.push(Sam::new(id, SamKind::Pantsir, DVec3::new(0.0, 70.0, 0.0), 0.0)); let mut out = Out::default(); sim.tick = seed; - sim.blast_damage(DVec3::new(0.0, 69.0, 6.0 + seed as f64 * 0.06), 3, &mut out); + sim.blast_damage(DVec3::new(0.0, 69.0, 6.0 + seed as f64 * 0.06), 3, &Ground::default(), &mut out); let s = &sim.sams[0]; if s.alive() && s.part(Part::Tracks) <= 0.0 && s.part(Part::Launcher) > 0.0 && s.part(Part::Gun) > 0.0 { assert!(!sim.order_move(0, DVec3::new(300.0, 70.0, 0.0)), "failed running gear must refuse a march"); assert!(why::missile_blocks(&sim.sams[0]) & why::LAUNCHER_DOWN == 0); - assert!(sim.why(0).iter().any(|c| c.text.contains("ходовая отказала")), "{:?}", sim.why(0).iter().map(|c| &c.text).collect::>()); + assert!(sim.why(0).iter().any(|c| matches!(c.reason, why::CheckReason::PartEffect(why::PartEffect::TracksFailed))), "{:?}", sim.why(0)); found = true; break; } @@ -181,6 +181,34 @@ fn a_close_blast_can_leave_a_firing_but_immobile_system() { assert!(found, "no seed left it firing but immobile"); } +#[test] +fn a_fresh_own_radar_track_keeps_gun_hits_equal_day_and_night() { + fn firing(seed: u64, night: bool) -> (u32, u32) { + let mut sim = Sim::new(seed); + let (mut ground, mut out) = flat(); + let gun = radar_at(&mut sim, SamKind::Gepard, 0.0); + sim.night = night; + sim.add_drone( + DroneKind::Shahed, + DVec3::new(0.0, 95.0, 35.0), + DVec3::ZERO, + DVec3::new(5000.0, 95.0, 35.0), + ); + let starting_bursts = sim.sams[gun].bursts; + for _ in 0..240 { + sim.step(&mut ground, None, &mut out); + } + (starting_bursts - sim.sams[gun].bursts, sim.sams[gun].kills) + } + + for seed in 0..32 { + let day = firing(seed, false); + let night = firing(seed, true); + assert!(day.0 > 0, "seed {seed} did not fire"); + assert_eq!(night, day, "seed {seed}: radar gun changed its fire or kills at night"); + } +} + #[test] fn failed_parts_block_what_they_serve_and_repair_restores_them() { let mut sim = Sim::new(2); diff --git a/airdef-mod/src/sim/sight.rs b/airdef-mod/src/sim/sight.rs index 6c0238b..2065678 100644 --- a/airdef-mod/src/sim/sight.rs +++ b/airdef-mod/src/sim/sight.rs @@ -29,13 +29,15 @@ pub(super) const GUN_PICK_AGE: u64 = 40; /// A track older than this is shown as a forecast. pub const FORECAST_AGE: u64 = 20; /// Radio horizon: `HORIZON * (√mast + √height)` blocks. -const HORIZON: f64 = 35.0; +pub(super) const HORIZON: f64 = 35.0; /// Anything this near is seen (and heard), whatever hides it. -const NEAR: f64 = 24.0; +pub(super) const NEAR: f64 = 24.0; pub(super) struct Horizon { /// Per ray, per sample out: the steepest rise of the land so far (tan of its elevation). pub(super) rays: Vec>, + antenna: DVec3, + reach: f64, } impl Horizon { @@ -58,9 +60,12 @@ impl Horizon { .collect() }) .collect(); - Self { rays } + Self { rays, antenna, reach } } + fn matches(&self, sam: &Sam) -> bool { + self.antenna == antenna(sam) && self.reach == sensor_span(sam) + } /// Whether the land between the antenna and `p` rises over the line to it. pub(super) fn hides(&self, antenna: DVec3, p: DVec3) -> bool { let rel = p - antenna; @@ -78,7 +83,11 @@ impl Horizon { /// Where a unit's radar antenna is. pub(super) fn antenna(sam: &Sam) -> DVec3 { - sam.pos + DVec3::Y * sam.kind.spec().mast + sam.pos + DVec3::Y * sam.sensor_mast() +} + +fn sensor_span(sam: &Sam) -> f64 { + sam.kind.spec().sensor * if sam.kind == SamKind::Radar { sam.radar_role.reach_factor() } else { 1.0 } } /// How far a system's search radar reaches for this kind at `height` over the land: @@ -88,8 +97,8 @@ pub(super) fn reach(sam: &Sam, kind: DroneKind, height: f64) -> f64 { if spec.sensor <= 0.0 { return 0.0; } - let radar = spec.sensor * sam.radar_fit() * kind.rcs().powf(0.25); - let horizon = HORIZON * (spec.mast.sqrt() + height.max(0.0).sqrt()); + let radar = spec.sensor * sam.radar_fit() * if sam.kind == SamKind::Radar { sam.radar_role.reach_factor() } else { 1.0 } * kind.rcs().powf(0.25); + let horizon = HORIZON * (sam.sensor_mast().sqrt() + height.max(0.0).sqrt()); radar.min(horizon).max(NEAR) } @@ -127,15 +136,17 @@ impl Sim { /// A radar for a unit of `kind` at `pos` as the map draws it; `id`: the unit it is (its parts and horizon), None for a ghost. pub fn low_sensor(&self, kind: SamKind, pos: DVec3, id: Option) -> Option { let spec = kind.spec(); - if spec.sensor <= 0.0 { + if spec.sensor <= 0.0 || id.and_then(|i| self.sams.iter().find(|s| s.id == i)).is_some_and(|s| s.mode == battery::SamMode::LauncherOnly) { return None; } let fit = id.and_then(|i| self.sams.iter().find(|s| s.id == i)).map_or(1.0, |s| s.radar_fit()); let height = LOW_KIND.spec().clearance; - let radar = spec.sensor * fit * LOW_KIND.rcs().powf(0.25); - let reach = radar.min(HORIZON * (spec.mast.sqrt() + height.sqrt())).max(NEAR); - let terrain = id.is_some_and(|i| self.horizons.contains_key(&i)); - Some(LowSensor { id: id.unwrap_or(u32::MAX), ant: pos + DVec3::Y * spec.mast, base: pos.y, reach, terrain }) + let mast = id.and_then(|i| self.sams.iter().find(|s| s.id == i)).map_or(spec.mast, |s| s.sensor_mast()); + let factor = id.and_then(|i| self.sams.iter().find(|s| s.id == i)).filter(|s| s.kind == SamKind::Radar).map_or(1.0, |s| s.radar_role.reach_factor()); + let radar = spec.sensor * fit * factor * LOW_KIND.rcs().powf(0.25); + let reach = radar.min(HORIZON * (mast.sqrt() + height.sqrt())).max(NEAR); + let terrain = id.and_then(|i| self.sams.iter().find(|s| s.id == i)).is_some_and(|s| self.horizons.get(&s.id).is_some_and(|h| h.matches(s))); + Some(LowSensor { id: id.unwrap_or(u32::MAX), ant: pos + DVec3::Y * mast, base: pos.y, reach, terrain }) } /// The radars that are up now. @@ -297,13 +308,26 @@ impl Track { } impl Sam { + /// Fictional antenna height and scan characteristics for gameplay roles. + pub(super) fn sensor_mast(&self) -> f64 { + self.kind.spec().mast + if self.kind == SamKind::Radar && self.radar_role == battery::RadarRole::Height { 8.0 } else { 0.0 } + } + pub(super) fn scan_profile(&self) -> Scan { + if self.kind != SamKind::Radar { return self.kind.scan(); } + match self.radar_role { + battery::RadarRole::Multifunction => self.kind.scan(), + battery::RadarRole::Search => Scan { period: 170, agile: false }, + battery::RadarRole::Engagement => Scan { period: 70, agile: true }, + battery::RadarRole::Height => Scan { period: 140, agile: false }, + } + } /// Ticks for its beam to go round (longer when the radar is damaged). pub(super) fn scan_ticks(&self) -> f64 { - self.kind.scan().period as f64 / self.radar_fit().max(0.2) + self.scan_profile().period as f64 / self.radar_fit().max(0.2) } /// Its own radar works: it has one, it is not failed and the unit is deployed. pub fn radar_up(&self) -> bool { - self.alive() && !self.moving() && self.kind.spec().sensor > 0.0 && self.part(Part::Sensor) > 0.0 + self.radar_on && self.mode != battery::SamMode::LauncherOnly && self.alive() && !self.moving() && self.kind.spec().sensor > 0.0 && self.part(Part::Sensor) > 0.0 } pub fn track(&self, id: u32) -> Option<&Track> { self.tracks.iter().find(|t| t.id == id) @@ -339,18 +363,24 @@ impl Sim { /// into the tracks, old tracks out, and the network's tracks shared. pub(super) fn update_sight(&mut self, ground: &mut Ground) { let now = self.tick; + // A role, mast or location change invalidates the cached terrain immediately. + for sam in &self.sams { + if sam.radar_up() && self.horizons.get(&sam.id).is_some_and(|h| !h.matches(sam)) { + self.horizons.insert(sam.id, Horizon::build(antenna(sam), sensor_span(sam), ground, now)); + } + } // One horizon every few ticks: the land answers come in over time. if !self.sams.is_empty() && now % 3 == 0 { let radars: Vec = - (0..self.sams.len()).filter(|&i| self.sams[i].alive() && self.sams[i].kind.spec().sensor > 0.0).collect(); + (0..self.sams.len()).filter(|&i| self.sams[i].radar_up()).collect(); if !radars.is_empty() { let i = radars[(now / 3) as usize % radars.len()]; let sam = &self.sams[i]; - let h = Horizon::build(antenna(sam), sam.kind.spec().sensor, ground, now); + let h = Horizon::build(antenna(sam), sensor_span(sam), ground, now); self.horizons.insert(sam.id, h); } } - let alive: std::collections::HashSet = self.sams.iter().filter(|s| s.alive()).map(|s| s.id).collect(); + let alive: std::collections::HashSet = self.sams.iter().filter(|s| s.radar_up()).map(|s| s.id).collect(); self.horizons.retain(|id, _| alive.contains(id)); // Each drone's height over the land under it (or over the systems' ground, unknown). let heights: Vec> = @@ -371,7 +401,7 @@ impl Sim { index.clear(); index.extend(sam.tracks.iter().enumerate().map(|(k, t)| (t.id, k))); let spec = sam.kind.spec(); - let scan = sam.kind.scan(); + let scan = sam.scan_profile(); let period = sam.scan_ticks(); let w = TAU / period; let (b0, ttl) = (sam.beam, ((period * 3.0) as u32).max(60)); @@ -385,7 +415,7 @@ impl Sim { } let rel = d.pos - at; let dist = rel.length(); - if dist > spec.sensor * 1.2 { + if dist > spec.sensor * if sam.kind == SamKind::Radar { sam.radar_role.reach_factor().max(1.0) * 1.2 } else { 1.2 } { continue; } let locked = sam.target == Some(d.id) || sam.gun_target == Some(d.id); @@ -393,7 +423,11 @@ impl Sim { // The beam passes its bearing this tick (the angle wraps at 2π: the difference is taken round). let in_beam = swept(rel.x.atan2(-rel.z), b0, w); let revisit = scan.agile && held.is_some_and(|t| t.own_tick > 0 && now - t.own_tick >= (period / 3.0) as u64); - if !(in_beam || locked || revisit) { + // A search radar's network track cues this fictional tracking radar to look, but is + // never itself a measurement: reach, terrain, probability and noise still apply. + let cue = sam.kind == SamKind::Radar && sam.radar_role == battery::RadarRole::Engagement + && held.is_some_and(|t| t.own_tick == 0 && t.age(now) <= MISSILE_AGE); + if !(in_beam || locked || revisit || cue) { continue; } let height = h.unwrap_or(d.pos.y - sam.pos.y); @@ -412,7 +446,7 @@ impl Sim { if dist > NEAR && dice(seed, now, key, 0) >= p { continue; } - let sigma = if locked { 0.3 } else { 0.8 + dist * 0.004 }; + let sigma = if locked { 0.3 } else if sam.kind == SamKind::Radar && sam.radar_role == battery::RadarRole::Engagement { 0.45 + dist * 0.002 } else { 0.8 + dist * 0.004 }; let noise = DVec3::new(dice(seed, now, key, 1) - 0.5, (dice(seed, now, key, 2) - 0.5) * 0.5, dice(seed, now, key, 3) - 0.5) * 2.0 * sigma; fresh.push(Track { id: d.id, @@ -432,15 +466,24 @@ impl Sim { } // The network: every unit gets the tracks the radars within data-link range measured themselves, // with the time they were measured: a relayed track is never fresher than its measurement. + // Fictional passive observation: hostile aircraft within 1600 blocks fix an emitting radar + // every 20 ticks. Silence leaves the previous report to age normally. + if now % 20 == 0 { + for s in self.sams.iter().filter(|s| s.radar_up()) { + if self.drones.iter().any(|d| d.alive() && d.pos.distance(s.pos) <= 1600.0) { + self.intel.observe(s, now, ew::Seen::Emission); + } + } + } let shared: Vec<(u32, DVec3, f64, Vec)> = self .sams .iter() - .filter(|s| s.alive() && !s.moving() && s.link_range() > 0.0) - .map(|s| (s.id, s.pos, s.link_range(), s.tracks.iter().filter(|t| t.own_tick == t.tick).copied().collect::>())) + .filter(|s| s.radar_up() && s.link_range() > 0.0) + .map(|s| (s.id, s.pos, s.link_range(), s.tracks.iter().filter(|t| t.own_tick > 0 && t.own_tick == t.tick).copied().collect::>())) .filter(|s| !s.3.is_empty()) .collect(); for sam in &mut self.sams { - if sam.alive() && !sam.moving() && sam.kind.armed() && sam.link_range() > 0.0 && !shared.is_empty() { + if sam.alive() && !sam.moving() && (sam.kind.armed() || (sam.kind == SamKind::Radar && sam.radar_role == battery::RadarRole::Engagement)) && sam.link_range() > 0.0 && !shared.is_empty() { index.clear(); index.extend(sam.tracks.iter().enumerate().map(|(k, t)| (t.id, k))); let mine = sam.link_range(); diff --git a/airdef-mod/src/sim/sites.rs b/airdef-mod/src/sim/sites.rs index 8d1de66..2fcbcf2 100644 --- a/airdef-mod/src/sim/sites.rs +++ b/airdef-mod/src/sim/sites.rs @@ -164,6 +164,10 @@ pub struct Sites { pub list: Vec, next: u32, jobs: Vec<(u32, u32)>, + /// Sites a new wave kept for something still in the air (#108): their kit stays until that is gone. + holding: Vec, + /// Drones that left a site and are still in the air: (drone, site), so their site is not taken away under them. + flying: Vec<(u32, u32)>, } /// What a site says about a drone due to leave. @@ -194,6 +198,8 @@ impl Sites { pub fn clear(&mut self) { self.list.clear(); self.jobs.clear(); + self.holding.clear(); + self.flying.clear(); } pub(super) fn add(&mut self, mut site: LaunchSite) -> u32 { self.next += 1; @@ -205,11 +211,34 @@ impl Sites { pub(super) fn job(&mut self, drone: u32, site: u32) { self.jobs.push((drone, site)); } - /// The old raid's sites go, but not those with jobs still to fly. - pub(super) fn retire(&mut self, queued: &[u32]) { + /// The old raid's sites go, but not those with jobs still to fly, nor those something in the air still came + /// from or is flying to (#108: a missile on its way is not left shooting at a vanished base). + pub(super) fn retire(&mut self, queued: &[u32], refs: &[u32]) { self.jobs.retain(|j| queued.contains(&j.0)); let jobs = &self.jobs; - self.list.retain(|s| jobs.iter().any(|j| j.1 == s.id)); + let mut list = std::mem::take(&mut self.list); + list.retain(|s| jobs.iter().any(|j| j.1 == s.id) || refs.contains(&s.id)); + self.list = list; + // What only the references hold: let go once they are gone, so no old bases pile up. + self.holding = self.list.iter().filter(|s| !jobs.iter().any(|j| j.1 == s.id)).map(|s| s.id).collect(); + } + /// Drops the sites `retire` held only for a flight, a drone or a scout link that is now gone. + pub(super) fn prune(&mut self, refs: &[u32]) { + if self.holding.is_empty() { + return; + } + let live = |id: &u32| refs.contains(id) || self.jobs.iter().any(|j| j.1 == *id); + let gone: Vec = self.holding.iter().copied().filter(|id| !live(id)).collect(); + if gone.is_empty() { + return; + } + self.holding.retain(|id| live(id)); + self.list.retain(|s| !gone.contains(&s.id)); + } + /// Sites drones still in the air left: (drone, site) for those alive in the list, forgetting the others. + pub(super) fn flying_sites(&mut self, alive: &[u32]) -> Vec { + self.flying.retain(|(d, _)| alive.contains(d)); + self.flying.iter().map(|f| f.1).collect() } fn take(&mut self, drone: u32, now: u64) -> Slot { let Some(j) = self.jobs.iter().position(|j| j.0 == drone) else { return Slot::Free }; @@ -229,6 +258,8 @@ impl Sites { s.next_free = now + s.class.cadence(); let air = s.offmap; self.jobs.swap_remove(j); + // A drone in the air still belongs to its site: a new wave must not take the pad from under it (#108). + self.flying.push((drone, site)); if air { Slot::Air } else { Slot::Go } } } @@ -1052,4 +1083,112 @@ mod tests { }; assert_eq!(run(false), run(true)); } + + // ---------------------------------------------------------------- #108: what holds a site beyond its wave + + /// A site with the radars' fix on it, and no launches left: a strike can be sent at it without any drone flying. + fn fixed_site(sim: &mut Sim, pos: DVec3) -> u32 { + let id = sim.sites.add(LaunchSite { + id: 0, + side: 0, + sector: 0, + bearing: 0.0, + class: SiteClass::Ramp, + pos, + range: pos.z, + water: false, + stock: 0, + planned: 0, + alive: true, + next_free: 0, + offmap: false, + }); + sim.strikes.known.push(super::super::strike::Known { + site: id, + class: SiteClass::Ramp, + obs: Vec::new(), + est: Some(super::super::strike::Estimate { + pos: glam::DVec2::new(pos.x, pos.z), + radius: 5.0, + fix: true, + bearing: 0.0, + bearing_err: 0.01, + origin: glam::DVec2::ZERO, + }), + launches: 1, + last: 0, + state: super::super::strike::SiteState::Active, + aimed: false, + }); + id + } + #[test] + fn a_wave_keeps_the_site_a_strike_is_still_flying_to() { + let mut sim = Sim::new(11); + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0)); + let mut ground = Ground::default(); + let mut out = Out::default(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + // The next wave is planned while the missile is in the air: its housekeeping must not take the target away. + let refs = sim.site_refs(); + assert!(refs.contains(&id), "the flight is no reference to its site"); + sim.sites.retire(&[], &refs); + assert!(sim.sites.get(id).is_some(), "a new wave took the site a missile was flying to"); + // The missile lands: the site is hit, and nothing keeps it afterwards. + for _ in 0..300 { + sim.step(&mut ground, None, &mut out); + if sim.strikes.flights.is_empty() { + break; + } + } + assert!(sim.strikes.flights.is_empty()); + assert_eq!(sim.strikes.fired, 1); + assert_eq!(sim.strikes.destroyed + sim.strikes.stunned + sim.strikes.missed, 1, "{:?}", out.messages); + assert!(sim.sites.get(id).is_none(), "the old base stays on the map for ever"); + assert!(sim.strikes.flights.is_empty()); + } + + #[test] + fn a_wave_keeps_the_site_a_drone_left_from_until_that_is_gone() { + let mut sim = Sim::new(11); + let id = sim.sites.add(LaunchSite { + id: 0, + side: 0, + sector: 0, + bearing: 0.0, + class: SiteClass::Ramp, + pos: DVec3::new(0.0, 64.0, 300.0), + range: 300.0, + water: false, + stock: 1, + planned: 1, + alive: true, + next_free: 0, + offmap: false, + }); + let site = sim.sites.get(id).unwrap().clone(); + let d = sim.make_drone(DroneKind::Shahed, site.muzzle(0), -DVec3::Z, DVec3::new(0.0, 70.0, 0.0)); + sim.sites.job(d.id, id); + sim.queue.push((1, d)); + let (mut ground, mut out) = (Ground::default(), Out::default()); + // Let the drone leave the ramp. + let mut left = false; + for _ in 0..200 { + sim.step(&mut ground, None, &mut out); + left |= sim.drones.iter().any(|d| d.alive()); + if left { + break; + } + } + assert!(left, "the drone never left"); + let refs = sim.site_refs(); + assert!(refs.contains(&id), "a drone in the air is no reference to the site it left"); + sim.sites.retire(&[], &refs); + assert!(sim.sites.get(id).is_some(), "the site of a flying drone was taken away"); + // Once the drone is gone, the site is let go: no old bases pile up. + sim.drones.clear(); + let refs = sim.site_refs(); + sim.sites.prune(&refs); + assert!(sim.sites.get(id).is_none(), "an old base stays after its last drone"); + } } diff --git a/airdef-mod/src/sim/specs_generated.rs b/airdef-mod/src/sim/specs_generated.rs new file mode 100644 index 0000000..d2347a2 --- /dev/null +++ b/airdef-mod/src/sim/specs_generated.rs @@ -0,0 +1,542 @@ +// Generated by tools/gen_catalog.py from catalog/specs.json. Do not edit. +// Game-scale values, not real weapon data; the catalog is the only copy. + +/// Share of the old slew speed a launcher keeps when it has no rate of its own. +const DRIVE_SCALE: f64 = 0.4; +/// Ticks a drive takes to reach its top speed (and to stop from it). +const DRIVE_RAMP: f64 = 8.0; +const DRIVE_TRAINABLE: (f64, f64) = (0.12, 0.0); +const DRIVE_VERTICAL: (f64, f64) = (0.15, 0.0); +const DRIVE_OTHER: (f64, f64) = (0.05, 0.05); +/// Ballistic flight's gravity, blocks a tick². +const GRAVITY: f64 = 0.05; +/// Height over the target where the Oreshnik's blocks release their submunitions. +const BLOCK_SPLIT: f64 = 300.0; +/// How near a depot a system reloads straight from it. +const DEPOT_REACH: f64 = 60.0; +/// How near a truck must come to hand a reload over, or to load up at a depot. +const HANDOVER: f64 = 10.0; +/// What a truck carries. +const TRUCK_LOAD: u32 = 2; +const STOCK_LIMIT: u32 = 1000000; + +const DRONE_SPEC_SHAHED: DroneSpec = DroneSpec { + name: "Шахед-136", + key: "shahed", + model: "airdef:shahed", + speed: 0.6, + turn: 0.035, + cruise: 45.0, + clearance: 18.0, + dive: 32.0, + warhead: 3, + scale: 1.3, + engine: Engine::Prop(1.34, 1.0), + decoy: false, + stealth: 1.0, + rcs: 0.3, + sound: (sound::DRONE_SHAHED, 1.0, 48), + class: Class::Drone, + ballistic: None, + launch_sound: sound::LAUNCH_S400_EJECT, +}; + +const DRONE_SPEC_GERAN3: DroneSpec = DroneSpec { + name: "Герань-3", + key: "geran3", + model: "airdef:geran3", + speed: 1.6, + turn: 0.03, + cruise: 60.0, + clearance: 18.0, + dive: 50.0, + warhead: 2, + scale: 1.15, + engine: Engine::Jet(0.34, 1.55, 1.0), + decoy: false, + stealth: 1.1, + rcs: 0.4, + sound: (sound::DRONE_GERAN3, 1.0, 40), + class: Class::Jet, + ballistic: None, + launch_sound: sound::LAUNCH_S400_EJECT, +}; + +const DRONE_SPEC_LANCET: DroneSpec = DroneSpec { + name: "Ланцет-3", + key: "lancet", + model: "airdef:lancet", + speed: 1.1, + turn: 0.07, + cruise: 25.0, + clearance: 10.0, + dive: 38.0, + warhead: 1, + scale: 0.9, + engine: Engine::Prop(0.93, 0.55), + decoy: false, + stealth: 1.4, + rcs: 0.08, + sound: (sound::DRONE_LANCET, 1.0, 32), + class: Class::Drone, + ballistic: None, + launch_sound: sound::LAUNCH_S400_EJECT, +}; + +const DRONE_SPEC_GERBERA: DroneSpec = DroneSpec { + name: "Гербера", + key: "gerbera", + model: "airdef:gerbera", + speed: 0.5, + turn: 0.04, + cruise: 40.0, + clearance: 15.0, + dive: 30.0, + warhead: 0, + scale: 1.1, + engine: Engine::Prop(1.03, 0.9), + decoy: true, + stealth: 0.9, + rcs: 0.4, + sound: (sound::DRONE_SHAHED, 1.3, 40), + class: Class::Drone, + ballistic: None, + launch_sound: sound::LAUNCH_S400_EJECT, +}; + +const DRONE_SPEC_KH101: DroneSpec = DroneSpec { + name: "Х-101", + key: "kh101", + model: "airdef:kh101", + speed: 2.4, + turn: 0.025, + cruise: 0.0, + clearance: 12.0, + dive: 26.0, + warhead: 6, + scale: 1.6, + engine: Engine::Jet(-0.27, 1.4, 0.6), + decoy: false, + stealth: 1.5, + rcs: 0.05, + sound: (sound::DRONE_CRUISE, 1.0, 50), + class: Class::Cruise, + ballistic: None, + launch_sound: sound::LAUNCH_PATRIOT, +}; + +const DRONE_SPEC_KALIBR: DroneSpec = DroneSpec { + name: "Калибр", + key: "kalibr", + model: "airdef:kalibr", + speed: 2.1, + turn: 0.025, + cruise: 0.0, + clearance: 15.0, + dive: 26.0, + warhead: 5, + scale: 1.5, + engine: Engine::Jet(-0.2, 1.5, 0.6), + decoy: false, + stealth: 1.3, + rcs: 0.15, + sound: (sound::DRONE_CRUISE, 1.2, 50), + class: Class::Cruise, + ballistic: None, + launch_sound: sound::LAUNCH_PATRIOT, +}; + +const DRONE_SPEC_KH22: DroneSpec = DroneSpec { + name: "Х-22", + key: "kh22", + model: "airdef:kh22", + speed: 4.0, + turn: 0.03, + cruise: 140.0, + clearance: 40.0, + dive: 150.0, + warhead: 7, + scale: 1.8, + engine: Engine::Rocket(1.5, 1.3), + decoy: false, + stealth: 1.4, + rcs: 1.0, + sound: (sound::DRONE_ROCKET, 0.8, 46), + class: Class::Supersonic, + ballistic: None, + launch_sound: sound::LAUNCH_PATRIOT, +}; + +const DRONE_SPEC_ZIRCON: DroneSpec = DroneSpec { + name: "Циркон", + key: "zircon", + model: "airdef:zircon", + speed: 5.5, + turn: 0.025, + cruise: 90.0, + clearance: 30.0, + dive: 110.0, + warhead: 6, + scale: 1.4, + engine: Engine::Rocket(1.5, 1.1), + decoy: false, + stealth: 2.0, + rcs: 0.4, + sound: (sound::DRONE_ROCKET, 1.35, 46), + class: Class::Supersonic, + ballistic: None, + launch_sound: sound::LAUNCH_S400, +}; + +const DRONE_SPEC_ISKANDER: DroneSpec = DroneSpec { + name: "Искандер-М", + key: "iskander", + model: "airdef:iskander", + speed: 5.0, + turn: 0.04, + cruise: 0.0, + clearance: 0.0, + dive: 0.0, + warhead: 8, + scale: 1.8, + engine: Engine::Rocket(1.5, 1.6), + decoy: false, + stealth: 1.6, + rcs: 0.6, + sound: (sound::DRONE_ROCKET, 0.7, 46), + class: Class::Ballistic, + ballistic: Some(1000.0), + launch_sound: sound::LAUNCH_S500, +}; + +const DRONE_SPEC_KINZHAL: DroneSpec = DroneSpec { + name: "Кинжал", + key: "kinzhal", + model: "airdef:kinzhal", + speed: 7.5, + turn: 0.03, + cruise: 0.0, + clearance: 0.0, + dive: 0.0, + warhead: 8, + scale: 1.7, + engine: Engine::Rocket(1.5, 1.5), + decoy: false, + stealth: 2.2, + rcs: 0.5, + sound: (sound::DRONE_ROCKET, 0.6, 46), + class: Class::Ballistic, + ballistic: Some(1500.0), + launch_sound: sound::LAUNCH_S500, +}; + +const DRONE_SPEC_ORESHNIK: DroneSpec = DroneSpec { + name: "Орешник", + key: "oreshnik", + model: "airdef:iskander", + speed: 9.0, + turn: 0.0, + cruise: 0.0, + clearance: 0.0, + dive: 0.0, + warhead: 0, + scale: 2.6, + engine: Engine::Rocket(1.5, 2.4), + decoy: false, + stealth: 3.0, + rcs: 2.0, + sound: (sound::DRONE_ROCKET, 0.5, 46), + class: Class::Ballistic, + ballistic: Some(2600.0), + launch_sound: sound::LAUNCH_S500, +}; + +const DRONE_SPEC_ORESHNIKBLOCK: DroneSpec = DroneSpec { + name: "Орешник: блок", + key: "oreshnik_block", + model: "airdef:reentry", + speed: 9.0, + turn: 0.0, + cruise: 0.0, + clearance: 0.0, + dive: 0.0, + warhead: 4, + scale: 1.6, + engine: Engine::Rocket(1.0, 1.0), + decoy: false, + stealth: 3.5, + rcs: 0.4, + sound: (sound::DRONE_BALLISTIC, 0.8, 9999), + class: Class::Ballistic, + ballistic: None, + launch_sound: sound::LAUNCH_S500, +}; + +const DRONE_SPEC_ORESHNIKSUB: DroneSpec = DroneSpec { + name: "Орешник: суббоеприпас", + key: "oreshnik_sub", + model: "airdef:reentry", + speed: 9.0, + turn: 0.05, + cruise: 0.0, + clearance: 0.0, + dive: 0.0, + warhead: 3, + scale: 1.1, + engine: Engine::Rocket(1.0, 1.0), + decoy: false, + stealth: 4.0, + rcs: 0.08, + sound: (sound::DRONE_BALLISTIC, 1.0, 9999), + class: Class::Ballistic, + ballistic: None, + launch_sound: sound::LAUNCH_S500, +}; + +const MISSILE_SPEC_PAC3: MissileSpec = MissileSpec { + model: "airdef:pac3", + speed: 5.5, + accel: 0.35, + turn: 0.3, + boost: 8, + cold: 0, + eject: 0.0, + eject_drag: 0.0, + pk: 0.9, + fuse: 4.0, + life: 240, + scale: 1.0, + flame: 1.0, + rear: 28.0, + seeker: 110.0, + launch_sound: sound::LAUNCH_PATRIOT, + voice: (sound::MISSILE_FLIGHT, 1.0, 1.0), +}; + +const MISSILE_SPEC_M57E6: MissileSpec = MissileSpec { + model: "airdef:m57e6", + speed: 5.0, + accel: 0.5, + turn: 0.32, + boost: 5, + cold: 0, + eject: 0.0, + eject_drag: 0.0, + pk: 0.82, + fuse: 3.0, + life: 160, + scale: 0.85, + flame: 0.65, + rear: 28.0, + seeker: 0.0, + launch_sound: sound::LAUNCH_PANTSIR, + voice: (sound::MISSILE_FLIGHT, 1.0, 1.12), +}; + +const MISSILE_SPEC_M48N6: MissileSpec = MissileSpec { + model: "airdef:m48n6", + speed: 7.0, + accel: 0.5, + turn: 0.22, + boost: 8, + cold: 20, + eject: 2.4, + eject_drag: 0.07, + pk: 0.9, + fuse: 5.0, + life: 300, + scale: 1.5, + flame: 1.4, + rear: 30.0, + seeker: 120.0, + launch_sound: sound::LAUNCH_S400_EJECT, + voice: (sound::MISSILE_FLIGHT, 1.0, 0.88), +}; + +const MISSILE_SPEC_M77N6: MissileSpec = MissileSpec { + model: "airdef:m77n6", + speed: 9.0, + accel: 0.6, + turn: 0.22, + boost: 8, + cold: 24, + eject: 2.6, + eject_drag: 0.07, + pk: 0.95, + fuse: 6.0, + life: 320, + scale: 1.7, + flame: 1.6, + rear: 32.0, + seeker: 120.0, + launch_sound: sound::LAUNCH_S500_EJECT, + voice: (sound::MISSILE_FLIGHT, 1.0, 0.8), +}; + +const MISSILE_SPEC_IRIST: MissileSpec = MissileSpec { + model: "airdef:irist_missile", + speed: 7.0, + accel: 0.5, + turn: 0.35, + boost: 6, + cold: 8, + eject: 1.8, + eject_drag: 0.1, + pk: 0.9, + fuse: 4.0, + life: 260, + scale: 0.95, + flame: 0.9, + rear: 28.0, + seeker: 90.0, + launch_sound: sound::LAUNCH_S400_EJECT, + voice: (sound::MISSILE_FLIGHT, 1.0, 1.05), +}; + +const MISSILE_SPEC_AMRAAM: MissileSpec = MissileSpec { + model: "airdef:amraam_missile", + speed: 6.0, + accel: 0.45, + turn: 0.3, + boost: 6, + cold: 0, + eject: 0.0, + eject_drag: 0.0, + pk: 0.85, + fuse: 4.0, + life: 260, + scale: 0.9, + flame: 0.9, + rear: 28.0, + seeker: 130.0, + launch_sound: sound::LAUNCH_PATRIOT, + voice: (sound::MISSILE_FLIGHT, 1.0, 1.05), +}; + +const MISSILE_SPEC_M9M317: MissileSpec = MissileSpec { + model: "airdef:m48n6", + speed: 6.5, + accel: 0.5, + turn: 0.25, + boost: 8, + cold: 0, + eject: 0.0, + eject_drag: 0.0, + pk: 0.85, + fuse: 5.0, + life: 300, + scale: 1.1, + flame: 1.2, + rear: 32.0, + seeker: 90.0, + launch_sound: sound::LAUNCH_PATRIOT, + voice: (sound::MISSILE_FLIGHT, 1.0, 0.9), +}; + +const MISSILE_SPEC_M9M338: MissileSpec = MissileSpec { + model: "airdef:m57e6", + speed: 5.5, + accel: 0.6, + turn: 0.4, + boost: 4, + cold: 8, + eject: 1.6, + eject_drag: 0.1, + pk: 0.9, + fuse: 3.0, + life: 160, + scale: 1.0, + flame: 0.8, + rear: 28.0, + seeker: 70.0, + launch_sound: sound::LAUNCH_S400_EJECT, + voice: (sound::MISSILE_FLIGHT, 1.0, 1.15), +}; + +const MISSILE_SPEC_P1SUN: MissileSpec = MissileSpec { + model: "airdef:p1sun", + speed: 2.8, + accel: 0.12, + turn: 0.3, + boost: 4, + cold: 0, + eject: 0.0, + eject_drag: 0.0, + pk: 0.8, + fuse: 3.0, + life: 900, + scale: 0.8, + flame: 0.0, + rear: 10.0, + seeker: 60.0, + launch_sound: sound::DRONE_LANCET, + voice: (sound::DRONE_LANCET, 0.8, 1.6), +}; + +impl DroneKind { + /// Its row of numbers from `catalog/specs.json`. + pub fn spec(self) -> &'static DroneSpec { + match self { + DroneKind::Shahed => &DRONE_SPEC_SHAHED, + DroneKind::Geran3 => &DRONE_SPEC_GERAN3, + DroneKind::Lancet => &DRONE_SPEC_LANCET, + DroneKind::Gerbera => &DRONE_SPEC_GERBERA, + DroneKind::Kh101 => &DRONE_SPEC_KH101, + DroneKind::Kalibr => &DRONE_SPEC_KALIBR, + DroneKind::Kh22 => &DRONE_SPEC_KH22, + DroneKind::Zircon => &DRONE_SPEC_ZIRCON, + DroneKind::Iskander => &DRONE_SPEC_ISKANDER, + DroneKind::Kinzhal => &DRONE_SPEC_KINZHAL, + DroneKind::Oreshnik => &DRONE_SPEC_ORESHNIK, + DroneKind::OreshnikBlock => &DRONE_SPEC_ORESHNIKBLOCK, + DroneKind::OreshnikSub => &DRONE_SPEC_ORESHNIKSUB, + } + } +} + +impl MissileKind { + fn spec(self) -> &'static MissileSpec { + match self { + MissileKind::Pac3 => &MISSILE_SPEC_PAC3, + MissileKind::M57e6 => &MISSILE_SPEC_M57E6, + MissileKind::M48n6 => &MISSILE_SPEC_M48N6, + MissileKind::M77n6 => &MISSILE_SPEC_M77N6, + MissileKind::IrisT => &MISSILE_SPEC_IRIST, + MissileKind::Amraam => &MISSILE_SPEC_AMRAAM, + MissileKind::M9m317 => &MISSILE_SPEC_M9M317, + MissileKind::M9m338 => &MISSILE_SPEC_M9M338, + MissileKind::P1sun => &MISSILE_SPEC_P1SUN, + } + } +} + +/// Save keys by kind; a rename needs an explicit migration. +#[cfg(test)] +pub(super) const DRONE_SPEC_KEYS: [(DroneKind, &str); 13] = [ + (DroneKind::Shahed, "shahed"), + (DroneKind::Geran3, "geran3"), + (DroneKind::Lancet, "lancet"), + (DroneKind::Gerbera, "gerbera"), + (DroneKind::Kh101, "kh101"), + (DroneKind::Kalibr, "kalibr"), + (DroneKind::Kh22, "kh22"), + (DroneKind::Zircon, "zircon"), + (DroneKind::Iskander, "iskander"), + (DroneKind::Kinzhal, "kinzhal"), + (DroneKind::Oreshnik, "oreshnik"), + (DroneKind::OreshnikBlock, "oreshnik_block"), + (DroneKind::OreshnikSub, "oreshnik_sub"), +]; + +#[cfg(test)] +pub(super) const MISSILE_SPEC_MODELS: [(MissileKind, &str); 9] = [ + (MissileKind::Pac3, "airdef:pac3"), + (MissileKind::M57e6, "airdef:m57e6"), + (MissileKind::M48n6, "airdef:m48n6"), + (MissileKind::M77n6, "airdef:m77n6"), + (MissileKind::IrisT, "airdef:irist_missile"), + (MissileKind::Amraam, "airdef:amraam_missile"), + (MissileKind::M9m317, "airdef:m48n6"), + (MissileKind::M9m338, "airdef:m57e6"), + (MissileKind::P1sun, "airdef:p1sun"), +]; diff --git a/airdef-mod/src/sim/strike.rs b/airdef-mod/src/sim/strike.rs index 54ec70a..a96ac87 100644 --- a/airdef-mod/src/sim/strike.rs +++ b/airdef-mod/src/sim/strike.rs @@ -3,38 +3,54 @@ //! **Finding a site.** Nothing is known by magic. A site shows itself two ways, and only to a radar that is up: //! - a launch inside a radar's reach (low targets: the radio horizon, the hills): a *point* sighting, good to a few //! percent of the distance; -//! - a launched drone held on a radar's own track: the track points back to where it came from, a *ray* from where it was -//! picked up, a few degrees wide, and the time since the launch (the alarm) at the drone's speed gives how far: a range -//! good to some percent plus a second of launch time. Two radars holding it give two sightings. A ballistic missile's arc -//! says more: a point sighting, a few percent of the range. +//! - a drone first detected later supplies a tentative reverse-course bearing from two measured track positions. +//! A turn before detection can invalidate that bearing, so it is deliberately broad and cannot locate the site. +//! Multiple sensors following the same flight do not count as independent launch observations. //! -//! The sightings of one site are combined by weighted least squares (`solve`): points average, a ray with a range is -//! a long thin ellipse along it, a bare ray pulls across itself only, and each weighs by how sure it is. The answer carries its own error (`radius`, the 90% circle), which -//! shrinks with the launches seen and with a second radar holding the same drones. A site whose sightings pin no place -//! down (bare rays along one line, a circle wider than `max_fix_radius`) is *a bearing*, not yet a place (no `fix`), and -//! cannot be struck. +//! Directly observed launches provide independent point sightings, combined by weighted least squares (`solve`). +//! A site without a sufficiently accurate fix remains a bearing and cannot be struck. //! -//! **Striking it.** `order_strike`: one of a few strike missiles (a stock that refills slowly), fired from where the -//! player stands at the *estimated* place. It flies `STRIKE_SPEED`; on arrival the enemy's cover may shoot it down; else -//! it lands with its own scatter. The true distance from the site decides: inside `HIT_R` the site is destroyed (stock -//! burnt, jobs cancelled, wreck and fire), inside `SUPPRESS_R` it is stunned for a while (its launches are delayed, not -//! cancelled), else it hit the ground beside it and nothing changed. So a badly known site is very likely a wasted missile. +//! **Striking it.** A manual order spends a prepared, explicitly typed round from a ready physical carrier. +//! There is no virtual enemy air defence. Only a simulated interceptor can justify an intercepted outcome. //! //! **Off the map** (`offmap`: no land was found or loaded, so no kit stands anywhere): the site is scouted like any other, //! as bearing and range, but there is nothing to see burn. A strike that would have hit just ends its launches //! ("halts" them) and says so; the result is not drawn, and a miss is lost beyond the loaded map. use super::*; -use super::sites::{LaunchSite, SiteClass}; +use super::sites::SiteClass; +#[cfg(test)] +use super::sites::LaunchSite; use glam::DVec2; +/// Minecraft presentation profile; neither dimensions nor timing reproduce a real weapon. +pub const ROUND_MODEL: &str = "airdef:rm48u"; +pub const ROUND_SCALE: f64 = 1.5; +/// Ticks the gas throw takes before the motor lights: the same staged cold launch as the +/// interceptor's `cold` (#129), so a physical strike shows the unlit ejection too. +pub const EJECT_TICKS: u64 = 20; + +/// The unlit ejection's height at flight age `age`: a gas throw of 2.2 blocks a tick with a +/// linear drag of 0.09 a tick², integrated (game scale, like the interceptor's `eject`). +fn eject_rise(age: u64) -> f64 { + let a = age as f64; + (2.2 * a - 0.045 * a * a).max(0.0) +} + +/// Shared live/replay appearance, including the unlit cold-ejection phase. +pub(super) fn round_visual(out: &mut Out, id: u32, pos: DVec3, rot: DQuat, lit: bool) { + out.model(id, ROUND_MODEL, pos, rot, ROUND_SCALE, false); + if lit { + let spec = MissileKind::M48n6.spec(); + let back = (spec.rear - 8.0) / 16.0 * ROUND_SCALE + spec.flame; + out.model(id + 1, "airdef:flame", pos + rot * DVec3::new(0.0, 0.0, back), rot, spec.flame, true); + } +} + + /// Every number of the mechanic. pub struct Tune { /// Angular error of a track pointing back (radians, 1 sigma): 3 degrees. pub track_sigma: f64, - /// The range from the time of flight: error as a part of the range, and ticks of launch time (times the speed). - pub range_sigma: (f64, f64), - /// A ballistic missile's launch point from its arc: error as a part of the distance, and the least. - pub ballistic_sigma: (f64, f64), /// A launch seen by a radar in reach: error as a part of the distance, and the least (blocks). pub seen_sigma: (f64, f64), /// How high a launch is, for the radar's horizon (blocks over the ground). @@ -50,6 +66,9 @@ pub struct Tune { pub stock: u32, pub stock_max: u32, pub resupply: u32, + /// Ticks between the checkpoints at which the refilling progress is written to a save: a restart is at most + /// this much of the wait to the next missile behind (#113). + pub progress_save: u32, /// Blocks a tick, the farthest target (blocks), and the cruise height over the launch (blocks). pub speed: f64, pub range: f64, @@ -61,9 +80,6 @@ pub struct Tune { pub suppress_r: f64, /// Ticks a stunned site launches nothing. pub suppress_ticks: u64, - /// The chance the enemy's cover shoots the missile down: none, light, heavy; and the shares of sites that have them. - pub cover_p: [f64; 3], - pub cover_share: [f64; 2], /// Ticks the wreck of a destroyed site smokes. pub burn: u64, /// A launched drone not taken into account after this many ticks is forgotten. @@ -72,8 +88,6 @@ pub struct Tune { pub const TUNE: Tune = Tune { track_sigma: 0.052, - range_sigma: (0.08, 20.0), - ballistic_sigma: (0.05, 20.0), seen_sigma: (0.04, 8.0), launch_height: 4.0, max_fix_radius: 800.0, @@ -83,6 +97,7 @@ pub const TUNE: Tune = Tune { stock: 3, stock_max: 4, resupply: 6000, + progress_save: 100, speed: 8.0, range: 4500.0, height: 40.0, @@ -90,8 +105,6 @@ pub const TUNE: Tune = Tune { hit_r: 35.0, suppress_r: 90.0, suppress_ticks: 600, - cover_p: [0.0, 0.3, 0.65], - cover_share: [0.4, 0.75], burn: 1800, scout_life: 1800, }; @@ -100,8 +113,8 @@ pub const TUNE: Tune = Tune { const RADIUS_K: f64 = 3.0; /// One sighting: a point (`dir` none, `sigma` in blocks), a ray from `at` along `dir` (`sigma` in radians), or a ray with -/// a range on it (`range`: blocks from `at` to the site and its error): the track says where from, and the time since -/// the launch flash at the alarm, at the drone's speed, how far. +/// a range on it (`range`: blocks from `at` to the site and its error). A range requires an independent measurement; +/// a late track by itself does not provide one. #[derive(Clone, Copy, Debug)] pub struct Obs { pub at: DVec2, @@ -246,31 +259,69 @@ pub struct Known { pub launches: u32, pub last: u64, pub state: SiteState, - /// What a strike taught of its cover (0 none, 1 light, 2 heavy). - pub cover: Option, /// Where the strike on it is aimed, if one is on its way. pub aimed: bool, } -/// A launched drone still to be picked up on a track: (drone, site, class, launched at). +/// A launched drone awaiting track history. `at` is used only to expire this bookkeeping. #[derive(Clone, Copy, Debug)] struct Flown { drone: u32, site: u32, class: SiteClass, at: u64, + sample: Option<(u32, u64, DVec2)>, } -/// A strike missile in flight. +/// The target of a strike as it was when the missile left: what the outcome is judged against, even if the site +/// has been taken off the map since (#108). +#[derive(Clone, Copy, Debug)] +pub struct Target { + /// Where the site really stands (x, z). + pub pos: DVec2, + pub stock: u32, + pub offmap: bool, + pub alive: bool, +} + +/// A strike missile in flight: the shot it was fired as, where it is, and where it is going. #[derive(Clone, Copy, Debug)] pub struct Flight { pub id: u32, pub site: u32, + /// Physical launcher id, absent only for old test fixtures. + pub carrier: Option, + /// Where it was fired from, and where it is now (the swept step, not the plan). pub from: DVec3, - /// The aim (x, z). + pub pos: DVec3, + /// The aim (x, z) the player was told of, and the place it really lands: the aim plus its own scatter, + /// fixed at the launch so the flight and its outcome name the same point. pub aim: DVec2, + pub land: DVec2, pub start: u64, pub arrive: u64, + /// The site as it was at the launch. + pub snap: Target, + /// Ticks it was held at an unread stretch of the world (#61). + hold: u16, +} + +impl Flight { + /// The id of its track in a recording (a range of its own, apart from the drones'). + pub fn track(&self) -> u32 { + 0x7100_0000 | self.id + } + /// The key of its exact-shape questions, apart from those of the drones' and interceptors'. + fn key(&self) -> u32 { + 0x9000_0000 | self.id + } +} + +#[derive(Clone, Copy, Debug)] +pub(super) struct PendingStrike { + carrier: u32, + site: u32, + aim: DVec2, } #[derive(Default)] @@ -280,6 +331,7 @@ pub struct Strikes { pub progress: u32, pub known: Vec, pub flights: Vec, + pub(super) pending: Vec, flown: Vec, next: u32, /// Smoking wrecks: (where, until). @@ -294,8 +346,6 @@ pub struct Strikes { pub stunned: u32, pub missed: u32, pub shot_down: u32, - /// The enemy's cover forced (tests); else it follows the seed. - pub cover_override: Option, } impl Strikes { @@ -314,6 +364,10 @@ impl Strikes { pub fn seconds_to_next(&self) -> u32 { (TUNE.resupply - self.progress.min(TUNE.resupply)) / 20 } + /// Sites a living launched drone not yet picked up on a track still came from (#108). + pub(super) fn scouted<'a>(&'a self, alive: &'a [u32]) -> impl Iterator + 'a { + self.flown.iter().filter(move |f| alive.contains(&f.drone)).map(|f| f.site) + } } /// A standard normal from the seed and three numbers (the combat's random sequence is left alone). @@ -341,8 +395,9 @@ impl Known { let place = match &self.est { Some(e) if e.fix => { let d = e.pos - DVec2::new(from.x, from.z); - format!("{:.0}° {}, {}, ±{:.0} м ({})", compass(d).to_degrees(), wind(compass(d)), crate::panel::distance(d.length()), e.radius, accuracy(e)) + format!("{:.0}° {}, {}, ±{:.0} м ({})", compass(d).to_degrees(), wind(compass(d)), super::distance(d.length()), e.radius, accuracy(e)) } + Some(e) if e.bearing_err >= std::f64::consts::PI => "место неизвестно: обратный курс цели не определяет место старта".into(), Some(e) => format!("{:.0}° {} ±{:.0}°, дальность неизвестна (только азимут)", e.bearing.to_degrees(), wind(e.bearing), e.bearing_err.to_degrees().max(1.0)), None => "место неизвестно".into(), }; @@ -359,39 +414,17 @@ impl Known { } } }; - let cover = match self.cover { - Some(0) => " · ПВО не прикрыта", - Some(_) => " · прикрыта ПВО", - None => "", - }; - format!("ПУ №{} ({}): {place}{state}{cover}; пусков замечено {}", self.site, class_name(self.class), self.launches) + format!("ПУ №{} ({}): {place}{state}; пусков замечено {}", self.site, class_name(self.class), self.launches) } } impl Sim { - /// Whether the enemy covers this site: 0 none, 1 light, 2 heavy; fixed by the seed and the site. - pub fn cover_of(&self, site: &LaunchSite) -> u8 { - if let Some(c) = self.strikes.cover_override { - return c; - } - let r = dice(self.seed, site.id as u64, site.side as u64, 101); - // A ballistic battery is a prize: guarded more often. - let r = if site.class == SiteClass::Battery { r * 0.7 } else { r }; - if r < TUNE.cover_share[0] { - 0 - } else if r < TUNE.cover_share[1] { - 1 - } else { - 2 - } - } - /// A drone just left site `site` (a launch the radars may have seen). pub(super) fn scout_launch(&mut self, drone: u32, site: u32, kind: DroneKind, at: DVec3) { let Some(s) = self.sites.get(site) else { return }; - let (class, offmap, pos) = (s.class, s.offmap, s.pos); + let (class, offmap) = (s.class, s.offmap); if self.strikes.flown.len() < 400 { - self.strikes.flown.push(Flown { drone, site, class, at: self.tick }); + self.strikes.flown.push(Flown { drone, site, class, at: self.tick, sample: None }); } if offmap { return; @@ -412,7 +445,7 @@ impl Sim { if let Some(dist) = seen { let sigma = (dist * TUNE.seen_sigma.0).max(TUNE.seen_sigma.1); let (gx, gz) = (gauss(self.seed, drone as u64, site as u64, 1), gauss(self.seed, drone as u64, site as u64, 2)); - let at = DVec2::new(pos.x + gx * sigma, pos.z + gz * sigma); + let at = DVec2::new(at.x + gx * sigma, at.z + gz * sigma); self.add_obs(site, class, Obs::point(at, sigma)); } } @@ -423,12 +456,17 @@ impl Sim { let i = match self.strikes.known.iter().position(|k| k.site == site) { Some(i) => i, None => { - self.strikes.known.push(Known { site, class, obs: Vec::new(), est: None, launches: 0, last: now, state: SiteState::Active, cover: None, aimed: false }); + self.strikes.known.push(Known { site, class, obs: Vec::new(), est: None, launches: 0, last: now, state: SiteState::Active, aimed: false }); self.strikes.known.len() - 1 } }; let before = self.strikes.known[i].est; let k = &mut self.strikes.known[i]; + // A course bearing is only a weak hypothesis. Once a launch is seen, do not + // let that hypothesis pull the measured point away from its actual location. + if obs.dir.is_none() { + k.obs.retain(|o| o.dir.is_none()); + } k.obs.push(obs); if k.obs.len() > TUNE.keep_obs { k.obs.remove(0); @@ -441,6 +479,7 @@ impl Sim { let words = |e: &Option| e.as_ref().map(accuracy); let msg = match (&before, &k.est) { (None, Some(e)) if e.fix => Some(format!("РЛС засекла пуски: новая ПУ №{} ({}), место определено ±{:.0} м", site, class_name(class), e.radius)), + (None, Some(e)) if e.bearing_err >= std::f64::consts::PI => Some(format!("РЛС обнаружила цель с ПУ №{} ({}); место старта неизвестно", site, class_name(class))), (None, Some(e)) => Some(format!("РЛС засекла пуски: новая ПУ №{} ({}), азимут {:.0}° {}", site, class_name(class), e.bearing.to_degrees(), wind(e.bearing))), (Some(b), Some(e)) if !b.fix && e.fix => Some(format!("ПУ №{}: место определено, ±{:.0} м ({})", site, e.radius, accuracy(e))), (Some(_), Some(_)) if words(&before) != words(&k.est) => Some(format!("ПУ №{}: точность {}, ±{:.0} м", site, accuracy(k.est.as_ref().unwrap()), k.est.as_ref().unwrap().radius)), @@ -456,6 +495,7 @@ impl Sim { pub(super) fn step_strikes(&mut self, ground: &mut Ground, player: Option, out: &mut Out) { let now = self.tick; out.messages.append(&mut self.strikes.news); + #[cfg(any(test, feature = "test-fixtures"))] if self.strikes.stock < TUNE.stock_max { self.strikes.progress += 1; if self.strikes.progress >= TUNE.resupply { @@ -469,41 +509,43 @@ impl Sim { let mut new: Vec<(u32, SiteClass, Obs)> = Vec::new(); let flown = std::mem::take(&mut self.strikes.flown); let mut keep = Vec::with_capacity(flown.len()); - for f in flown { - let Some(d) = self.drones.iter().find(|d| d.id == f.drone).filter(|d| d.alive()) else { continue }; - if now.saturating_sub(f.at) > TUNE.scout_life { + for mut f in flown { + if !self.drones.iter().any(|d| d.id == f.drone && d.alive()) || now.saturating_sub(f.at) > TUNE.scout_life { continue; } - // The tracks of the (up to two) systems whose own radar holds it: each is a sighting of its own. - let mut held: Vec<(u32, sight::Track)> = self.sams.iter().filter(|s| s.alive()).filter_map(|s| s.track(f.drone).filter(|t| t.own_tick > 0).map(|t| (s.id, *t))).collect(); - if held.is_empty() { - keep.push(f); + // A single tentative bearing per site avoids treating correlated samples, + // sensors or flights following the same route as independent evidence. + // Directly observed launches, if any, already carry stronger information. + if self.strikes.known.iter().any(|k| k.site == f.site && !k.obs.is_empty()) + || new.iter().any(|(site, _, _)| *site == f.site) + { continue; } - held.sort_by_key(|h| std::cmp::Reverse(h.1.tick)); - held.truncate(2); - let Some(site) = self.sites.get(f.site) else { continue }; - let truth = DVec2::new(site.pos.x, site.pos.z); - for (sam, t) in held { - let from = DVec2::new(t.pos.x, t.pos.z); - let away = truth - from; - let dist = away.length(); - if dist < 30.0 { - continue; - } - let (k, c) = (f.drone as u64, f.site as u64 * 64 + sam as u64 % 64); - if d.kind.spec().ballistic.is_some() { - let sigma = (dist * TUNE.ballistic_sigma.0).max(TUNE.ballistic_sigma.1); - let at = DVec2::new(truth.x + gauss(self.seed, k, c, 3) * sigma, truth.y + gauss(self.seed, k, c, 4) * sigma); - new.push((f.site, f.class, Obs::point(at, sigma))); + let tracks = self.sams.iter().filter(|s| s.alive()).filter_map(|s| { + s.track(f.drone).filter(|t| t.own_tick > 0 && t.tick == t.own_tick).map(|t| (s.id, *t)) + }); + let held = if let Some((sam, _, _)) = f.sample { + tracks.filter(|(id, _)| *id == sam).max_by_key(|(_, t)| t.own_tick) + } else { + tracks.max_by_key(|(_, t)| t.own_tick) + }; + let Some((sam, t)) = held else { keep.push(f); continue }; + let pos = DVec2::new(t.pos.x, t.pos.z); + if let Some((_, first_tick, first_pos)) = f.sample { + // Require two distinct measurements and enough movement; never infer + // elapsed flight time from the unseen launch bookkeeping. + let delta = pos - first_pos; + if t.own_tick > first_tick && delta.length() >= 20.0 { + let direction = -delta.normalize(); + // Course before detection may have turned substantially. This is + // a broad indication, never a ranged or ballistic point estimate. + new.push((f.site, f.class, Obs::ray(first_pos, direction, std::f64::consts::PI))); } else { - // Bearing from the track; range from the time of flight since the launch (the alarm), at the - // drone's speed: a fraction of the range and a second of launch time. - let a = away.y.atan2(away.x) + gauss(self.seed, k, c, 5) * TUNE.track_sigma; - let sr = dist * TUNE.range_sigma.0 + d.kind.spec().speed * TUNE.range_sigma.1; - let r = (dist + gauss(self.seed, k, c, 6) * sr).max(20.0); - new.push((f.site, f.class, Obs { range: Some((r, sr)), ..Obs::ray(from, DVec2::new(a.cos(), a.sin()), TUNE.track_sigma) })); + keep.push(f); } + } else { + f.sample = Some((sam, t.own_tick, pos)); + keep.push(f); } } self.strikes.flown = keep; @@ -534,39 +576,217 @@ impl Sim { } } - /// Strike missiles on their way: drawn, heard, and landed when their time is up. + /// Strike missiles on their way: moved a swept step at a time, drawn and heard where they really are, and + /// landed or broken on what the world says is in the way (#106). fn fly_strikes(&mut self, ground: &mut Ground, player: Option, out: &mut Out) { let now = self.tick; let spec = DroneKind::Kalibr.spec(); let flights = std::mem::take(&mut self.strikes.flights); let mut rest = Vec::with_capacity(flights.len()); - for f in flights { - if now >= f.arrive { - self.land_strike(f, ground, player, out); + for mut f in flights { + // The terminal point: where it lands, at the land there as known now (a ridge that changed since the + // launch is flown over or into as it stands). With no land told of, the height it left at. + let below = ground.at(DVec3::new(f.land.x, 0.0, f.land.y), now); + let end = DVec3::new(f.land.x, below.unwrap_or(f.from.y), f.land.y); + let t = ((now - f.start) as f64 / (f.arrive - f.start).max(1) as f64).clamp(0.0, 1.0); + let age = now.saturating_sub(f.start); + let arc = |t: f64| TUNE.height * (std::f64::consts::PI * t).sin().max(0.0).sqrt(); + let planned = if f.carrier.is_some() && age < EJECT_TICKS { + // The unlit gas throw: it rises and slows, no motor yet (#129). + f.from + DVec3::Y * eject_rise(age) + } else if f.carrier.is_some() { + // From where the ejection reached, onto the cruise arc: the plan is continuous + // at ignition, and the flight time counted at the order includes the ejection. + let top = f.from + DVec3::Y * eject_rise(EJECT_TICKS); + let t = ((age - EJECT_TICKS) as f64 / (f.arrive - f.start).saturating_sub(EJECT_TICKS).max(1) as f64).clamp(0.0, 1.0); + top.lerp(end, t) + DVec3::Y * arc(t) + } else { + f.from.lerp(end, t) + DVec3::Y * arc(t) + }; + let from = f.pos; + // Time spent waiting for world data must not accumulate into a faster step. + let delta = planned - from; + let speed = if f.carrier.is_some() && age >= EJECT_TICKS { + TUNE.speed * (0.2 + 0.8 * ((age - EJECT_TICKS) as f64 / 20.0).clamp(0.0, 1.0)) + } else { TUNE.speed }; + let want = from + delta.clamp_length_max(speed); + // A missile that never reaches its point (the world never tells of the way) ends somewhere: it does + // not stay in the air for ever. + if now >= f.arrive + 600 { + self.crash_strike(f, from, ground, player, out); continue; } - let t = (now - f.start) as f64 / (f.arrive - f.start).max(1) as f64; - let end = DVec3::new(f.aim.x, f.from.y, f.aim.y); - let pos = f.from.lerp(end, t) + DVec3::Y * (TUNE.height * (std::f64::consts::PI * t).sin().max(0.0).sqrt()); - let dir = (end - f.from).normalize_or(DVec3::NEG_Z) + DVec3::Y * 0.001; + let vel = want - from; + if vel.length() > 1e-6 { + ground.ahead(from, vel, 20.0, now); + } + let sweep = ground.trace(f.key(), from, want, false, now); + let mut pos = want; + let mut permitted = true; + let contact = match sweep { + world::Sweep::Hit { point, .. } => Some(point), + world::Sweep::Wait { t } => { + permitted = false; + pos = from.lerp(want, (t - 1e-6).max(0.0)); + None + } + world::Sweep::Unknown { .. } => { + // Unread is not air (#61): the flight holds at the edge of what is read, and only a chunk + // the game doesn't have (not one still loading) is passed, high over the land. + let low = ground.at(from, now).is_some_and(|g| from.y < g + 12.0); + if let Some(t) = ground.unread(&sweep, &mut f.hold, low) { + permitted = false; + pos = from.lerp(want, t); + } + None + } + world::Sweep::Clear => { + ground.unread(&sweep, &mut f.hold, false); + None + }, + }; + if let Some(point) = contact { + // A surface exactly at the intended endpoint is the landing, not an intervening wall. + if point.distance(end) < 1e-6 { + self.land_strike(f, point, ground, player, out); + } else { + self.crash_strike(f, point, ground, player, out); + } + continue; + } + if permitted && pos.distance(end) < 1e-6 { + self.land_strike(f, pos, ground, player, out); + continue; + } + // The land's height says it met the ground where the blocks were not read: the same contact. + let here = ground.at(pos, now); + if permitted && ground.ground_hit(here, pos, 0.2, &sweep, now) { + self.crash_strike(f, pos, ground, player, out); + continue; + } + let dir = (pos - from).normalize_or(end - f.from).normalize_or(DVec3::NEG_Z); let rot = look(dir, 0.0); let id = 0x7500_0000 + f.id * PARTS; - out.model(id, spec.model, pos, rot, spec.scale, false); + if f.carrier.is_some() { + round_visual(out, id, pos, rot, age >= EJECT_TICKS); + } else { + out.model(id, spec.model, pos, rot, spec.scale, false); + } let flicker = 0.7 + 0.3 * self.vis.f(); - if let Engine::Jet(up, back, size) = spec.engine { + if f.carrier.is_none() && let Engine::Jet(up, back, size) = spec.engine { out.model(id + 1, "airdef:geran3_flame", pos + rot * DVec3::new(0.0, up * spec.scale, back * spec.scale), rot, spec.scale * size * flicker, true); } - if let Some((event, pitch)) = DroneKind::Kalibr.engine_voice() { - out.engine(id, event, pos, dir.normalize() * TUNE.speed, 0.8, pitch, false); + if f.carrier.is_some() { + if age == EJECT_TICKS { out.sound(super::catalog::sound::LAUNCH_S400, pos, 1.0, 1.0); } + if age >= EJECT_TICKS { + let (event, gain, pitch) = MissileKind::M48n6.engine_voice(); + out.engine(id, event, pos, pos - from, gain, pitch, false); + } + } else if let Some((event, pitch)) = DroneKind::Kalibr.engine_voice() { + out.engine(id, event, pos, pos - from, 0.8, pitch, false); } + f.pos = pos; rest.push(f); } rest.append(&mut self.strikes.flights); self.strikes.flights = rest; + // A site held only for something still in the air goes when that is gone (#108): no old bases pile up. + let refs = self.site_refs(); + self.sites.prune(&refs); } - /// Fires a strike missile at known site `id` from `from`. Returns the line for the player. - pub fn order_strike(&mut self, id: u32, from: DVec3, out: &mut Out) -> Result { + /// The sites something still in the air came from or is flying to (#108): a site retired by a new wave is kept + /// while these exist, and let go after. + pub(super) fn site_refs(&mut self) -> Vec { + let mut v: Vec = self.strikes.flights.iter().map(|f| f.site).collect(); + v.extend(self.strikes.pending.iter().map(|p| p.site)); + let alive: Vec = self.drones.iter().filter(|d| d.alive()).map(|d| d.id).collect(); + v.extend(self.strikes.scouted(&alive)); + v.extend(self.sites.flying_sites(&alive)); + v.sort_unstable(); + v.dedup(); + v + } + + /// Reserve a physical carrier and its cell; ammunition is spent only on ejection. + pub fn order_strike(&mut self, id: u32, _from: DVec3, _out: &mut Out) -> Result { + let k = self.strikes.get(id).ok_or_else(|| format!("ПУ №{id} не найдена"))?; + let est = k.est.filter(|e| e.fix).ok_or("Место ПУ неизвестно: РЛС должна увидеть сам пуск. /airdef sites — состояние разведки")?; + if matches!(k.state, SiteState::Destroyed(_)) { return Err("ПУ уже уничтожена".into()); } + if self.strikes.flights.iter().any(|f| f.site == id) || self.strikes.pending.iter().any(|p| p.site == id) { + return Err("По этой ПУ ракета уже готовится или в полёте".into()); + } + if !self.sites.get(id).is_some_and(|s| s.alive) { return Err("ПУ уже нет на карте".into()); } + let (i, _) = self.sams.iter().enumerate().filter_map(|(i,s)| { + let dist = (est.pos - DVec2::new(s.pos.x, s.pos.z)).length(); + (matches!(catalog::unit(s.kind).ground_strike(), catalog::GroundStrike::Rm48uGame) + && s.alive() && !s.moving() && s.reloading == 0 && s.cooldown == 0 + && s.part(Part::Launcher) > 0.0 && s.ground_rounds > u32::from(s.ground_return) && s.preparing.is_none() && dist <= TUNE.range) + .then_some((i, dist)) + }).min_by(|(i,a), (j,b)| self.sams[*j].ground_rounds.cmp(&self.sams[*i].ground_rounds) + .then(a.total_cmp(b)).then(self.sams[*i].id.cmp(&self.sams[*j].id))) + .ok_or("Нет готового С-400 с загруженной RM48U в игровой дальности; подготовьте боезапас у склада")?; + let sam = &mut self.sams[i]; + sam.sync_tube_cells(); + let cell = sam.next_tube_cell(); + sam.preparing = Some(launch::Prepare { target: id, cell, start: self.tick, ground: true }); + sam.cap_cell = cell as u8; + sam.cap_open = 0.0; + self.strikes.pending.push(PendingStrike { carrier: sam.id, site: id, aim: est.pos }); + Ok(format!("С-400 №{} готовит RM48U по ПУ №{id}", i + 1)) + } + + pub(super) fn prepare_ground_strikes(&mut self, out: &mut Out) { + let pending = std::mem::take(&mut self.strikes.pending); + for p in pending { + let Some(i) = self.sams.iter().position(|s| s.id == p.carrier) else { continue }; + let valid_site = self.sites.get(p.site).is_some_and(|s| s.alive) + && self.strikes.get(p.site).is_some_and(|k| !matches!(k.state, SiteState::Destroyed(_))); + let sam = &self.sams[i]; + let valid = valid_site && sam.preparing.is_some_and(|q| q.ground && q.target == p.site) + && sam.alive() && !sam.moving() && sam.reloading == 0 && sam.cooldown == 0 + && sam.part(Part::Launcher) > 0.0 && sam.ground_rounds > u32::from(sam.ground_return); + if !valid { + out.messages.push(format!("Подготовка RM48U по ПУ №{} отменена: изменилось состояние носителя или цели", p.site)); + if self.sams[i].preparing.is_some_and(|q| q.ground && q.target == p.site) { self.sams[i].cancel_prepare(); } + continue; + } + let prep = self.sams[i].preparing.unwrap(); + self.sams[i].cap_open = (self.tick.saturating_sub(prep.start) as f32 / launch::OPEN_TICKS as f32).clamp(0.0, 1.0); + if self.tick.saturating_sub(prep.start) < launch::OPEN_TICKS { + self.strikes.pending.push(p); + continue; + } + let site = self.sites.get(p.site).unwrap(); + let snap = Target { pos: DVec2::new(site.pos.x, site.pos.z), stock: site.stock, offmap: site.offmap, alive: site.alive }; + let sam = &mut self.sams[i]; + let (start, _) = sam.muzzle(prep.cell); + sam.eject_cell(prep.cell); + sam.ground_rounds -= 1; + sam.cooldown = 20; + sam.launched = 12; + sam.fired += 1; + let dist = (p.aim - DVec2::new(sam.pos.x, sam.pos.z)).length(); + // The launch betrays the carrier: the enemy saw it fire, no more than that. + self.intel.observe(&self.sams[i], self.tick, ew::Seen::Launch); + self.strikes.fired += 1; + self.strikes.next += 1; + // The counted flight includes the unlit ejection: the missile is honest about + // arriving a second later (#129). + let ticks = (dist / TUNE.speed).ceil().max(20.0) as u64 + EJECT_TICKS; + let (gx, gz) = (gauss(self.seed, self.strikes.next as u64, p.site as u64, 7), gauss(self.seed, self.strikes.next as u64, p.site as u64, 8)); + self.strikes.flights.push(Flight { id: self.strikes.next, site: p.site, carrier: Some(p.carrier), from: start, pos: start, aim: p.aim, + land: p.aim + DVec2::new(gx, gz) * TUNE.cep, start: self.tick, arrive: self.tick + ticks, snap, hold: 0 }); + if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == p.site) { k.aimed = true; } + self.record_strike_launch(self.strikes.next, p.carrier, start); + out.sound(super::catalog::sound::LAUNCH_S400_EJECT, start, 1.0, 1.0); + out.particles("minecraft:cloud", start, 0.6, DVec3::Y * 0.05, 0.05, 30); + } + } + + /// Synthetic launcher used only by flight/impact regression fixtures; absent from production builds. + #[cfg(any(test, feature = "test-fixtures"))] + pub fn order_test_strike(&mut self, id: u32, from: DVec3, out: &mut Out) -> Result { let k = self.strikes.get(id).ok_or_else(|| format!("ПУ №{id} не найдена: /airdef sites покажет известные"))?; let est = k.est.filter(|e| e.fix).ok_or_else(|| { format!("ПУ №{id}: места не знаем, только азимут. Нужно больше пусков на виду или вторая РЛС в стороне от первой") @@ -582,14 +802,33 @@ impl Sim { } let dist = (est.pos - DVec2::new(from.x, from.z)).length(); if dist > TUNE.range { - return Err(format!("ПУ №{id} дальше дальности удара ({})", crate::panel::distance(TUNE.range))); + return Err(format!("ПУ №{id} дальше дальности удара ({})", super::distance(TUNE.range))); } + // The target as it is now: the missile is judged against this whatever becomes of the site meanwhile (#108). + let site = self.sites.get(id).ok_or_else(|| format!("ПУ №{id} уже нет на карте"))?; + let snap = Target { pos: DVec2::new(site.pos.x, site.pos.z), stock: site.stock, offmap: site.offmap, alive: site.alive }; self.strikes.stock -= 1; self.strikes.fired += 1; self.strikes.next += 1; - let ticks = (dist / TUNE.speed).ceil().max(20.0) as u64; + // The counted flight includes the unlit ejection (#129). + let ticks = (dist / TUNE.speed).ceil().max(20.0) as u64 + EJECT_TICKS; let start = from + DVec3::Y * 1.5; - self.strikes.flights.push(Flight { id: self.strikes.next, site: id, from: start, aim: est.pos, start: self.tick, arrive: self.tick + ticks }); + // Its own scatter at the aim, rolled once here: the flight and its outcome name one point. + let (gx, gz) = (gauss(self.seed, self.strikes.next as u64, id as u64, 7), gauss(self.seed, self.strikes.next as u64, id as u64, 8)); + let land = est.pos + DVec2::new(gx, gz) * TUNE.cep; + self.strikes.flights.push(Flight { + id: self.strikes.next, + site: id, + carrier: None, + from: start, + pos: start, + aim: est.pos, + land, + start: self.tick, + arrive: self.tick + ticks, + snap, + hold: 0, + }); if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == id) { k.aimed = true; } @@ -600,42 +839,29 @@ impl Sim { "Удар по ПУ №{id}: ракета пошла в точку ±{:.0} м, лёт {} с, дальше {}. Осталось ударных ракет: {}", est.radius, ticks / 20, - crate::panel::distance(dist), + super::distance(dist), self.strikes.stock )) } - /// A strike arrives. - fn land_strike(&mut self, f: Flight, ground: &mut Ground, player: Option, out: &mut Out) { + /// A strike arrives at `at` (the contact point: the ground under its landing point, or the face it met). + fn land_strike(&mut self, f: Flight, at: DVec3, ground: &mut Ground, player: Option, out: &mut Out) { let now = self.tick; if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == f.site) { k.aimed = false; } - let Some(site) = self.sites.get(f.site).cloned() else { - out.messages.push(format!("Удар по ПУ №{}: площадка уже свёрнута, ракета ушла в пустоту", f.site)); - return; + // The site as it stands now; if it has been taken off the map since the launch (a new wave's housekeeping, + // a clear), the shot was still fired at that place: it has its outcome by its coordinates (#108). + let (truth, offmap, alive, stock) = match self.sites.get(f.site) { + Some(s) => (DVec2::new(s.pos.x, s.pos.z), s.offmap, s.alive, s.stock), + None => (f.snap.pos, f.snap.offmap, f.snap.alive, f.snap.stock), }; - let cover = self.cover_of(&site); - if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == f.site) { - k.cover = Some(cover); - } - let spot = |x: f64, z: f64, ground: &mut Ground, fallback: f64| DVec3::new(x, ground.at(DVec3::new(x, 0.0, z), now).unwrap_or(fallback), z); - // The enemy's cover. - if dice(self.seed, f.id as u64, f.site as u64, 201) < TUNE.cover_p[cover as usize] { - self.strikes.shot_down += 1; - let at = spot(f.aim.x, f.aim.y, ground, site.pos.y) + DVec3::Y * 40.0; - self.blast_at(at, 0, true, false, ground, player, out); - self.log(at, record::EventKind::Strike { site: f.site, result: record::StrikeResult::Intercepted }); - out.messages.push(format!("Удар по ПУ №{}: ракета сбита ПВО противника у площадки, цель цела", f.site)); - return; - } - // Where it lands: the aim and the missile's scatter; how far from the site decides. - let (gx, gz) = (gauss(self.seed, f.id as u64, f.site as u64, 7), gauss(self.seed, f.id as u64, f.site as u64, 8)); - let land = DVec2::new(f.aim.x + gx * TUNE.cep, f.aim.y + gz * TUNE.cep); - let miss = (land - DVec2::new(site.pos.x, site.pos.z)).length(); - let result = if site.offmap && miss <= TUNE.hit_r { + // No seed-only defender: interceptions must come from physical simulated combat (#118). + // Where it lands was fixed at the launch; how far from the site decides. + let miss = (f.land - truth).length(); + let result = if offmap && miss <= TUNE.hit_r { record::StrikeResult::Halted - } else if !site.alive { + } else if !alive { record::StrikeResult::Missed } else if miss <= TUNE.hit_r { record::StrikeResult::Destroyed @@ -645,25 +871,24 @@ impl Sim { record::StrikeResult::Missed }; use record::StrikeResult as R; - if site.offmap { + if offmap { // Nothing stands there, nothing is seen: the launches stop or they do not. if result == R::Halted { let n = self.halt_site(f.site); self.strikes.destroyed += 1; self.strikes.cancelled.push((f.site, n)); self.set_state(f.site, SiteState::Destroyed(now)); - self.log(site.pos, record::EventKind::Strike { site: f.site, result }); + self.emit_combat_event(at, record::EventKind::Strike { site: f.site, result }); out.messages.push(format!("Удар по ПУ №{}: за пределами загруженной карты, результат не виден; пуски оттуда прекратились (отменено {n})", f.site)); } else { self.strikes.missed += 1; + self.emit_combat_event(at, record::EventKind::Strike { site: f.site, result: R::Missed }); out.messages.push(format!("Удар по ПУ №{}: за пределами загруженной карты; пуски идут, ракета потеряна", f.site)); } return; } - let at = spot(land.x, land.y, ground, site.pos.y); match result { R::Destroyed => { - let stock = site.stock; let n = self.destroy_site(f.site); self.strikes.destroyed += 1; self.strikes.cancelled.push((f.site, n)); @@ -677,7 +902,7 @@ impl Sim { self.later.push((when, at + off, if loaded { 2 } else { 0 }, true)); } self.strikes.wrecks.push((at, now + TUNE.burn)); - self.log(at, record::EventKind::Strike { site: f.site, result }); + self.emit_combat_event(at, record::EventKind::Strike { site: f.site, result }); out.messages.push(format!( "Удар по ПУ №{}: площадка уничтожена, запас сгорел ({stock}); отменено запусков: {n}", f.site @@ -690,7 +915,7 @@ impl Sim { self.strikes.stunned += 1; self.set_state(f.site, SiteState::Stunned(now + TUNE.suppress_ticks)); self.blast_at(at, 4, false, false, ground, player, out); - self.log(at, record::EventKind::Strike { site: f.site, result }); + self.emit_combat_event(at, record::EventKind::Strike { site: f.site, result }); out.messages.push(format!( "Удар по ПУ №{}: накрыла рядом ({miss:.0} м), площадка подавлена на {} с, но цела", f.site, @@ -700,12 +925,28 @@ impl Sim { _ => { self.strikes.missed += 1; self.blast_at(at, 4, false, false, ground, player, out); - self.log(at, record::EventKind::Strike { site: f.site, result: R::Missed }); + self.emit_combat_event(at, record::EventKind::Strike { site: f.site, result: R::Missed }); out.messages.push(format!("Удар по ПУ №{}: промах на {miss:.0} м, площадка цела, пуски идут", f.site)); } } } + /// A strike missile met the land or a building short of its aim: it goes off there, and the site is untouched + /// because nothing was aimed at. One result for the statistics and the record, before impact (#106). + fn crash_strike(&mut self, f: Flight, at: DVec3, ground: &mut Ground, player: Option, out: &mut Out) { + if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == f.site) { + k.aimed = false; + } + self.strikes.missed += 1; + self.blast_at(at, 4, false, false, ground, player, out); + self.emit_combat_event(at, record::EventKind::Strike { site: f.site, result: record::StrikeResult::Blocked }); + out.messages.push(format!( + "Удар по ПУ №{}: ракета разбилась о рельеф или строение в {:.0} м от цели, площадка цела", + f.site, + (at.x - f.snap.pos.x).hypot(at.z - f.snap.pos.y) + )); + } + fn set_state(&mut self, site: u32, state: SiteState) { if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == site) { k.state = state; @@ -734,16 +975,9 @@ impl Sim { s }).collect(); if v.is_empty() { - v.push("Известных ПУ нет: их находят РЛС по пускам и трекам вылетевших целей".into()); + v.push("Известных ПУ нет: РЛС должна заметить пуск или вылетевшую цель; для удара нужно определить место пуска".into()); } - v.push(format!( - "Ударных ракет: {}/{}{} · в полёте {} · дальность {}", - self.strikes.stock, - TUNE.stock_max, - if self.strikes.stock < TUNE.stock_max { format!(" (следующая через {} с)", self.strikes.seconds_to_next()) } else { String::new() }, - self.strikes.flights.len(), - crate::panel::distance(TUNE.range) - )); + v.push("Удар: /airdef strike <вражеская ПУ>; RM48U: /airdef ground-stock <склад> <число>, затем ground-load <С-400>".into()); v } } @@ -798,37 +1032,11 @@ mod tests { assert!((165..=196).contains(&within), "{within} of 200 inside the circle"); } - #[test] - fn a_track_with_a_time_of_flight_range_places_the_site_and_more_of_them_narrow_it() { - let truth = DVec2::new(100.0, 3000.0); - let ranged = |n: u64, seed: u64| -> Vec { - (0..n) - .map(|k| { - let at = DVec2::new(-40.0 + gauss(seed, k, 9, 1) * 30.0, 2200.0 + gauss(seed, k, 9, 2) * 30.0); - let away = truth - at; - let a = away.y.atan2(away.x) + gauss(seed, k, 1, 5) * TUNE.track_sigma; - let sr = away.length() * TUNE.range_sigma.0 + 20.0; - Obs { range: Some((away.length() + gauss(seed, k, 1, 6) * sr, sr)), ..Obs::ray(at, DVec2::new(a.cos(), a.sin()), TUNE.track_sigma) } - }) - .collect() - }; - let (mut within, mut shrink) = (0, 0); - for seed in 0..100u64 { - let (one, many) = (solve(&ranged(1, seed)).unwrap(), solve(&ranged(16, seed)).unwrap()); - within += usize::from((many.pos - truth).length() <= many.radius); - shrink += usize::from(many.radius < one.radius * 0.5); - } - assert!(within >= 80, "{within} of 100 inside the circle"); - assert_eq!(shrink, 100); - // Even one is a (rough) place for a site 800 blocks away. - assert!(solve(&ranged(1, 1)).unwrap().fix); - } - /// A ramp 300 blocks south of the origin with `n` shaheds queued 40 ticks apart, and a radar `radar` blocks from it /// (None: no radar). fn scene(seed: u64, n: u32, radar: Option) -> (Sim, Ground, Out, u32) { let mut sim = Sim::new(seed); - sim.strikes.cover_override = Some(0); + if let Some(at) = radar { let id = sim.id(); sim.sams.push(Sam::new(id, SamKind::Patriot, at, 0.0)); @@ -863,6 +1071,56 @@ mod tests { } } + #[test] + fn late_track_uses_only_measured_history_and_does_not_accumulate_correlated_fixes() { + fn observed(hidden_x: f64, hidden_launch_tick: u64) -> (Sim, u32, u32) { + let (mut sim, mut ground, mut out, id) = scene(9, 0, Some(DVec3::ZERO)); + let site = sim.sites.list.iter_mut().find(|site| site.id == id).unwrap(); + site.pos.x = hidden_x; + site.offmap = true; + let d = sim.make_drone(DroneKind::Shahed, DVec3::new(0.0, 100.0, 1000.0), -DVec3::Z, DVec3::ZERO); + let drone = d.id; + sim.drones.push(d); + sim.tick = hidden_launch_tick; + sim.scout_launch(drone, id, DroneKind::Shahed, DVec3::new(hidden_x, 70.0, 700.0)); + let track = sight::Track { id: drone, kind: DroneKind::Shahed, pos: DVec3::new(0.0, 100.0, 1000.0), vel: -DVec3::Z * 5.0, tick: 100, own_tick: 100, ttl: 100, hits: 3, locked: false }; + sim.sams[0].tracks.push(track); + sim.tick = 100; + sim.step_strikes(&mut ground, None, &mut out); + let t = &mut sim.sams[0].tracks[0]; + t.pos.z = 960.0; + t.tick = 108; + t.own_tick = 108; + sim.tick = 108; + sim.step_strikes(&mut ground, None, &mut out); + (sim, id, drone) + } + let (mut a, id, drone) = observed(0.0, 1); + let (b, _, _) = observed(1500.0, 40); + let ea = a.strikes.get(id).unwrap().est.unwrap(); + let eb = b.strikes.get(id).unwrap().est.unwrap(); + assert!(!ea.fix && !eb.fix); + assert_eq!((ea.origin, ea.pos, ea.bearing, ea.bearing_err), (eb.origin, eb.pos, eb.bearing, eb.bearing_err)); + assert_eq!(ea.bearing_err, std::f64::consts::PI); + + // Repeated sensors/observations of the same course cannot manufacture confidence. + for _ in 0..20 { + a.strikes.flown.push(Flown { drone, site: id, class: SiteClass::Ramp, at: 108, sample: None }); + } + a.tick = 112; + a.step_strikes(&mut Ground::default(), None, &mut Out::default()); + let k = a.strikes.get(id).unwrap(); + assert_eq!(k.obs.len(), 1); + assert_eq!(k.est.unwrap().bearing_err, ea.bearing_err); + + // A real launch measurement supersedes the tentative course bearing. + a.add_obs(id, SiteClass::Ramp, Obs::point(DVec2::new(20.0, 700.0), 20.0)); + let k = a.strikes.get(id).unwrap(); + assert_eq!(k.obs.len(), 1); + assert!(k.est.unwrap().fix); + assert_eq!(k.est.unwrap().pos, DVec2::new(20.0, 700.0)); + } + #[test] fn launches_seen_by_a_radar_in_reach_locate_the_site_and_the_circle_shrinks() { let (mut sim, mut ground, mut out, id) = scene(11, 12, Some(DVec3::new(0.0, 64.0, 200.0))); @@ -898,10 +1156,10 @@ mod tests { let before = sim.launched; assert!(before >= 3 && before < 14, "{before}"); let player = DVec3::new(0.0, 64.0, 0.0); - let said = sim.order_strike(id, player, &mut out).unwrap(); + let said = sim.order_test_strike(id, player, &mut out).unwrap(); assert!(said.contains("ПУ"), "{said}"); assert_eq!(sim.strikes.stock, TUNE.stock - 1); - assert!(sim.order_strike(id, player, &mut out).is_err(), "a second missile at the same site while one flies"); + assert!(sim.order_test_strike(id, player, &mut out).is_err(), "a second missile at the same site while one flies"); out.messages.clear(); run(&mut sim, &mut ground, &mut out, 400); let site = sim.sites.get(id).unwrap(); @@ -912,7 +1170,7 @@ mod tests { assert!(sim.strikes.wrecks.len() == 1 && sim.fires.list.len() == 1, "a wreck and a fire"); assert!(!sim.blasts.is_empty() || sim.tick > 0); assert!(matches!(sim.strikes.get(id).unwrap().state, SiteState::Destroyed(_))); - assert!(sim.order_strike(id, player, &mut out).is_err(), "destroyed already"); + assert!(sim.order_test_strike(id, player, &mut out).is_err(), "destroyed already"); // The result is on the record for replays and stats. assert_eq!((sim.strikes.fired, sim.strikes.destroyed), (1, 1)); } @@ -931,7 +1189,7 @@ mod tests { k.est = Some(e); sim.strikes.known[0].obs.clear(); let queued = sim.queue.len(); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); out.messages.clear(); run(&mut sim, &mut ground, &mut out, 300); assert!(out.messages.iter().any(|m| m.contains("промах")), "{:?}", out.messages); @@ -949,7 +1207,7 @@ mod tests { let mut e = k.est.unwrap(); e.pos = DVec2::new(0.0, 300.0 + 62.0); k.est = Some(e); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); let queued = sim.queue.len(); run(&mut sim, &mut ground, &mut out, 300); if sim.strikes.stunned == 1 { @@ -975,14 +1233,13 @@ mod tests { launches: 1, last: 0, state: SiteState::Active, - cover: None, aimed: false, }); assert!(!sim.strikes.known[0].est.unwrap().fix); - let e = sim.order_strike(id, DVec3::ZERO, &mut out).unwrap_err(); + let e = sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap_err(); assert!(e.contains("азимут"), "{e}"); assert_eq!(sim.strikes.stock, TUNE.stock, "no missile spent"); - assert!(sim.order_strike(99, DVec3::ZERO, &mut out).is_err()); + assert!(sim.order_test_strike(99, DVec3::ZERO, &mut out).is_err()); } #[test] @@ -990,9 +1247,9 @@ mod tests { let (mut sim, mut ground, mut out, id) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); run(&mut sim, &mut ground, &mut out, 120); // Too far. - assert!(sim.order_strike(id, DVec3::new(0.0, 64.0, 300.0 - TUNE.range - 100.0), &mut out).is_err()); + assert!(sim.order_test_strike(id, DVec3::new(0.0, 64.0, 300.0 - TUNE.range - 100.0), &mut out).is_err()); sim.strikes.stock = 0; - let e = sim.order_strike(id, DVec3::ZERO, &mut out).unwrap_err(); + let e = sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap_err(); assert!(e.contains("нет"), "{e}"); sim.strikes.progress = TUNE.resupply - 1; run(&mut sim, &mut ground, &mut out, 2); @@ -1000,26 +1257,178 @@ mod tests { } #[test] - fn enemy_cover_shoots_some_strikes_down_and_none_with_no_cover() { - let mut down = [0u32; 3]; - for cover in 0..3u8 { - for seed in 1..=30u64 { - let (mut sim, mut ground, mut out, id) = scene(seed, 14, Some(DVec3::new(0.0, 64.0, 200.0))); - sim.strikes.cover_override = Some(cover); - run(&mut sim, &mut ground, &mut out, 120); - sim.order_strike(id, DVec3::ZERO, &mut out).unwrap(); - run(&mut sim, &mut ground, &mut out, 200); - down[cover as usize] += sim.strikes.shot_down; - } + fn no_seed_only_enemy_defence_can_intercept_a_flight() { + for seed in 1..=30u64 { + let (mut sim, mut ground, mut out, id) = scene(seed, 14, Some(DVec3::new(0.0, 64.0, 200.0))); + run(&mut sim, &mut ground, &mut out, 120); + sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap(); + run(&mut sim, &mut ground, &mut out, 200); + assert_eq!(sim.strikes.shot_down, 0, "seed {seed}"); + assert!(!out.messages.iter().any(|m| m.contains("сбита ПВО противника"))); + assert!(sim.rec.iter().flat_map(|r| &r.events).chain(sim.replays.iter().flat_map(|r| &r.events)).all(|e| !matches!(e.kind, record::EventKind::Strike { result: record::StrikeResult::Intercepted, .. }))); } - assert_eq!(down[0], 0); - assert!(down[1] > 3 && down[1] < 20 && down[2] > down[1], "{down:?}"); + } + + #[test] + fn production_orders_never_launch_from_player_or_spend_generic_stock() { + let (mut sim, mut ground, mut out, id) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); + run(&mut sim, &mut ground, &mut out, 120); + let stock = sim.strikes.stock; + for player in [DVec3::ZERO, DVec3::new(4000.0, 64.0, 300.0)] { + let e = sim.order_strike(id, player, &mut out).unwrap_err(); + assert!(e.contains("RM48U"), "{e}"); + assert!(sim.strikes.flights.is_empty()); + assert_eq!(sim.strikes.stock, stock); + assert_eq!(sim.strikes.fired, 0); + } + sim.sams.clear(); + assert!(sim.order_strike(id, DVec3::ZERO, &mut out).unwrap_err().contains("Нет готового")); + } + + #[test] + fn typed_preparation_and_atomic_carrier_selection() { + let (mut sim, mut ground, mut out, site) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); + run(&mut sim, &mut ground, &mut out, 120); + let depot_id = sim.id(); + sim.sams.push(Sam::new(depot_id, SamKind::Depot, DVec3::ZERO, 0.0)); + let depot = sim.sams.len() - 1; + let a_id = sim.id(); + sim.sams.push(Sam::new(a_id, SamKind::S400, DVec3::new(5.0, 0.0, 0.0), 0.0)); + let a = sim.sams.len() - 1; + let b_id = sim.id(); + sim.sams.push(Sam::new(b_id, SamKind::S400, DVec3::new(10.0, 0.0, 0.0), 0.0)); + let b = sim.sams.len() - 1; + assert_eq!((sim.sams[a].missiles, sim.sams[a].ground_rounds, sim.sams[depot].ground_stock), (4, 0, 0)); + assert!(sim.load_ground_round(a).is_err()); + sim.sams[depot].ground_stock = 4; // Explicit scenario supply. + sim.load_ground_round(a).unwrap(); + sim.load_ground_round(b).unwrap(); + sim.load_ground_round(b).unwrap(); + assert_eq!((sim.sams[a].missiles, sim.sams[a].ground_rounds, sim.sams[b].missiles, sim.sams[b].ground_rounds), (3, 1, 2, 2)); + assert_eq!((sim.sams[depot].ground_stock, sim.sams[depot].air_48n6_stock), (1, 3)); + sim.unload_ground_round(a).unwrap(); + assert_eq!((sim.sams[a].missiles, sim.sams[a].ground_rounds, sim.sams[depot].ground_stock, sim.sams[depot].air_48n6_stock), (4, 0, 2, 2)); + sim.load_ground_round(a).unwrap(); + sim.sams[a].cooldown = 0; + sim.sams[b].cooldown = 0; + let cell = sim.sams[b].next_tube_cell(); + let muzzle = sim.sams[b].muzzle(cell).0; + let old_stock = sim.strikes.stock; + sim.order_strike(site, DVec3::new(99999.0, 0.0, 99999.0), &mut out).unwrap(); + assert_eq!((sim.sams[a].ground_rounds, sim.sams[b].ground_rounds, sim.strikes.stock), (1, 2, old_stock)); + assert!(sim.order_strike(site, DVec3::ZERO, &mut out).is_err()); + run(&mut sim, &mut ground, &mut out, launch::OPEN_TICKS as u32 + 1); + assert_eq!((sim.sams[a].ground_rounds, sim.sams[b].ground_rounds, sim.strikes.stock), (1, 1, old_stock)); + let f = sim.strikes.flights.last().unwrap(); + assert_eq!((f.carrier, f.from), (Some(b_id), muzzle)); + assert!(sim.rec.as_ref().unwrap().tracks.iter().any(|t| t.what == record::What::StrikeCarrier(b_id))); + assert!(sim.order_strike(site, DVec3::ZERO, &mut out).is_err()); + assert_eq!(sim.sams[b].ground_rounds, 1); + sim.repair(); + assert!(sim.sams[b].missiles + sim.sams[b].ground_rounds <= 4); + } + + #[test] + fn ground_preparation_cancels_without_spending_when_carrier_or_site_changes() { + for reason in 0..6 { + let mut sim = Sim::new(11); + let site = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); + let mut sam = Sam::new(123, SamKind::S400, DVec3::ZERO, 0.0); + sam.missiles = 2; sam.ground_rounds = 2; sam.cooldown = 0; + sim.sams.push(sam); + let mut out = Out::default(); + sim.order_strike(site, DVec3::ZERO, &mut out).unwrap(); + match reason { + 0 => sim.sams[0].health = 0.0, + 1 => sim.sams[0].state = SamState::Moving, + 2 => sim.sams[0].reloading = 5, + 3 => sim.sams[0].cooldown = 5, + 4 => sim.sams[0].ground_rounds = 0, + _ => sim.sites.list.iter_mut().find(|s| s.id == site).unwrap().alive = false, + } + sim.tick += launch::OPEN_TICKS; + sim.prepare_ground_strikes(&mut out); + assert!(sim.strikes.pending.is_empty() && sim.strikes.flights.is_empty()); + assert_eq!(sim.strikes.fired, 0); + assert_eq!(sim.sams[0].ground_rounds, if reason == 4 { 0 } else { 2 }); + assert!(sim.sams[0].preparing.is_none()); + } + } + + #[test] + fn return_request_reserves_one_round_from_strikes() { + let mut sim = Sim::new(11); + let a = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); + let mut sam = Sam::new(123, SamKind::S400, DVec3::ZERO, 0.0); + sam.missiles = 3; sam.ground_rounds = 1; sam.ground_return = true; sam.cooldown = 0; + sim.sams.push(sam); + let mut out = Out::default(); + assert!(sim.order_strike(a, DVec3::ZERO, &mut out).is_err()); + sim.sams[0].ground_rounds = 2; + sim.sams[0].missiles = 2; + sim.order_strike(a, DVec3::ZERO, &mut out).unwrap(); + sim.tick += launch::OPEN_TICKS; + sim.prepare_ground_strikes(&mut out); + assert_eq!(sim.sams[0].ground_rounds, 1); + assert!(sim.sams[0].ground_return); + } + + #[test] + fn ground_orders_reserve_the_carrier_and_target_until_ejection() { + let mut sim = Sim::new(11); + let a = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); + let b = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 400.0), 0); + let mut sam = Sam::new(123, SamKind::S400, DVec3::ZERO, 0.0); + sam.missiles = 2; sam.ground_rounds = 2; sam.cooldown = 0; + sim.sams.push(sam); + let mut out = Out::default(); + sim.order_strike(a, DVec3::ZERO, &mut out).unwrap(); + assert!(sim.order_strike(a, DVec3::ZERO, &mut out).is_err()); + assert!(sim.order_strike(b, DVec3::ZERO, &mut out).is_err()); + assert_eq!(sim.sams[0].ground_rounds, 2); + sim.tick += launch::OPEN_TICKS; + sim.prepare_ground_strikes(&mut out); + assert_eq!((sim.sams[0].ground_rounds, sim.strikes.fired, sim.strikes.flights.len()), (1, 1, 1)); + assert!(sim.strikes.pending.is_empty()); + } + + #[test] + fn fully_loaded_ground_cells_do_not_consume_air_reload_cargo() { + let mut sim = Sim::new(1); + sim.sams.push(Sam::new(1, SamKind::Depot, DVec3::ZERO, 0.0)); + sim.sams[0].ground_stock = 4; + sim.sams.push(Sam::new(2, SamKind::S400, DVec3::new(10.0, 0.0, 0.0), 0.0)); + for _ in 0..4 { sim.load_ground_round(1).unwrap(); } + let cargo = sim.sams[0].cargo; + let mut ground = Ground::default(); + let mut out = Out::default(); + run(&mut sim, &mut ground, &mut out, 30); + assert_eq!(sim.sams[0].cargo, cargo); + assert_eq!((sim.sams[1].missiles, sim.sams[1].ground_rounds), (0, 4)); + } + + #[test] + fn unavailable_carriers_cannot_spend_ground_rounds() { + let (mut sim, mut ground, mut out, site) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); + run(&mut sim, &mut ground, &mut out, 120); + let id = sim.id(); + let mut s = Sam::new(id, SamKind::S400, DVec3::ZERO, 0.0); + s.missiles = 2; s.ground_rounds = 2; s.cooldown = 0; + sim.sams.push(s); + let i = sim.sams.len() - 1; + sim.sams[i].health = 0.0; + assert!(sim.order_strike(site, DVec3::ZERO, &mut out).is_err()); + sim.sams[i].health = 1.0; sim.sams[i].reloading = 5; + assert!(sim.order_strike(site, DVec3::ZERO, &mut out).is_err()); + sim.sams[i].reloading = 0; sim.sams[i].state = SamState::Moving; + assert!(sim.order_strike(site, DVec3::ZERO, &mut out).is_err()); + assert_eq!(sim.sams[i].ground_rounds, 2); } #[test] fn an_off_map_site_is_found_by_bearing_and_a_strike_there_only_ends_its_launches() { let mut sim = Sim::new(5); - sim.strikes.cover_override = Some(0); + // Two radars apart watch the start of an off-map site's drones. for x in [-150.0, 150.0] { let id = sim.id(); @@ -1056,7 +1465,7 @@ mod tests { // ... and the shot aims at what is known: force a good fix to test the outcome. let k = sim.strikes.known.iter_mut().find(|k| k.site == id).unwrap(); k.est = Some(Estimate { pos: DVec2::new(0.0, 700.0), radius: 20.0, fix: true, bearing: 3.14, bearing_err: 0.01, origin: DVec2::ZERO }); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); out.messages.clear(); let wrecks = sim.fires.list.len(); run(&mut sim, &mut ground, &mut out, 500); @@ -1071,7 +1480,7 @@ mod tests { let (mut sim, mut ground, mut out, id) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); run(&mut sim, &mut ground, &mut out, 100); // A recording is running (the launched drones started it): the strike lands in it. - sim.order_strike(id, DVec3::ZERO, &mut out).unwrap(); + sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap(); run(&mut sim, &mut ground, &mut out, 200); let count = |r: &record::Recording| r.events.iter().filter(|e| matches!(e.kind, record::EventKind::Strike { .. })).count(); let n = sim.rec.iter().chain(sim.replays.iter()).map(count).sum::(); @@ -1094,7 +1503,7 @@ mod tests { let land = Land(|_, _| 64); let (mut ground, mut out) = (Ground::default(), Out::default()); let mut sim = Sim::new(21); - sim.strikes.cover_override = Some(0); + let order = RaidOrder { kinds: vec![(DroneKind::Shahed, 30)], approach: Approach::Sector, dist: 300.0, aim: Aim::Objects }; sim.order_raid(&order, &[DVec3::new(0.0, 70.0, 0.0)], &mut out); let mut n = 0; @@ -1115,7 +1524,7 @@ mod tests { sim.step(&mut ground, None, &mut out); if !struck && sim.strikes.get(site.id).is_some_and(|k| k.est.is_some_and(|e| e.fix && e.radius < 40.0)) && sim.sites.pending(site.id) > 3 { left_at_strike = sim.sites.pending(site.id); - sim.order_strike(site.id, DVec3::new(0.0, 70.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(site.id, DVec3::new(0.0, 70.0, 0.0), &mut out).unwrap(); struck = true; } } @@ -1126,4 +1535,289 @@ mod tests { assert!(sim.queue.is_empty() && sim.sites.pending(site.id) == 0); assert!(left_at_strike > 3); } + + // ---------------------------------------------------------------- #106: the strike flies through the world + + /// A wall across `at` (z), solid in the sections from the ground up, with `hole` (a band of block heights) left + /// open by its exact shapes: the narrow phase is what decides. + struct Wall { + at: i32, + hole: Option<(i32, i32)>, + } + impl Wall { + fn in_wall(&self, y: i32, z: i32) -> bool { + z == self.at && (64..200).contains(&y) + } + } + impl world::WorldSource for Wall { + fn solid(&self, _x: i32, y: i32, z: i32) -> Option { + Some(y < 64 || self.in_wall(y, z)) + } + fn boxes(&self, _x: i32, y: i32, z: i32) -> Vec<[f64; 6]> { + let open = self.hole.is_some_and(|(lo, hi)| (lo..hi).contains(&y)); + if self.in_wall(y, z) && !open { vec![[0.0, 0.0, 0.0, 1.0, 1.0, 1.0]] } else { Vec::new() } + } + } + + /// The game's answers of the tick, as `step` would get them: the land heights and the sections. + fn serve(ground: &mut Ground, src: &dyn world::WorldSource, now: u64) { + let cols = ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect(); + ground.answer(cols, now); + let secs = ground.world.take_wanted(now); + ground.world.serve(src, secs, now); + } + + /// Reads the sections along `a`..`b` ahead of the flight, as a real game has them loaded before the shot. + fn warm(ground: &mut Ground, src: &dyn world::WorldSource, a: DVec3, b: DVec3) { + for _ in 0..4 { + ground.world.sweep(a, b, false, 0); + let w = ground.world.take_wanted(0); + ground.world.serve(src, w, 0); + } + } + + /// A site at `pos` with `stock` launches still ahead of it and the radars' fix on it: no drone has to fly first. + fn fixed_site(sim: &mut Sim, pos: DVec3, stock: u32) -> u32 { + let id = sim.sites.add(LaunchSite { + id: 0, + side: 0, + sector: 0, + bearing: 0.0, + class: SiteClass::Ramp, + pos, + range: pos.z, + water: false, + stock, + planned: stock, + alive: true, + next_free: 0, + offmap: false, + }); + sim.strikes.known.push(Known { + site: id, + class: SiteClass::Ramp, + obs: Vec::new(), + est: Some(Estimate { pos: DVec2::new(pos.x, pos.z), radius: 5.0, fix: true, bearing: 0.0, bearing_err: 0.01, origin: DVec2::ZERO }), + launches: 1, + last: 0, + state: SiteState::Active, + aimed: false, + }); + for i in 0..stock { + let site = sim.sites.get(id).unwrap().clone(); + let d = sim.make_drone(DroneKind::Shahed, site.muzzle(i), -DVec3::Z, DVec3::new(0.0, 70.0, 0.0)); + sim.sites.job(d.id, id); + // Well after the strike: the site keeps its stock and its launches. + sim.queue.push((20_000 + u64::from(i), d)); + } + id + } + + fn strike_events(sim: &Sim) -> Vec { + sim.rec.iter().chain(sim.replays.iter()).flat_map(|r| r.events.iter().map(|e| e.kind)).filter(|k| matches!(k, record::EventKind::Strike { .. })).collect() + } + + // #106: a wall between the shot and the site catches the missile: it goes off at the contact, the site is untouched + // and keeps its launches, the stock is spent once and the result is counted once, before impact handling. + #[test] + fn a_counter_strike_breaks_on_a_wall_between_it_and_its_site() { + let mut sim = Sim::new(11); + // A broken flight is counted once and never becomes a fictitious interception. + + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 4); + let (mut ground, mut out) = (Ground::default(), Out::default()); + let wall = Wall { at: 140, hole: None }; + warm(&mut ground, &wall, DVec3::new(0.0, 65.5, 0.0), DVec3::new(0.0, 105.0, 300.0)); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + out.messages.clear(); + let mut crossed = false; + for _ in 0..300 { + serve(&mut ground, &wall, sim.tick); + sim.step(&mut ground, None, &mut out); + crossed |= sim.strikes.flights.iter().any(|f| f.pos.z > 144.0); + if sim.strikes.flights.is_empty() { + break; + } + } + assert!(!crossed, "the missile flew through the wall"); + assert!(sim.strikes.flights.is_empty(), "still flying"); + let site = sim.sites.get(id).expect("the site was taken away"); + assert!(site.alive && site.stock == 4, "the site was hit through the wall: {site:?}"); + assert_eq!(sim.sites.pending(id), 4, "its launches still stand"); + assert_eq!((sim.strikes.fired, sim.strikes.missed, sim.strikes.destroyed, sim.strikes.stunned, sim.strikes.shot_down), (1, 1, 0, 0, 0)); + // The blast is at the wall's face, not at the site 160 blocks on. + let at = sim.blasts.first().expect("no blast").at; + assert!((at.z - 140.0).abs() < 1.0 && at.x.abs() < 1.0, "burst at {at:?}, not at the wall"); + assert!(out.messages.iter().any(|m| m.contains("разбилась")), "{:?}", out.messages); + // One result, recorded, with the flight it belongs to as a track of its own (#109). + let events = strike_events(&sim); + assert_eq!(events.len(), 1, "{events:?}"); + assert!(matches!(events[0], record::EventKind::Strike { result: record::StrikeResult::Blocked, .. }), "{:?}", events[0]); + let rec = sim.rec.iter().chain(sim.replays.iter()).next().expect("the shot made no recording"); + let tr = rec.tracks.iter().find(|t| t.what == record::What::Strike).expect("no recorded flight"); + let end = tr.at(rec.origin, tr.last() as f64).expect("no last point"); + assert!(end.z < 145.0 && end.z > 100.0, "the recorded flight ends at {end:?}, not at the wall"); + assert!((end.z - at.z).abs() < 3.0, "the recorded flight and the blast disagree: {end:?} / {at:?}"); + } + + // #106: the sections say "solid" over the whole wall, but its exact shape leaves an opening: the flight goes + // through it by the narrow phase (and a slab in the way is flown over, not assumed clear). + #[test] + fn a_counter_strike_passes_an_opening_by_the_blocks_shape() { + let mut sim = Sim::new(11); + + // No launches: only the strike can make a recording here. + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); + let (mut ground, mut out) = (Ground::default(), Out::default()); + // A slit between 100 and 110 blocks high, where the missile flies at about 104. + let wall = Wall { at: 140, hole: Some((100, 110)) }; + warm(&mut ground, &wall, DVec3::new(0.0, 65.5, 0.0), DVec3::new(0.0, 105.0, 300.0)); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + let mut crossed = false; + for _ in 0..400 { + serve(&mut ground, &wall, sim.tick); + sim.step(&mut ground, None, &mut out); + crossed |= sim.strikes.flights.iter().any(|f| f.pos.z > 144.0); + if sim.strikes.flights.is_empty() { + break; + } + } + assert!(crossed, "the missile was stopped by a wall with a slit: {:?}", out.messages); + assert_eq!(sim.strikes.missed, 0, "it broke on the slit: {:?}", out.messages); + // It went on to its point: the site is hit (or narrowly missed), and never "blocked". + let events = strike_events(&sim); + assert_eq!(events.len(), 1, "{events:?}"); + assert!(!matches!(events[0], record::EventKind::Strike { result: record::StrikeResult::Blocked, .. }), "{:?}", events[0]); + assert_eq!(sim.strikes.fired, 1); + } + + #[test] + fn terminal_wall_is_resolved_before_landing() { + let mut sim = Sim::new(11); + + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 2); + let (mut ground, mut out) = (Ground::default(), Out::default()); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + let f = sim.strikes.flights[0]; + let wall = Wall { at: f.land.y.floor() as i32 - 1, hole: None }; + for _ in 0..500 { + serve(&mut ground, &wall, sim.tick); + sim.step(&mut ground, None, &mut out); + if sim.strikes.flights.is_empty() { break; } + } + assert!(sim.strikes.flights.is_empty()); + assert_eq!((sim.strikes.destroyed, sim.strikes.missed), (0, 1)); + assert!(sim.sites.get(id).unwrap().alive); + assert_eq!(sim.sites.pending(id), 2); + assert!(matches!(strike_events(&sim).as_slice(), [record::EventKind::Strike { result: record::StrikeResult::Blocked, .. }])); + assert!((sim.blasts[0].at.z - f64::from(wall.at)).abs() < 0.01); + } + + #[test] + fn delayed_world_answers_do_not_accelerate_the_strike() { + let mut sim = Sim::new(11); + + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 1000.0), 0); + let (mut ground, mut out) = (Ground::default(), Out::default()); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + for _ in 0..20 { sim.step(&mut ground, None, &mut out); } + let f = sim.strikes.flights[0]; + let open = Wall { at: 10_000, hole: None }; + warm(&mut ground, &open, f.from, DVec3::new(f.land.x, 110.0, f.land.y)); + let mut previous = f.pos; + for _ in 0..500 { + serve(&mut ground, &open, sim.tick); + sim.step(&mut ground, None, &mut out); + if let Some(f) = sim.strikes.flights.first() { + assert!(previous.distance(f.pos) <= TUNE.speed + 1e-8, "jump at tick {}", sim.tick); + previous = f.pos; + } else { break; } + } + assert!(sim.strikes.flights.is_empty(), "strike failed to arrive"); + assert_eq!(sim.strikes.destroyed, 1, "{:?}", out.messages); + } + + // #106: an open way works as before: the missile reaches its point, the site dies, one result, one stock spent. + #[test] + fn a_counter_strike_on_an_open_way_still_reaches_its_site() { + let mut sim = Sim::new(11); + + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 2); + let (mut ground, mut out) = (Ground::default(), Out::default()); + let open = Wall { at: 10_000, hole: None }; + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + for _ in 0..400 { + serve(&mut ground, &open, sim.tick); + sim.step(&mut ground, None, &mut out); + if sim.strikes.flights.is_empty() { + break; + } + } + assert!(sim.strikes.flights.is_empty()); + assert_eq!((sim.strikes.fired, sim.strikes.missed), (1, 0), "{:?}", out.messages); + assert_eq!(sim.strikes.destroyed, 1, "{:?}", out.messages); + assert!(!sim.sites.get(id).unwrap().alive); + } + + // #108: a missile already in the air is not lost when its site is taken off the map: it keeps its outcome by + // the coordinates it was fired at, and the stock and the result are accounted once. + #[test] + fn a_strike_whose_site_is_taken_off_the_map_still_has_its_result() { + let mut sim = Sim::new(11); + + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); + let (mut ground, mut out) = (Ground::default(), Out::default()); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + // The world unloads (or the sky is cleared): no site to find on arrival. + sim.clear(); + assert!(sim.sites.get(id).is_none()); + out.messages.clear(); + for _ in 0..400 { + sim.step(&mut ground, None, &mut out); + if sim.strikes.flights.is_empty() { + break; + } + } + assert!(sim.strikes.flights.is_empty(), "the missile vanished with the site"); + assert!(!out.messages.iter().any(|m| m.contains("свёрнута") || m.contains("пустоту")), "{:?}", out.messages); + assert_eq!(sim.strikes.fired, 1); + assert_eq!(sim.strikes.destroyed + sim.strikes.stunned + sim.strikes.missed, 1, "{:?}", out.messages); + assert_eq!(sim.strikes.shot_down, 0); + assert_eq!(strike_events(&sim).len(), 1, "the result was not recorded"); + } + + // #109: after the last enemy is gone, a strike still flying five seconds later keeps its recording open; + // its track and outcome survive, and the recording closes only after the usual quiet tail. + #[test] + fn a_late_counter_strike_keeps_the_recording_until_it_lands() { + let mut sim = Sim::new(11); + + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 1000.0), 0); + let (mut ground, mut out) = (Ground::default(), Out::default()); + let open = Wall { at: 10_000, hole: None }; + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + let drone = sim.make_drone(DroneKind::Shahed, DVec3::new(0.0, 65.0, 0.0), -DVec3::Z, DVec3::new(0.0, 64.0, 1000.0)); + sim.drones.push(drone); + serve(&mut ground, &open, sim.tick); + sim.step(&mut ground, None, &mut out); + assert!(sim.rec.is_some()); + sim.drones.clear(); + for _ in 0..100 { + serve(&mut ground, &open, sim.tick); + sim.step(&mut ground, None, &mut out); + } + assert!(!sim.strikes.flights.is_empty(), "the strike landed too early for this scenario"); + assert!(sim.rec.is_some(), "the recording closed five seconds after the last drone"); + for _ in 0..400 { + serve(&mut ground, &open, sim.tick); + sim.step(&mut ground, None, &mut out); + if sim.rec.is_none() { + break; + } + } + assert!(sim.rec.is_none(), "the recording never closed after the strike and its quiet tail"); + let rec = sim.replays.last().expect("the late strike was not kept"); + assert!(rec.tracks.iter().any(|t| t.what == record::What::Strike)); + assert!(rec.events.iter().any(|e| matches!(e.kind, record::EventKind::Strike { .. }))); + } } diff --git a/airdef-mod/src/sim/unit_specs_generated.rs b/airdef-mod/src/sim/unit_specs_generated.rs new file mode 100644 index 0000000..1581692 --- /dev/null +++ b/airdef-mod/src/sim/unit_specs_generated.rs @@ -0,0 +1,424 @@ +// Generated by tools/gen_catalog.py from catalog/specs.json. Do not edit. +// Game-scale values, not real weapon data; the catalog is the only copy. + +const NAMED_BATTERY_RADAR: &[(&str, f64, f64)] = &[("airdef:radar_trailer", 0.0, 4.2)]; +const NAMED_BATTERY_SPINNER: Option<(&str, f64, f64, f64, f64)> = Some(("airdef:radar_panel", 0.9, 4.2, 0.9, -0.3)); +const NAMED_TUBE_PIVOT: DVec3 = DVec3::new(0.0, -0.5, 1.5); + +const UNIT_SPEC_PATRIOT: SamSpec = SamSpec { + name: "Patriot", + key: "patriot", + missile: Some(MissileLoad { kind: MissileKind::Pac3, magazine: 4, min: 10.0, max: 450.0, interval: 16 }), + gun: None, + reload: 220, + scale: 1.3, + rest_pitch: 0.66, + roles: 30, + family: Family::Trainable, + parts: Parts { + body: "airdef:patriot_trailer", + launcher: Some("airdef:patriot_launcher"), + pack: None, + dish: None, + fixed: &[("airdef:patriot_truck", 0.0, -3.0), ("airdef:patriot_radar", 0.0, 3.6)], + spinner: None, + }, + mount: (0.69, 1.2), + pivot: DVec3::new(0.0, -0.5, 1.375), + muzzle: Muzzle::Patriot, + sensor: 585.0, + mast: 5.0, +}; + +const UNIT_SPEC_GEPARD: SamSpec = SamSpec { + name: "Гепард", + key: "gepard", + missile: None, + gun: Some(GunSpec { range: 135.0, track: 195.0, p: 0.3, bursts: 160, shell: 10.0, slew: (0.22, 0.16) }), + reload: 160, + scale: 1.25, + rest_pitch: 0.0, + roles: 7, + family: Family::Gun, + parts: Parts { + body: "airdef:gepard_hull", + launcher: Some("airdef:gepard_turret"), + pack: Some("airdef:gepard_guns"), + dish: Some(("airdef:gepard_dish", 0.95, 0.6, 0.0)), + fixed: &[], + spinner: None, + }, + mount: (1.55, 0.25), + pivot: DVec3::ZERO, + muzzle: Muzzle::None, + sensor: 255.0, + mast: 3.0, +}; + +const UNIT_SPEC_PANTSIR: SamSpec = SamSpec { + name: "Панцирь-С1", + key: "pantsir", + missile: Some(MissileLoad { kind: MissileKind::M57e6, magazine: 12, min: 25.0, max: 225.0, interval: 14 }), + gun: Some(GunSpec { range: 90.0, track: 135.0, p: 0.25, bursts: 140, shell: 9.0, slew: (0.25, 0.18) }), + reload: 280, + scale: 1.3, + rest_pitch: 0.0, + roles: 15, + family: Family::Turret, + parts: Parts { + body: "airdef:pantsir_truck", + launcher: Some("airdef:pantsir_turret"), + pack: Some("airdef:pantsir_pack"), + dish: Some(("airdef:pantsir_dish", 0.85, 0.3, 0.3)), + fixed: &[], + spinner: None, + }, + mount: (1.35, 0.69), + pivot: DVec3::ZERO, + muzzle: Muzzle::Pantsir, + sensor: 292.0, + mast: 4.0, +}; + +const UNIT_SPEC_S400: SamSpec = SamSpec { + name: "С-400", + key: "s400", + missile: Some(MissileLoad { kind: MissileKind::M48n6, magazine: 4, min: 30.0, max: 720.0, interval: 20 }), + gun: None, + reload: 420, + scale: 1.5, + rest_pitch: HALF_PI - 0.02, + roles: 30, + family: Family::Vertical, + parts: Parts { + body: "airdef:s400_chassis", + launcher: Some("airdef:s400_tubes"), + pack: None, + dish: None, + fixed: NAMED_BATTERY_RADAR, + spinner: NAMED_BATTERY_SPINNER, + }, + mount: (0.7, 1.45), + pivot: NAMED_TUBE_PIVOT, + muzzle: Muzzle::Grid(2, 2, 7.0), + sensor: 936.0, + mast: 5.0, +}; + +const UNIT_SPEC_S500: SamSpec = SamSpec { + name: "С-500", + key: "s500", + missile: Some(MissileLoad { kind: MissileKind::M77n6, magazine: 2, min: 40.0, max: 1050.0, interval: 20 }), + gun: None, + reload: 520, + scale: 1.6, + rest_pitch: HALF_PI - 0.02, + roles: 24, + family: Family::Vertical, + parts: Parts { + body: "airdef:s500_chassis", + launcher: Some("airdef:s500_tubes"), + pack: None, + dish: None, + fixed: NAMED_BATTERY_RADAR, + spinner: NAMED_BATTERY_SPINNER, + }, + mount: (0.7, 1.45), + pivot: NAMED_TUBE_PIVOT, + muzzle: Muzzle::Grid(2, 1, 9.5), + sensor: 1365.0, + mast: 5.0, +}; + +const UNIT_SPEC_IRIST: SamSpec = SamSpec { + name: "IRIS-T SLM", + key: "irist", + missile: Some(MissileLoad { kind: MissileKind::IrisT, magazine: 8, min: 15.0, max: 600.0, interval: 10 }), + gun: None, + reload: 300, + scale: 1.4, + rest_pitch: HALF_PI - 0.02, + roles: 15, + family: Family::Vertical, + parts: Parts { + body: "airdef:irist_chassis", + launcher: Some("airdef:irist_tubes"), + pack: None, + dish: None, + fixed: NAMED_BATTERY_RADAR, + spinner: NAMED_BATTERY_SPINNER, + }, + mount: (0.7, 1.45), + pivot: NAMED_TUBE_PIVOT, + muzzle: Muzzle::Grid(4, 2, 4.0), + sensor: 780.0, + mast: 5.0, +}; + +const UNIT_SPEC_NASAMS: SamSpec = SamSpec { + name: "NASAMS", + key: "nasams", + missile: Some(MissileLoad { kind: MissileKind::Amraam, magazine: 6, min: 10.0, max: 450.0, interval: 14 }), + gun: None, + reload: 260, + scale: 1.3, + rest_pitch: 0.5, + roles: 14, + family: Family::Trainable, + parts: Parts { + body: "airdef:nasams_trailer", + launcher: Some("airdef:nasams_launcher"), + pack: None, + dish: None, + fixed: &[("airdef:radar_trailer", 0.0, 3.4)], + spinner: Some(("airdef:radar_panel", 0.9, 3.4, 0.6, -0.3)), + }, + mount: (0.62, 0.4), + pivot: NAMED_TUBE_PIVOT, + muzzle: Muzzle::Grid(3, 2, 4.0), + sensor: 585.0, + mast: 5.0, +}; + +const UNIT_SPEC_BUK: SamSpec = SamSpec { + name: "Бук-М2", + key: "buk", + missile: Some(MissileLoad { kind: MissileKind::M9m317, magazine: 4, min: 20.0, max: 675.0, interval: 18 }), + gun: None, + reload: 400, + scale: 1.4, + rest_pitch: 0.0, + roles: 14, + family: Family::Turret, + parts: Parts { + body: "airdef:buk_chassis", + launcher: Some("airdef:buk_turret"), + pack: Some("airdef:buk_rails"), + dish: None, + fixed: &[], + spinner: None, + }, + mount: (1.55, 0.2), + pivot: DVec3::ZERO, + muzzle: Muzzle::Rails(&[-9.0, -3.0, 13.0, 19.0]), + sensor: 878.0, + mast: 4.0, +}; + +const UNIT_SPEC_TOR: SamSpec = SamSpec { + name: "Тор-М2", + key: "tor", + missile: Some(MissileLoad { kind: MissileKind::M9m338, magazine: 8, min: 8.0, max: 240.0, interval: 8 }), + gun: None, + reload: 300, + scale: 1.3, + rest_pitch: 0.0, + roles: 15, + family: Family::Vertical, + parts: Parts { + body: "airdef:tor_chassis", + launcher: Some("airdef:tor_turret"), + pack: None, + dish: None, + fixed: &[], + spinner: None, + }, + mount: (1.5, 0.0), + pivot: DVec3::ZERO, + muzzle: Muzzle::Cells, + sensor: 315.0, + mast: 3.5, +}; + +const UNIT_SPEC_P1SUN: SamSpec = SamSpec { + name: "P1-SUN", + key: "p1sun", + missile: Some(MissileLoad { kind: MissileKind::P1sun, magazine: 8, min: 5.0, max: 675.0, interval: 12 }), + gun: None, + reload: 240, + scale: 1.1, + rest_pitch: 0.35, + roles: 1, + family: Family::Trainable, + parts: Parts { + body: "airdef:nasams_trailer", + launcher: Some("airdef:p1sun_launcher"), + pack: None, + dish: None, + fixed: &[], + spinner: None, + }, + mount: (0.6, 0.3), + pivot: NAMED_TUBE_PIVOT, + muzzle: Muzzle::Grid(4, 2, 3.0), + sensor: 0.0, + mast: 2.0, +}; + +const UNIT_SPEC_RADAR: SamSpec = SamSpec { + name: "РЛС дальнего обнаружения", + key: "radar", + missile: None, + gun: None, + reload: 0, + scale: 1.5, + rest_pitch: 0.0, + roles: 0, + family: Family::Static, + parts: Parts { + body: "airdef:radar_truck", + launcher: None, + pack: None, + dish: None, + fixed: &[], + spinner: Some(("airdef:radar_panel", 1.2, 1.2, 1.5, -0.25)), + }, + mount: (0.0, 0.0), + pivot: DVec3::ZERO, + muzzle: Muzzle::None, + sensor: 2550.0, + mast: 16.0, +}; + +const UNIT_SPEC_DEPOT: SamSpec = SamSpec { + name: "Склад боеприпасов", + key: "depot", + missile: None, + gun: None, + reload: 0, + scale: 1.5, + rest_pitch: 0.0, + roles: 0, + family: Family::Static, + parts: Parts { + body: "airdef:depot", + launcher: None, + pack: None, + dish: None, + fixed: &[], + spinner: None, + }, + mount: (0.0, 0.0), + pivot: DVec3::ZERO, + muzzle: Muzzle::None, + sensor: 0.0, + mast: 1.0, +}; + +const UNIT_SPEC_TZM: SamSpec = SamSpec { + name: "ТЗМ", + key: "tzm", + missile: None, + gun: None, + reload: 0, + scale: 1.3, + rest_pitch: 0.0, + roles: 0, + family: Family::Truck, + parts: Parts { + body: "airdef:tzm_truck", + launcher: None, + pack: None, + dish: None, + fixed: &[], + spinner: None, + }, + mount: (0.0, 0.0), + pivot: DVec3::ZERO, + muzzle: Muzzle::None, + sensor: 0.0, + mast: 2.0, +}; + +const UNIT_SPEC_EW: SamSpec = SamSpec { + name: "Станция РЭБ", + key: "ew", + missile: None, + gun: None, + reload: 0, + scale: 1.3, + rest_pitch: 0.0, + roles: 0, + family: Family::Static, + parts: Parts { + body: "airdef:ew_truck", + launcher: None, + pack: None, + dish: None, + fixed: &[], + spinner: None, + }, + mount: (0.0, 0.0), + pivot: DVec3::ZERO, + muzzle: Muzzle::None, + sensor: 0.0, + mast: 3.0, +}; + +const UNIT_SPEC_LIGHT: SamSpec = SamSpec { + name: "Прожектор", + key: "light", + missile: None, + gun: None, + reload: 0, + scale: 1.2, + rest_pitch: 0.6, + roles: 0, + family: Family::Light, + parts: Parts { + body: "airdef:searchlight", + launcher: Some("airdef:searchlight_head"), + pack: None, + dish: None, + fixed: &[], + spinner: None, + }, + mount: (0.85, 0.0), + pivot: DVec3::ZERO, + muzzle: Muzzle::None, + sensor: 0.0, + mast: 1.5, +}; + +impl SamKind { + /// Its row of numbers from `catalog/specs.json`. + pub fn spec(self) -> &'static SamSpec { + match self { + SamKind::Patriot => &UNIT_SPEC_PATRIOT, + SamKind::Gepard => &UNIT_SPEC_GEPARD, + SamKind::Pantsir => &UNIT_SPEC_PANTSIR, + SamKind::S400 => &UNIT_SPEC_S400, + SamKind::S500 => &UNIT_SPEC_S500, + SamKind::IrisT => &UNIT_SPEC_IRIST, + SamKind::Nasams => &UNIT_SPEC_NASAMS, + SamKind::Buk => &UNIT_SPEC_BUK, + SamKind::Tor => &UNIT_SPEC_TOR, + SamKind::P1sun => &UNIT_SPEC_P1SUN, + SamKind::Radar => &UNIT_SPEC_RADAR, + SamKind::Depot => &UNIT_SPEC_DEPOT, + SamKind::Tzm => &UNIT_SPEC_TZM, + SamKind::Ew => &UNIT_SPEC_EW, + SamKind::Light => &UNIT_SPEC_LIGHT, + } + } +} + +/// Save keys by kind; a rename needs an explicit migration. +#[cfg(test)] +pub(super) const UNIT_SPEC_KEYS: [(SamKind, &str); 15] = [ + (SamKind::Patriot, "patriot"), + (SamKind::Gepard, "gepard"), + (SamKind::Pantsir, "pantsir"), + (SamKind::S400, "s400"), + (SamKind::S500, "s500"), + (SamKind::IrisT, "irist"), + (SamKind::Nasams, "nasams"), + (SamKind::Buk, "buk"), + (SamKind::Tor, "tor"), + (SamKind::P1sun, "p1sun"), + (SamKind::Radar, "radar"), + (SamKind::Depot, "depot"), + (SamKind::Tzm, "tzm"), + (SamKind::Ew, "ew"), + (SamKind::Light, "light"), +]; diff --git a/airdef-mod/src/sim/units.rs b/airdef-mod/src/sim/units.rs index 0788a42..3bf3c26 100644 --- a/airdef-mod/src/sim/units.rs +++ b/airdef-mod/src/sim/units.rs @@ -107,11 +107,6 @@ pub(super) struct Drive { pub pitch: (f64, f64), } -/// The drives are this share of the speeds they had before they had a speed of their own. -const DRIVE_SCALE: f64 = 0.4; -/// Ticks a drive takes to reach its top speed (and to stop from it). -const DRIVE_RAMP: f64 = 8.0; - pub struct SamSpec { pub name: &'static str, pub key: &'static str, @@ -140,39 +135,19 @@ impl SamSpec { pub(super) fn drive(&self) -> Drive { let (yaw, pitch) = match (&self.gun, self.family) { (Some(g), _) => g.slew, - (None, Family::Trainable) => (0.12, 0.0), - (None, Family::Vertical) => (0.15, 0.0), - (None, _) => (0.05, 0.05), + (None, Family::Trainable) => DRIVE_TRAINABLE, + (None, Family::Vertical) => DRIVE_VERTICAL, + (None, _) => DRIVE_OTHER, }; let axis = |speed: f64| (speed * DRIVE_SCALE, speed * DRIVE_SCALE / DRIVE_RAMP); Drive { yaw: axis(yaw), pitch: axis(pitch) } } } -const NO_PARTS: Parts = Parts { body: "", launcher: None, pack: None, dish: None, fixed: &[], spinner: None }; -/// The battery radar trailer behind a vertical launcher. -const BATTERY_RADAR: &[(&str, f64, f64)] = &[("airdef:radar_trailer", 0.0, 4.2)]; -const BATTERY_SPINNER: Option<(&str, f64, f64, f64, f64)> = Some(("airdef:radar_panel", 0.9, 4.2, 0.9, -0.3)); -const TUBE_PIVOT: DVec3 = DVec3::new(0.0, -0.5, 1.5); +include!("unit_specs_generated.rs"); impl SamKind { - pub const ALL: [SamKind; 15] = [ - SamKind::Patriot, - SamKind::Gepard, - SamKind::Pantsir, - SamKind::S400, - SamKind::S500, - SamKind::IrisT, - SamKind::Nasams, - SamKind::Buk, - SamKind::Tor, - SamKind::P1sun, - SamKind::Radar, - SamKind::Depot, - SamKind::Tzm, - SamKind::Ew, - SamKind::Light, - ]; + pub const ALL: [SamKind; super::catalog::UNIT_COUNT] = super::catalog::UNIT_KINDS; /// The ones that shoot (the roles menu lists these). pub const ARMED: [SamKind; 10] = [ SamKind::Patriot, @@ -186,361 +161,6 @@ impl SamKind { SamKind::Tor, SamKind::P1sun, ]; - pub fn spec(self) -> &'static SamSpec { - match self { - SamKind::Patriot => { - const S: SamSpec = SamSpec { - name: "Patriot", - key: "patriot", - missile: Some(MissileLoad { kind: MissileKind::Pac3, magazine: 4, min: 10.0, max: 450.0, interval: 16 }), - gun: None, - reload: 220, - scale: 1.3, - rest_pitch: 0.66, - roles: 30, - family: Family::Trainable, - parts: Parts { - body: "airdef:patriot_trailer", - launcher: Some("airdef:patriot_launcher"), - fixed: &[("airdef:patriot_truck", 0.0, -3.0), ("airdef:patriot_radar", 0.0, 3.6)], - ..NO_PARTS - }, - mount: (0.69, 1.2), - pivot: DVec3::new(0.0, -0.5, 1.375), - muzzle: Muzzle::Patriot, - sensor: 585.0, - mast: 5.0, - }; - &S - } - SamKind::Gepard => { - const S: SamSpec = SamSpec { - name: "Гепард", - key: "gepard", - missile: None, - gun: Some(GunSpec { range: 135.0, track: 195.0, p: 0.3, bursts: 160, shell: 10.0, slew: (0.22, 0.16) }), - reload: 160, - scale: 1.25, - rest_pitch: 0.0, - roles: 7, - family: Family::Gun, - parts: Parts { - body: "airdef:gepard_hull", - launcher: Some("airdef:gepard_turret"), - pack: Some("airdef:gepard_guns"), - dish: Some(("airdef:gepard_dish", 0.95, 0.6, 0.0)), - ..NO_PARTS - }, - mount: (1.55, 0.25), - pivot: DVec3::ZERO, - muzzle: Muzzle::None, - sensor: 255.0, - mast: 3.0, - }; - &S - } - SamKind::Pantsir => { - const S: SamSpec = SamSpec { - name: "Панцирь-С1", - key: "pantsir", - missile: Some(MissileLoad { kind: MissileKind::M57e6, magazine: 12, min: 25.0, max: 225.0, interval: 14 }), - gun: Some(GunSpec { range: 90.0, track: 135.0, p: 0.25, bursts: 140, shell: 9.0, slew: (0.25, 0.18) }), - reload: 280, - scale: 1.3, - rest_pitch: 0.0, - roles: 15, - family: Family::Turret, - parts: Parts { - body: "airdef:pantsir_truck", - launcher: Some("airdef:pantsir_turret"), - pack: Some("airdef:pantsir_pack"), - dish: Some(("airdef:pantsir_dish", 0.85, 0.3, 0.3)), - ..NO_PARTS - }, - mount: (1.35, 0.69), - pivot: DVec3::ZERO, - muzzle: Muzzle::Pantsir, - sensor: 292.0, - mast: 4.0, - }; - &S - } - SamKind::S400 => { - const S: SamSpec = SamSpec { - name: "С-400", - key: "s400", - missile: Some(MissileLoad { kind: MissileKind::M48n6, magazine: 4, min: 30.0, max: 720.0, interval: 20 }), - gun: None, - reload: 420, - scale: 1.5, - rest_pitch: HALF_PI - 0.02, - roles: 30, - family: Family::Vertical, - parts: Parts { - body: "airdef:s400_chassis", - launcher: Some("airdef:s400_tubes"), - fixed: BATTERY_RADAR, - spinner: BATTERY_SPINNER, - ..NO_PARTS - }, - mount: (0.7, 1.45), - pivot: TUBE_PIVOT, - muzzle: Muzzle::Grid(2, 2, 7.0), - sensor: 936.0, - mast: 5.0, - }; - &S - } - SamKind::S500 => { - const S: SamSpec = SamSpec { - name: "С-500", - key: "s500", - missile: Some(MissileLoad { kind: MissileKind::M77n6, magazine: 4, min: 40.0, max: 1050.0, interval: 20 }), - gun: None, - reload: 520, - scale: 1.6, - rest_pitch: HALF_PI - 0.02, - roles: 24, - family: Family::Vertical, - parts: Parts { - body: "airdef:s500_chassis", - launcher: Some("airdef:s500_tubes"), - fixed: BATTERY_RADAR, - spinner: BATTERY_SPINNER, - ..NO_PARTS - }, - mount: (0.7, 1.45), - pivot: TUBE_PIVOT, - muzzle: Muzzle::Grid(2, 1, 9.5), - sensor: 1365.0, - mast: 5.0, - }; - &S - } - SamKind::IrisT => { - const S: SamSpec = SamSpec { - name: "IRIS-T SLM", - key: "irist", - missile: Some(MissileLoad { kind: MissileKind::IrisT, magazine: 8, min: 15.0, max: 600.0, interval: 10 }), - gun: None, - reload: 300, - scale: 1.4, - rest_pitch: HALF_PI - 0.02, - roles: 15, - family: Family::Vertical, - parts: Parts { - body: "airdef:irist_chassis", - launcher: Some("airdef:irist_tubes"), - fixed: BATTERY_RADAR, - spinner: BATTERY_SPINNER, - ..NO_PARTS - }, - mount: (0.7, 1.45), - pivot: TUBE_PIVOT, - muzzle: Muzzle::Grid(4, 2, 4.0), - sensor: 780.0, - mast: 5.0, - }; - &S - } - SamKind::Nasams => { - const S: SamSpec = SamSpec { - name: "NASAMS", - key: "nasams", - missile: Some(MissileLoad { kind: MissileKind::Amraam, magazine: 6, min: 10.0, max: 450.0, interval: 14 }), - gun: None, - reload: 260, - scale: 1.3, - rest_pitch: 0.5, - roles: 14, - family: Family::Trainable, - parts: Parts { - body: "airdef:nasams_trailer", - launcher: Some("airdef:nasams_launcher"), - fixed: &[("airdef:radar_trailer", 0.0, 3.4)], - spinner: Some(("airdef:radar_panel", 0.9, 3.4, 0.6, -0.3)), - ..NO_PARTS - }, - mount: (0.62, 0.4), - pivot: TUBE_PIVOT, - muzzle: Muzzle::Grid(3, 2, 4.0), - sensor: 585.0, - mast: 5.0, - }; - &S - } - SamKind::Buk => { - const S: SamSpec = SamSpec { - name: "Бук-М2", - key: "buk", - missile: Some(MissileLoad { kind: MissileKind::M9m317, magazine: 4, min: 20.0, max: 675.0, interval: 18 }), - gun: None, - reload: 400, - scale: 1.4, - rest_pitch: 0.0, - roles: 14, - family: Family::Turret, - parts: Parts { - body: "airdef:buk_chassis", - launcher: Some("airdef:buk_turret"), - pack: Some("airdef:buk_rails"), - ..NO_PARTS - }, - mount: (1.55, 0.2), - pivot: DVec3::ZERO, - muzzle: Muzzle::Rails(&[-9.0, -3.0, 13.0, 19.0]), - sensor: 878.0, - mast: 4.0, - }; - &S - } - SamKind::Tor => { - const S: SamSpec = SamSpec { - name: "Тор-М2", - key: "tor", - missile: Some(MissileLoad { kind: MissileKind::M9m338, magazine: 8, min: 8.0, max: 240.0, interval: 8 }), - gun: None, - reload: 300, - scale: 1.3, - rest_pitch: 0.0, - roles: 15, - family: Family::Vertical, - parts: Parts { body: "airdef:tor_chassis", launcher: Some("airdef:tor_turret"), ..NO_PARTS }, - mount: (1.5, 0.0), - pivot: DVec3::ZERO, - muzzle: Muzzle::Cells, - sensor: 315.0, - mast: 3.5, - }; - &S - } - SamKind::P1sun => { - const S: SamSpec = SamSpec { - name: "P1-SUN", - key: "p1sun", - missile: Some(MissileLoad { kind: MissileKind::P1sun, magazine: 8, min: 5.0, max: 675.0, interval: 12 }), - gun: None, - reload: 240, - scale: 1.1, - rest_pitch: 0.35, - roles: 1, - family: Family::Trainable, - parts: Parts { body: "airdef:nasams_trailer", launcher: Some("airdef:p1sun_launcher"), ..NO_PARTS }, - mount: (0.6, 0.3), - pivot: TUBE_PIVOT, - muzzle: Muzzle::Grid(4, 2, 3.0), - // No radar of its own: it fires on the network's tracks. - sensor: 0.0, - mast: 2.0, - }; - &S - } - SamKind::Radar => { - const S: SamSpec = SamSpec { - name: "РЛС дальнего обнаружения", - key: "radar", - missile: None, - gun: None, - reload: 0, - scale: 1.5, - rest_pitch: 0.0, - roles: 0, - family: Family::Static, - parts: Parts { - body: "airdef:radar_truck", - spinner: Some(("airdef:radar_panel", 1.2, 1.2, 1.5, -0.25)), - ..NO_PARTS - }, - mount: (0.0, 0.0), - pivot: DVec3::ZERO, - muzzle: Muzzle::None, - sensor: 2550.0, - mast: 16.0, - }; - &S - } - SamKind::Depot => { - const S: SamSpec = SamSpec { - name: "Склад боеприпасов", - key: "depot", - missile: None, - gun: None, - reload: 0, - scale: 1.5, - rest_pitch: 0.0, - roles: 0, - family: Family::Static, - parts: Parts { body: "airdef:depot", ..NO_PARTS }, - mount: (0.0, 0.0), - pivot: DVec3::ZERO, - muzzle: Muzzle::None, - sensor: 0.0, - mast: 1.0, - }; - &S - } - SamKind::Tzm => { - const S: SamSpec = SamSpec { - name: "ТЗМ", - key: "tzm", - missile: None, - gun: None, - reload: 0, - scale: 1.3, - rest_pitch: 0.0, - roles: 0, - family: Family::Truck, - parts: Parts { body: "airdef:tzm_truck", ..NO_PARTS }, - mount: (0.0, 0.0), - pivot: DVec3::ZERO, - muzzle: Muzzle::None, - sensor: 0.0, - mast: 2.0, - }; - &S - } - SamKind::Ew => { - const S: SamSpec = SamSpec { - name: "Станция РЭБ", - key: "ew", - missile: None, - gun: None, - reload: 0, - scale: 1.3, - rest_pitch: 0.0, - roles: 0, - family: Family::Static, - parts: Parts { body: "airdef:ew_truck", ..NO_PARTS }, - mount: (0.0, 0.0), - pivot: DVec3::ZERO, - muzzle: Muzzle::None, - sensor: 0.0, - mast: 3.0, - }; - &S - } - SamKind::Light => { - const S: SamSpec = SamSpec { - name: "Прожектор", - key: "light", - missile: None, - gun: None, - reload: 0, - scale: 1.2, - rest_pitch: 0.6, - roles: 0, - family: Family::Light, - parts: Parts { body: "airdef:searchlight", launcher: Some("airdef:searchlight_head"), ..NO_PARTS }, - mount: (0.85, 0.0), - pivot: DVec3::ZERO, - muzzle: Muzzle::None, - sensor: 0.0, - mast: 1.5, - }; - &S - } - } - } pub fn name(self) -> &'static str { self.spec().name } @@ -697,6 +317,7 @@ pub(super) fn draw_sam_model(sam: &Sam, rng: &mut Rng, out: &mut Out) { } Family::Static | Family::Truck => {} } + if sam.mode != battery::SamMode::LauncherOnly { if let Some((model, h, f, tilt)) = parts.dish { part(out, model, mount + turret * DVec3::new(0.0, h * s, f * s), DQuat::from_rotation_y(sam.dish) * DQuat::from_rotation_x(tilt), s); } @@ -704,9 +325,19 @@ pub(super) fn draw_sam_model(sam: &Sam, rng: &mut Rng, out: &mut Out) { part(out, model, sam.pos + up + DVec3::Y * h * s + base * DVec3::new(0.0, 0.0, back * s), base, s); } if let Some((model, h, back, size, tilt)) = parts.spinner { - let at = sam.pos + up + DVec3::Y * h * s + base * DVec3::new(0.0, 0.0, back * s); - part(out, model, at, DQuat::from_rotation_y(sam.dish * 0.6) * DQuat::from_rotation_x(tilt), s * size); + let extra = if sam.kind == SamKind::Radar && sam.radar_role == battery::RadarRole::Height { 8.0 } else { 0.0 }; + let foot = sam.pos + up + DVec3::Y * h * s + base * DVec3::new(0.0, 0.0, back * s); + for segment in 0..(extra as u32 / 2) { + part(out, "airdef:radar_mast", foot + DVec3::Y * (segment as f64 * 2.0 + 0.5), base, 1.0); + } + let at = foot + DVec3::Y * extra; + let heading = if sam.kind == SamKind::Radar && sam.radar_role == battery::RadarRole::Engagement { + sam.tracks.first().map_or(sam.dish * 0.6, |t| heading_of(t.pos - sam.pos)) + } else { sam.dish * 0.6 }; + part(out, model, at, DQuat::from_rotation_y(heading) * DQuat::from_rotation_x(tilt), s * size); } + } + launch::draw_caps(sam, out); if sam.flash > 0 { let barrels: &[DVec3] = match sam.kind { SamKind::Pantsir => &[DVec3::new(-0.95, 0.33, -1.7), DVec3::new(0.95, 0.33, -1.7)], diff --git a/airdef-mod/src/sim/voices.rs b/airdef-mod/src/sim/voices.rs index cd10dba..71eeec4 100644 --- a/airdef-mod/src/sim/voices.rs +++ b/airdef-mod/src/sim/voices.rs @@ -18,23 +18,24 @@ impl MissileKind { /// The sound of its motor, its gain and its pitch: a rocket's roar, or the electric whine of /// a motor without a flame. pub fn engine_voice(self) -> (&'static str, f32, f32) { - if self.spec().flame == 0.0 { - return ("airdef:drone.lancet", 0.8, 1.6); - } - let pitch = match self { - MissileKind::Pac3 => 1.0, - MissileKind::M57e6 => 1.12, - MissileKind::M48n6 => 0.88, - MissileKind::M77n6 => 0.8, - MissileKind::IrisT | MissileKind::Amraam => 1.05, - MissileKind::M9m317 => 0.9, - MissileKind::M9m338 => 1.15, - MissileKind::P1sun => 1.7, - }; - ("airdef:missile.flight", 1.0, pitch) + self.spec().voice } /// Ticks from the launch until the motor lights (a cold launch throws it out of the tube first). pub fn lit_after(self) -> u32 { self.spec().cold } } + +#[cfg(test)] +mod tests { + use super::*; + use super::super::catalog::sound; + + /// The catalog holds the voice that is actually played. P1-SUN has no flame, so it uses the + /// Lancet voice at 1.6; its old `P1sun => 1.7` match arm was unreachable behind `flame == 0`. + #[test] + fn missile_engine_voices_keep_their_played_values() { + assert_eq!(MissileKind::P1sun.engine_voice(), (sound::DRONE_LANCET, 0.8, 1.6)); + assert_eq!(MissileKind::M48n6.engine_voice(), (sound::MISSILE_FLIGHT, 1.0, 0.88)); + } +} diff --git a/airdef-mod/src/sim/why.rs b/airdef-mod/src/sim/why.rs index fc7c736..c34e9db 100644 --- a/airdef-mod/src/sim/why.rs +++ b/airdef-mod/src/sim/why.rs @@ -92,11 +92,14 @@ impl SamKind { /// A live big radar in data-link reach of `sam` that measured `id` recently. pub(super) fn radar_cue(sam: &Sam, radars: &[&Sam], id: u32, now: u64) -> bool { radars.iter().any(|r| { - r.alive() + r.radar_role.authorizes() + && r.link_range() > 0.0 + && sam.link_range() > 0.0 + && r.alive() && !r.moving() && r.radar_up() && r.pos.distance(sam.pos) <= r.link_range().min(sam.link_range()) - && r.track(id).is_some_and(|t| t.own_tick > 0 && t.age(now) <= FAR_RADAR_AGE) + && r.track(id).is_some_and(|t| t.own_tick > 0 && now.saturating_sub(t.own_tick) <= if sam.mode == battery::SamMode::LauncherOnly { sight::MISSILE_AGE } else { FAR_RADAR_AGE }) }) } @@ -134,6 +137,7 @@ pub(super) struct Opts { /// `role_in_reach` that the system has a target of its roles in range anyway (no long shots then). #[allow(clippy::too_many_arguments)] pub(super) fn basis(sam: &Sam, load: &units::MissileLoad, track: &Track, d: &Drone, now: u64, claimed: u32, k: f64, opts: Opts, cued: bool, role_in_reach: bool) -> Option { + if sam.mode == battery::SamMode::LauncherOnly && !cued { return None; } let blocks = target_blocks(sam.roles, Some(track), now, d); if blocks & !ROLE != 0 { return None; @@ -246,15 +250,42 @@ pub(super) fn gun_blocks(s: &Sam, aim_id: u32, gt: u32, aim_error: f64, dist: f6 // ---------------------------------------------------------------- the diagnostic -/// One condition and whether it holds. +/// A diagnostic condition and its structured or legacy reason. +#[derive(Debug)] pub struct Check { pub ok: bool, - pub text: String, + pub reason: CheckReason, +} + +#[derive(Debug)] +pub enum CheckReason { + /// Diagnostics not yet migrated to typed reasons. + Text(String), + Parts(Vec<(Part, f64)>), + PartEffect(PartEffect), +} + +#[derive(Debug, PartialEq)] +pub enum PartEffect { + SensorFailed, SensorDamaged { fit: f64 }, + MountFailed { yaw_degrees: f64 }, MountDamaged { fit: f64 }, + LauncherFailed, LauncherDamaged { cells: u32, capacity: u32, reload_seconds: f64 }, + GunFailed, GunDamaged { fit: f64 }, + TracksFailed, TracksDamaged { speed: f64 }, + CommsFailed, CommsDamaged { range: f64 }, +} + +impl Check { + #[cfg(test)] + pub(super) fn legacy_text(&self) -> &str { + match &self.reason { CheckReason::Text(text) => text, _ => "" } + } } fn check(ok: bool, text: impl Into) -> Check { - Check { ok, text: text.into() } + Check { ok, reason: CheckReason::Text(text.into()) } } +fn part_check(reason: CheckReason) -> Check { Check { ok: false, reason } } impl Sim { /// Why system `index` fires or doesn't, at its current target or at the most threatening @@ -289,7 +320,9 @@ impl Sim { v.push(check(alive > 0, format!("в небе {alive}, на картине {seen} (своя РЛС {own}, остальные по сети)"))); // The parts: what is worn or failed and what it costs. if !sam.comps.whole() { - v.push(check(false, format!("узлы: {}", sam.parts_text()))); + let parts = Part::ALL.into_iter().filter(|p| p.on(sam.kind) && sam.comps.get(*p) < 1.0) + .map(|p| (p, sam.comps.get(p))).collect(); + v.push(part_check(CheckReason::Parts(parts))); } for p in Part::ALL { if !p.on(sam.kind) || sam.part_state(p) == PartState::Ok { @@ -297,20 +330,20 @@ impl Sim { } let failed = sam.part_state(p) == PartState::Failed; let effect = match (p, failed) { - (Part::Sensor, true) => "РЛС отказала: своих измерений нет, стволы не стреляют, ракеты — по данным сети".to_string(), - (Part::Sensor, false) => format!("РЛС повреждена: дальность {:.0}%, обзор вдвое реже при худшем", sam.radar_fit() * 100.0), - (Part::Mount, true) => format!("привод наведения заклинил: стоит на {:.0}°, не доворачивается", sam.yaw.to_degrees().rem_euclid(360.0)), - (Part::Mount, false) => format!("привод наведения повреждён: поворот {:.0}% от нормы", sam.drive_fit() * 100.0), - (Part::Launcher, true) => "пусковая отказала: пуски невозможны".to_string(), - (Part::Launcher, false) => format!("пусковая повреждена: {} ячеек из {}, перезарядка {:.0} с", sam.cells(), spec.missile.as_ref().map_or(0, |m| m.magazine), sam.reload_time() as f64 / 20.0), - (Part::Gun, true) => "орудие заклинило: не стреляет".to_string(), - (Part::Gun, false) => format!("орудие повреждено: меткость {:.0}%, заклинивает", sam.gun_fit() * 100.0), - (Part::Tracks, true) => "ходовая отказала: на марш не выйдет".to_string(), - (Part::Tracks, false) => format!("ходовая повреждена: скорость марша {:.0}%", sam.drive_speed() * 100.0), - (Part::Comms, true) => "связь отказала: сети нет, цели только свои".to_string(), - (Part::Comms, false) => format!("связь повреждена: линия {:.0} м", sam.link_range()), + (Part::Sensor, true) => PartEffect::SensorFailed, + (Part::Sensor, false) => PartEffect::SensorDamaged { fit: sam.radar_fit() }, + (Part::Mount, true) => PartEffect::MountFailed { yaw_degrees: sam.yaw.to_degrees().rem_euclid(360.0) }, + (Part::Mount, false) => PartEffect::MountDamaged { fit: sam.drive_fit() }, + (Part::Launcher, true) => PartEffect::LauncherFailed, + (Part::Launcher, false) => PartEffect::LauncherDamaged { cells: sam.cells(), capacity: spec.missile.as_ref().map_or(0, |m| m.magazine), reload_seconds: sam.reload_time() as f64 / 20.0 }, + (Part::Gun, true) => PartEffect::GunFailed, + (Part::Gun, false) => PartEffect::GunDamaged { fit: sam.gun_fit() }, + (Part::Tracks, true) => PartEffect::TracksFailed, + (Part::Tracks, false) => PartEffect::TracksDamaged { speed: sam.drive_speed() }, + (Part::Comms, true) => PartEffect::CommsFailed, + (Part::Comms, false) => PartEffect::CommsDamaged { range: sam.link_range() }, }; - v.push(check(false, effect)); + v.push(part_check(CheckReason::PartEffect(effect))); } if sam.jam > 0 { v.push(check(false, format!("орудие заклинило, ещё {:.1} с", sam.jam as f64 / 20.0))); @@ -334,7 +367,7 @@ impl Sim { let dist = sam.track(t.id).map_or(t.pos, |tr| tr.est(self.tick)).distance(sam.pos); v.push(check( true, - format!("цель: {} {}, {}", t.kind.spec().name, if held { "(сопровождается)" } else { "(самая опасная)" }, crate::panel::distance(dist)), + format!("цель: {} {}, {}", t.kind.spec().name, if held { "(сопровождается)" } else { "(самая опасная)" }, super::distance(dist)), )); let track = sam.track(t.id); let blocks = target_blocks(sam.roles, track, now, t); @@ -390,12 +423,16 @@ impl Sim { _ => "не обнаружена ни своей РЛС, ни сетью".to_string(), }; v.push(check(blocks & (UNSEEN | STALE) == 0, detect)); - if sam.kind.spec().sensor > 0.0 { + if sam.mode == battery::SamMode::LauncherOnly { + let radars: Vec<&Sam> = self.sams.iter().filter(|s| s.kind == SamKind::Radar).collect(); + let connected = radar_cue(sam, &radars, t.id, now); + v.push(check(connected, if connected { "внешняя РЛС: свежий трек по связи" } else { "внешняя РЛС: нет свежего трека или связи" }.to_string())); + } else if sam.kind.spec().sensor > 0.0 { let period = sam.scan_ticks(); v.push(check( sam.radar_up(), if sam.radar_up() { - format!("обзор: оборот {:.1} с{}, луч на {:.0}°", period / 20.0, if sam.kind.scan().agile { ", электронный, цели в треке чаще" } else { "" }, sam.beam.to_degrees()) + format!("обзор: оборот {:.1} с{}, луч на {:.0}°", period / 20.0, if sam.scan_profile().agile { ", электронный, цели в треке чаще" } else { "" }, sam.beam.to_degrees()) } else { "обзор: РЛС не работает".to_string() }, @@ -427,7 +464,7 @@ impl Sim { let reach = reach_blocks(load.min, load.max, 1.0, sam.pos, est); // Beyond the normal reach, a heavy system may fire on a big radar's cue. let radars: Vec<&Sam> = self.sams.iter().filter(|s| s.kind == SamKind::Radar).collect(); - let cued = sam.kind.far_shooter() && radar_cue(sam, &radars, t.id, now); + let cued = radar_cue(sam, &radars, t.id, now); let role_in_reach = self.drones.iter().any(|o| { sam.track(o.id).is_some_and(|tr| allowed(sam.roles, tr, now, o) && in_reach(load.min, load.max, 1.0, sam.pos, tr.est(now)) && sam.doctrine.area_blocks(sam.pos, tr, now) | sam.doctrine.reserve_blocks(sam.missiles, o) == 0 && claimed(o, &missile_on, sam.target) < sam.doctrine.needed(o)) }); @@ -447,7 +484,7 @@ impl Sim { no.push(name); } } - v.push(check(far_ok, if far_ok { format!("дальний пуск по РЛС: цель на {} дальше обычных {}", crate::panel::distance(dist), crate::panel::distance(load.max)) } else { format!("дальний пуск по РЛС не разрешён: {}", no.join(", ")) })); + v.push(check(far_ok, if far_ok { format!("дальний пуск по РЛС: цель на {} дальше обычных {}", super::distance(dist), super::distance(load.max)) } else { format!("дальний пуск по РЛС не разрешён: {}", no.join(", ")) })); } let free = first_claim; let need = sam.doctrine.needed(t); @@ -483,7 +520,7 @@ impl Sim { )); } let doc_ok = chance || area | held_back == 0; - let grounds = blocks & !ROLE == 0 && (blocks & ROLE == 0 || chance) && doc_ok && (reach == 0 || far_ok); + let grounds = (sam.mode != battery::SamMode::LauncherOnly || cued) && blocks & !ROLE == 0 && (blocks & ROLE == 0 || chance) && doc_ok && (reach == 0 || far_ok); fires |= grounds && free < need && mb == 0 && aligned && land == 0; } if let Some(gun) = &spec.gun { @@ -493,8 +530,8 @@ impl Sim { cand, format!( "орудие: сопровождение до {}, цель на {}, других орудий на ней {on_it}{}", - crate::panel::distance(gun.track), - crate::panel::distance(dist), + super::distance(gun.track), + super::distance(dist), if cand { "" } else { " — вне сопровождения (нужна своя РЛС)" } ), )); @@ -502,7 +539,7 @@ impl Sim { let mut why = Vec::new(); for (bit, name) in [ (AIM_ERROR, format!("орудие ещё наводится ({:.0}°)", err.to_degrees())), - (OUT_OF_RANGE, format!("дальше {}", crate::panel::distance(gun.range))), + (OUT_OF_RANGE, format!("дальше {}", super::distance(gun.range))), (COOLDOWN, "пауза между очередями".to_string()), (RELOADING, "перезарядка".to_string()), (NO_ROUNDS, "нет снарядов".to_string()), @@ -516,6 +553,11 @@ impl Sim { v.push(check(gb == 0, if gb == 0 { "орудие готово".to_string() } else { format!("орудие не стреляет: {}", why.join(", ")) })); fires |= gb == 0 && cand; } + if let Some(p) = sam.preparing { + let opening = p.ground || now.saturating_sub(p.start) < launch::OPEN_TICKS; + v.push(check(!opening, format!("подготовка пуска: открывается крышка ТПК №{}", p.cell + 1))); + fires &= !opening; + } v.push(check(fires, if fires { "итог: готова стрелять" } else { "итог: стрелять сейчас не может" })); v } @@ -538,10 +580,10 @@ mod tests { sim.step(&mut ground, None, &mut out); } let w = sim.why(0); - assert!(w.last().is_some_and(|c| !c.ok), "{:?}", w.iter().map(|c| &c.text).collect::>()); - assert!(w.iter().any(|c| c.text.contains("нет целей классов"))); + assert!(w.last().is_some_and(|c| !c.ok), "{:?}", w.iter().map(|c| c.legacy_text()).collect::>()); + assert!(w.iter().any(|c| c.legacy_text().contains("нет целей классов"))); sim.sams[0].roles = 0xFF; - assert!(sim.why(0).iter().any(|c| c.text.starts_with("роль"))); + assert!(sim.why(0).iter().any(|c| c.legacy_text().starts_with("роль"))); } use super::sites::testland::{Land, serve}; @@ -586,7 +628,7 @@ mod tests { sim.step(&mut ground, None, &mut out); } let w = sim.why(0); - assert!(w.iter().any(|c| c.ok && c.text.starts_with("последний шанс")), "{:?}", w.iter().map(|c| &c.text).collect::>()); + assert!(w.iter().any(|c| c.ok && c.legacy_text().starts_with("последний шанс")), "{:?}", w.iter().map(|c| c.legacy_text()).collect::>()); } #[test] @@ -597,8 +639,8 @@ mod tests { assert!(until_fired(&mut sim, &mut ground, &land, &mut out, 150).is_none(), "fired at a Shahed far from its aim"); let w = sim.why(0); // Still named as the role block it was: no target of its classes. - assert!(w.last().is_some_and(|c| !c.ok) && w.iter().any(|c| c.text.contains("нет целей классов")), "{:?}", w.iter().map(|c| &c.text).collect::>()); - assert!(!w.iter().any(|c| c.text.starts_with("последний шанс"))); + assert!(w.last().is_some_and(|c| !c.ok) && w.iter().any(|c| c.legacy_text().contains("нет целей классов")), "{:?}", w.iter().map(|c| c.legacy_text()).collect::>()); + assert!(!w.iter().any(|c| c.legacy_text().starts_with("последний шанс"))); } #[test] @@ -665,7 +707,7 @@ mod tests { let (mut sim, mut ground, land, mut out) = far_setup(true, max * 1.4); assert!(fire_far(&mut sim, &mut ground, &land, &mut out, 80) >= 1, "no long shot at 1.4 x max with a radar"); let w = sim.why(0); - assert!(w.iter().any(|c| c.text.starts_with("дальний пуск по РЛС")), "{:?}", w.iter().map(|c| &c.text).collect::>()); + assert!(w.iter().any(|c| c.legacy_text().starts_with("дальний пуск по РЛС")), "{:?}", w.iter().map(|c| c.legacy_text()).collect::>()); // No radar, no shot (the system's own radar may well see it: that is not a cue). let (mut sim, mut ground, land, mut out) = far_setup(false, max * 1.4); assert_eq!(fire_far(&mut sim, &mut ground, &land, &mut out, 80), 0, "a long shot with no radar"); @@ -686,7 +728,8 @@ mod tests { let (mut sim, mut ground, land, mut out) = far_setup(true, max * 1.4); let far = sim.drones[0].id; sim.add_drone(DroneKind::Kh101, DVec3::new(300.0, 120.0, 0.0), DVec3::NEG_X, DVec3::new(0.0, 70.0, 0.0)); - for _ in 0..40 { + // The launcher must finish opening its TPK after the initial readiness cooldown. + for _ in 0..40 + launch::OPEN_TICKS { radar_sees(&mut sim); serve(&mut ground, &land, sim.tick); sim.step(&mut ground, None, &mut out); @@ -695,6 +738,53 @@ mod tests { assert!(sim.sams[0].fired > 0, "the closer target was not fired at"); } + #[test] + fn cold_launch_opens_before_spending_and_lights_only_after_ejection() { + let (mut sim, mut ground, land, mut out) = setup(SamKind::S400); + sim.last_chance = false; + sim.sams[0].cooldown = 0; + sim.add_drone(DroneKind::Kh101, DVec3::new(0.0, 160.0, -300.0), DVec3::Z, DVec3::ZERO); + let initial = sim.sams[0].missiles; + for _ in 0..200 { + serve(&mut ground, &land, sim.tick); + sim.step(&mut ground, None, &mut out); + if sim.sams[0].preparing.is_some() { break; } + } + assert!(sim.sams[0].preparing.is_some(), "a real tracked target must open a TPK"); + assert_eq!(sim.sams[0].missiles, initial); + assert!(sim.missiles.is_empty()); + // Loss during opening cancels without creating or consuming a missile. + sim.sams[0].health = 0.0; + sim.step(&mut ground, None, &mut out); + assert!(sim.sams[0].preparing.is_none() && sim.missiles.is_empty()); + assert_eq!(sim.sams[0].missiles, initial); + sim.sams[0].health = 1.0; + sim.sams[0].cooldown = 0; + for _ in 0..200 { + out = Out::default(); + serve(&mut ground, &land, sim.tick); + sim.step(&mut ground, None, &mut out); + if !sim.missiles.is_empty() { break; } + } + let m = sim.missiles.first().expect("ejection after reopening"); + let (id, kind) = (m.id, m.kind); + assert_eq!(sim.sams[0].missiles, initial - 1); + assert_eq!(sim.sams[0].spent_cells.count_ones(), 1); + assert!(!m.engine_lit()); + assert!(!out.engines.iter().any(|e| e.key == 0x4000_0000 | id)); + let mut last_speed = m.vel.length(); + for _ in 0..kind.lit_after() + 3 { + out = Out::default(); + serve(&mut ground, &land, sim.tick); + sim.step(&mut ground, None, &mut out); + let m = sim.missiles.iter().find(|m| m.id == id).expect("cold launch remains in flight"); + let voice = out.engines.iter().any(|e| e.key == 0x4000_0000 | id); + assert_eq!(voice, m.engine_lit()); + if m.engine_lit() { assert!(m.vel.length() > last_speed, "the lit motor must accelerate"); } + last_speed = m.vel.length(); + } + } + #[test] fn the_kill_chance_falls_to_half_at_the_far_end() { assert_eq!(reach_fit(100.0, 80.0), 1.0); diff --git a/airdef-mod/src/sim/world.rs b/airdef-mod/src/sim/world.rs index 8589d1e..5f6add2 100644 --- a/airdef-mod/src/sim/world.rs +++ b/airdef-mod/src/sim/world.rs @@ -238,27 +238,6 @@ impl WorldCache { pub fn is_empty(&self) -> bool { self.secs.is_empty() } - /// One line for the report. - pub fn text(&self) -> String { - let s = &self.stats; - format!( - "мир: {} секций ({} КБ), запросов {}, неизвестно {}, лучей {} (ударов {}, неизв. {}), клеток {}; взрывы в блоках: {} (подтверждено {}, без перемен {}, нет чанка {}), чужих ответов {}", - self.len(), - self.bytes() / 1024, - s.sections_asked, - s.sections_unknown, - s.sweeps, - s.hits, - s.unknown_sweeps, - s.cells, - s.blasts_asked, - s.blasts_confirmed, - s.blasts_unchanged, - s.blasts_unloaded, - s.late_dropped - ) - } - /// The game's note of changes: another world voids everything; changed /// sections (or too many to say) are read again when next used. pub fn changed(&mut self, epoch: u32, overflow: bool, sections: &[Sec]) { @@ -799,6 +778,55 @@ pub fn section_of(source: &dyn WorldSource, s: Sec) -> Fill { } impl WorldCache { + /// Read-only occupancy at the instant of a blast. Some(true) is known cover, + /// Some(false) a fully known open segment, None unresolved geometry. No query, + /// chunk load or later reply can change damage already committed for this blast. + /// This deliberately treats partial collision blocks as occupied cubes. + pub fn blast_cover(&self, a: DVec3, b: DVec3, now: u64) -> Option { + let d = b - a; + let mut p = [a.x.floor() as i32, a.y.floor() as i32, a.z.floor() as i32]; + let av = [a.x, a.y, a.z]; + let dv = [d.x, d.y, d.z]; + let step = dv.map(|v| if v > 0.0 { 1 } else { -1 }); + let mut next = [f64::INFINITY; 3]; + let mut delta = [f64::INFINITY; 3]; + for i in 0..3 { + if dv[i] != 0.0 { + let edge = f64::from(p[i]) + if dv[i] > 0.0 { 1.0 } else { 0.0 }; + next[i] = (edge - av[i]) / dv[i]; + delta[i] = (1.0 / dv[i]).abs(); + } + } + let mut unknown = false; + for n in 0..256 { + let fill = self.secs.get(&sec_of(p[0], p[1], p[2])) + .filter(|e| !e.stale && now.saturating_sub(e.since) < RECHECK) + .map(|e| &e.fill); + let solid = match fill { + Some(Fill::Empty) => Some(false), + Some(Fill::Full) => Some(true), + Some(Fill::Mixed(bits)) => { + let i = ((p[1] & 15) << 8 | (p[2] & 15) << 4 | (p[0] & 15)) as usize; + Some(bits[i >> 3] >> (i & 7) & 1 != 0) + } + _ => None, + }; + match solid { + // Ignore the struck/origin voxel, not the next wall behind it. + Some(true) if n > 0 => return Some(true), + None => unknown = true, + _ => {} + } + let axis = if next[0] < next[1] { + if next[0] < next[2] { 0 } else { 2 } + } else if next[1] < next[2] { 1 } else { 2 }; + if next[axis] > 1.0 { return if unknown { None } else { Some(false) }; } + next[axis] += delta[axis]; + p[axis] += step[axis]; + } + None + } + /// Answers the exact rays of the last tick from a synthetic world, as the game does one tick later. pub fn serve_rays(&mut self, source: &dyn WorldSource, rays: Vec<(u32, DVec3, DVec3)>) { let replies: Vec = rays diff --git a/airdef-mod/src/smoke.rs b/airdef-mod/src/smoke.rs index 8f1db63..64ae252 100644 --- a/airdef-mod/src/smoke.rs +++ b/airdef-mod/src/smoke.rs @@ -3,13 +3,18 @@ //! derives everything from a segment's age, so the columns and blasts are a pure function of the //! tick (nothing grows twice when a frame is shown again); trails are the only state, and they are //! cut back when the tick goes backwards. -use crate::sim::{Class, Sim}; +use crate::{ + scene::{MoverKind, Snapshot}, + sim::{Class, DroneKind}, +}; use glam::DVec3; use horizon_mod_sdk::render::SmokeSegment; use std::collections::HashMap; const MAX_TRAIL: usize = 2400; const MAX_TOTAL: usize = 4000; +// A small optical-density lift for powered missile trails. Keep geometry and budgets unchanged. +const MISSILE_TRAIL_DENSITY: f32 = 1.12; struct Trail { seg: SmokeSegment, @@ -47,10 +52,18 @@ fn seg(id: u32, a: DVec3, b: DVec3, born: u64, life: u32) -> SmokeSegment { } } - /// The column over one fire up to `tick`: a puff every 15 ticks (90 for the weaker fires) that /// rises, spreads and thins. A pure function of the tick. -fn fire_column(out: &mut Vec, fire_id: u32, at: DVec3, start: u64, size: f32, shown: bool, tick: u64, level: &dyn Fn(u64) -> (f32, f32)) { +fn fire_column( + out: &mut Vec, + fire_id: u32, + at: DVec3, + start: u64, + size: f32, + shown: bool, + tick: u64, + level: &dyn Fn(u64) -> (f32, f32), +) { // Only the strongest fires get a full column; the rest a thin puff now and then. let step = if shown { 15 } else { 90 }; let first = start.max(tick.saturating_sub(520)); @@ -87,7 +100,13 @@ fn blast_billows(out: &mut Vec, blasts: &[crate::sim::Blast], tick } let h = b.size as f64 * (0.2 + 0.5 * j as f64); let at = b.at + DVec3::Y * h; - let mut s = seg(0x5000_0000 | (b.id.wrapping_mul(7) + j as u32) & 0x0fff_ffff, at, at, born, 800); + let mut s = seg( + 0x5000_0000 | (b.id.wrapping_mul(7) + j as u32) & 0x0fff_ffff, + at, + at, + born, + 800, + ); s.radius_start = b.size * 0.6; s.radius_end = b.size * 3.2 + 4.0; s.density = 2.4 / (1.0 + j as f32 * 0.25); @@ -115,14 +134,42 @@ struct Look { /// A rocket's white line, thicker for a bigger missile (`scale`: Pantsir 0.85, S-500 1.7). fn rocket(scale: f32, density: f32, life: f32) -> Look { let scale = scale.min(2.0); - Look { r0: 0.25 * scale, r1: 1.2 + 1.6 * scale, density, soot: 0.02, clear: 0.8 + 1.2 * scale, life: life + 250.0 * scale } + Look { + r0: 0.25 * scale, + r1: 1.2 + 1.6 * scale, + density: density * MISSILE_TRAIL_DENSITY, + soot: 0.02, + clear: 0.8 + 1.2 * scale, + life: life + 250.0 * scale, + } } fn drone_look(class: Class, scale: f32) -> Look { match class { - Class::Drone => Look { r0: 0.4, r1: 1.8, density: 0.15, soot: 0.3, clear: 0.3, life: 250.0 }, - Class::Jet => Look { r0: 0.5, r1: 2.5, density: 0.5, soot: 0.25, clear: 1.5, life: 500.0 }, - Class::Cruise => Look { r0: 0.6, r1: 3.0, density: 0.6, soot: 0.3, clear: 2.0, life: 600.0 }, + Class::Drone => Look { + r0: 0.4, + r1: 1.8, + density: 0.15, + soot: 0.3, + clear: 0.3, + life: 250.0, + }, + Class::Jet => Look { + r0: 0.5, + r1: 2.5, + density: 0.5, + soot: 0.25, + clear: 1.5, + life: 500.0, + }, + Class::Cruise => Look { + r0: 0.6, + r1: 3.0, + density: 0.6 * MISSILE_TRAIL_DENSITY, + soot: 0.3, + clear: 2.0, + life: 600.0, + }, Class::Supersonic => rocket(scale, 0.9, 400.0), Class::Ballistic => rocket(scale, 1.1, 500.0), } @@ -146,11 +193,8 @@ fn style(s: &mut SmokeSegment, l: Look) { /// Points between a replay trail segment's ends (a segment every `TRAIL_GRID * STEP` ticks). const TRAIL_GRID: usize = 4; -/// The smoke a recording shows at its tick `t`: trails behind everything that flew, columns over -/// the fires its events lit, billows over its blasts. A pure function of `t` (nothing is kept -/// between calls, so a seek, a pause or a cut shows exactly what playing through would), and it -/// touches no live state. Trail segments sit on a fixed grid of each track's points. -pub fn replay_smoke(rec: &crate::sim::record::Recording, t: f64) -> Vec { +/// Trail segments sit on a fixed grid of each recorded track's points. +fn recorded_trails(rec: &crate::sim::record::Recording, t: f64) -> Vec { use crate::sim::record::{STEP, What}; let tick = t.max(0.0) as u64; let mut out: Vec = Vec::new(); @@ -162,6 +206,12 @@ pub fn replay_smoke(rec: &crate::sim::record::Recording, t: f64) -> Vec missile_look(kind), + // Legacy fixtures retain their cruise trail; physical carriers use a rocket trail. + What::StrikeCarrier(_) => missile_look(crate::sim::MissileKind::M48n6), + What::Strike => Some(drone_look( + Class::Cruise, + DroneKind::Kalibr.spec().trail_scale(), + )), }; let Some(look) = look else { continue }; let life = look.life as f64; @@ -169,12 +219,37 @@ pub fn replay_smoke(rec: &crate::sim::record::Recording, t: f64) -> Vec tick || a.distance(b) >= 400.0 { + if born > tick + || a.distance(b) >= 400.0 + || (matches!(tr.what, What::StrikeCarrier(_)) + && born < tr.first as u64 + crate::sim::strike::EJECT_TICKS) + || (matches!(tr.what, What::Missile(kind, _) if !rec.missile_is_lit(tr, born as f64, kind.lit_after()))) + { continue; } - let mut s = seg(0x2000_0000 | ((n as u32).wrapping_mul(7919) ^ g as u32) & 0x0fff_ffff, a, b, born, 1200); + let ignition = match tr.what { + What::StrikeCarrier(_) => Some(tr.first as f64 + crate::sim::strike::EJECT_TICKS as f64), + What::Missile(kind, _) => Some(rec.missile_ignition(tr.id).map_or(tr.first as f64 + kind.lit_after() as f64 + 1.0, |e| e.at as f64)), + _ => None, + }; + if let Some(ignition) = ignition { + let segment_start = tr.first as f64 + (g - 1) as f64 * stride; + if segment_start < ignition { a = tr.at(rec.origin, ignition).unwrap_or(b); } + } + let mut s = seg( + 0x2000_0000 | ((n as u32).wrapping_mul(7919) ^ g as u32) & 0x0fff_ffff, + a, + b, + born, + 1200, + ); style(&mut s, look); if (tick as f64) < born as f64 + s.life as f64 { out.push(s); @@ -186,29 +261,31 @@ pub fn replay_smoke(rec: &crate::sim::record::Recording, t: f64) -> Vec = rec - .events - .iter() - .enumerate() - .filter(|(_, e)| e.at as u64 <= tick) - .map(|(k, e)| { - let (size, life) = crate::sim::record::blast_spec(e.kind); - crate::sim::Blast { id: 0x4000_0000 | k as u32, at: e.pos, size, start: e.at as u64, life } - }) - .collect(); - blast_billows(&mut out, &blasts, tick); - out.truncate(MAX_TOTAL); out } +/// A recording's smoke is just another read-only scene view; seeking rebuilds the same segments +/// from its tracks, fire transitions and blasts without consulting the current simulation. +#[cfg(test)] +pub fn replay_smoke(rec: &crate::sim::record::Recording, t: f64) -> Vec { + let context = crate::presentation::Context::playback( + crate::presentation::Mode::Replay, + 1, + None, + 0, + 0, + t, + None, + 1.0, + DVec3::ZERO, + true, + ); + Smoke::default().segments(&Snapshot::at_context( + &context, + crate::scene::Source::Recorded(rec), + )) +} + impl Smoke { fn trail(&mut self, key: (u8, u32), at: DVec3, tick: u64, look: Look) { let Some(&(from, when)) = self.last.get(&key) else { @@ -229,33 +306,59 @@ impl Smoke { } /// Follows the simulation one tick: new trail segments for what flies, old ones dropped. - pub fn update(&mut self, sim: &Sim) { - let tick = sim.tick; + pub fn update(&mut self, scene: &Snapshot<'_>) { + debug_assert_eq!( + scene.source(), + "live", + "incremental trails are sampled from live poses only" + ); + let tick = scene.time().max(0.0) as u64; if tick < self.last_tick { self.trails.retain(|t| t.seg.born as u64 <= tick); self.last.clear(); } self.last_tick = tick; - for d in &sim.drones { - let spec = d.kind.spec(); - self.trail((0, d.id), d.pos, tick, drone_look(spec.class, spec.trail_scale())); - } - for m in &sim.missiles { - if let Some(look) = missile_look(m.kind) { - self.trail((1, m.id), m.pos, tick, look); + let movers = scene.movers(); + for mover in &movers { + match mover.kind { + MoverKind::Drone(kind) => { + let spec = kind.spec(); + self.trail( + (mover.group, mover.id), + mover.pos, + tick, + drone_look(spec.class, spec.trail_scale()), + ); + } + MoverKind::Missile { kind, engine_lit } => { + if !engine_lit { continue; } + if let Some(look) = missile_look(kind) { + self.trail((mover.group, mover.id), mover.pos, tick, look); + } + } + MoverKind::Strike { + physical, + engine_lit, + } => { + if engine_lit { + let look = if physical { + rocket(crate::sim::strike::ROUND_SCALE as f32, 0.8, 250.0) + } else { + drone_look(Class::Cruise, DroneKind::Kalibr.spec().trail_scale()) + }; + self.trail((mover.group, mover.id), mover.pos, tick, look); + } + } } } // Forget what is gone. if tick % 100 == 0 { - let alive: std::collections::HashSet<(u8, u32)> = sim - .drones - .iter() - .map(|d| (0, d.id)) - .chain(sim.missiles.iter().map(|m| (1, m.id))) - .collect(); + let alive: std::collections::HashSet<(u8, u32)> = + movers.iter().map(|m| (m.group, m.id)).collect(); self.last.retain(|k, _| alive.contains(k)); } - self.trails.retain(|t| tick < t.seg.born as u64 + t.seg.life as u64); + self.trails + .retain(|t| tick < t.seg.born as u64 + t.seg.life as u64); if self.trails.len() > MAX_TRAIL { let cut = self.trails.len() - MAX_TRAIL; self.trails.drain(..cut); @@ -263,17 +366,36 @@ impl Smoke { } /// Every segment alive at the simulation's tick: trails, fire columns, blast smoke. - pub fn segments(&self, sim: &Sim) -> Vec { - let tick = sim.tick; - let mut out: Vec = self.trails.iter().map(|t| t.seg.clone()).collect(); - // A column over each fire: a puff every 15 ticks that rises, spreads and thins. - for f in &sim.fires.list { - if f.quiet && f.size < 1.0 { + pub fn segments(&self, scene: &Snapshot<'_>) -> Vec { + let tick = scene.time().max(0.0) as u64; + let mut out = match scene.recording() { + Some(rec) => recorded_trails(rec, scene.time()), + None if scene.is_recorded_view() => Vec::new(), + None => self.trails.iter().map(|t| t.seg.clone()).collect(), + }; + // The same fire and blast adapters serve live effects and seekable recorded effects. + let mut fires = scene.fires(); + fires.sort_by(|a, b| { + b.size() + .total_cmp(&a.size()) + .then(a.start().cmp(&b.start())) + }); + for (rank, f) in fires.iter().enumerate() { + if f.quiet() && f.size() < 1.0 { continue; } - fire_column(&mut out, f.id, f.at, f.start, f.size, f.shown, tick, &|k| f.level(k)); + fire_column( + &mut out, + 0x1000_0000 | f.key(), + f.at(), + f.start(), + f.size(), + f.shown() && rank < crate::sim::fires::MAX, + tick, + &|k| f.level(k), + ); } - blast_billows(&mut out, &sim.blasts, tick); + blast_billows(&mut out, &scene.smoke_blasts(), tick); out.truncate(MAX_TOTAL); out } @@ -282,7 +404,220 @@ impl Smoke { #[cfg(test)] mod tests { use super::*; - use crate::sim::{DroneKind, MissileKind}; + use crate::scene; + use crate::{ + presentation::Context, + sim::{DroneKind, MissileKind, Sim}, + }; + + fn live_scene(sim: &Sim) -> Snapshot<'_> { + let ctx = Context::live(1, None, 0, sim.tick, None, DVec3::ZERO); + Snapshot::at_context(&ctx, scene::Source::Live(sim)) + } + + #[test] + fn replay_smoke_has_trails_and_fire_columns_and_is_a_function_of_time() { + use crate::sim::record::{Event, EventKind, Recording, What}; + let sim = Sim::new(1); + let mut rec = Recording::blank(160); + for id in 1..=2 { + let x = (id - 1) as f64 * 30.0; + rec.straight( + id, + What::Drone(DroneKind::Geran3), + 0, + 160, + DVec3::new(x, 80.0, 0.0), + DVec3::new(x, 80.0, 500.0), + ); + } + assert_eq!(rec.tracks.len(), 2); + rec.events = vec![Event { + at: 40, + pos: DVec3::new(5.0, 70.0, 5.0), + kind: EventKind::Hit { + drone: 1, + kind: DroneKind::Geran3, + power: 2, + object: None, + sam: None, + }, + }]; + let live_state = (sim.tick, sim.fires.list.len()); + let t = 130.0; + let a = replay_smoke(&rec, t); + assert!(a.iter().any(|s| s.clear > 0.0), "trails of both tracks"); + assert!(a.iter().any(|s| s.soot > 0.8), "a column over the fire"); + assert!(a.iter().any(|s| s.soot == 0.55), "the blast's billows"); + let _ = replay_smoke(&rec, 10.0); + let _ = replay_smoke(&rec, 155.0); + let b = replay_smoke(&rec, t); + assert_eq!( + a.iter().map(|s| (s.id, s.born as u64)).collect::>(), + b.iter().map(|s| (s.id, s.born as u64)).collect::>() + ); + assert!(a.iter().all(|s| s.born <= t)); + assert!(replay_smoke(&rec, -5.0).is_empty()); + let ctx = Context::playback( + crate::presentation::Mode::Replay, + 1, + None, + 0, + 0, + 400.0, + None, + 1.0, + DVec3::ZERO, + true, + ); + let scene = Snapshot::at_context(&ctx, crate::scene::Source::Recorded(&rec)); + assert!(scene.blasts().is_empty(), "the visual flash has expired"); + assert_eq!( + scene.smoke_blasts().len(), + 1, + "smoke persists on the recording timeline" + ); + assert!(replay_smoke(&rec, 400.0).iter().any(|s| s.soot == 0.55)); + assert_eq!((sim.tick, sim.fires.list.len()), live_state); + } + + #[test] + fn missing_recording_never_falls_back_to_cached_live_trails() { + let ctx = Context::playback( + crate::presentation::Mode::Replay, + 2, + None, + 0, + 900, + 40.0, + None, + 0.5, + DVec3::ZERO, + true, + ); + let scene = Snapshot::at_context(&ctx, scene::Source::Empty); + let mut smoke = Smoke::default(); + smoke.trails.push(Trail { + seg: seg(1, DVec3::ZERO, DVec3::Z, 10, 1200), + }); + + assert!(scene.is_recorded_view()); + assert_eq!(scene.time(), 40.0); + assert!(smoke.segments(&scene).is_empty()); + } + + #[test] + fn live_counter_strike_leaves_a_trail_and_releases_its_source() { + use crate::sim::sites::{LaunchSite, SiteClass}; + use crate::sim::strike::{Estimate, Known, SiteState}; + use glam::DVec2; + let mut sim = Sim::new(11); + let id = 1; + sim.sites.list.push(LaunchSite { + id, + side: 0, + sector: 0, + bearing: 0.0, + class: SiteClass::Ramp, + pos: DVec3::new(0.0, 64.0, 300.0), + range: 300.0, + water: false, + stock: 0, + planned: 0, + alive: true, + next_free: 0, + offmap: false, + }); + sim.strikes.known.push(Known { + site: id, + class: SiteClass::Ramp, + obs: Vec::new(), + est: Some(Estimate { + pos: DVec2::new(0.0, 300.0), + radius: 5.0, + fix: true, + bearing: 0.0, + bearing_err: 0.01, + origin: DVec2::ZERO, + }), + launches: 1, + last: 0, + state: SiteState::Active, + aimed: false, + }); + sim.order_test_strike( + id, + DVec3::new(0.0, 64.0, 0.0), + &mut crate::sim::Out::default(), + ) + .unwrap(); + let mut smoke = Smoke::default(); + sim.tick = 1; + smoke.update(&live_scene(&sim)); + let from = sim.strikes.flights[0].pos; + sim.tick = 2; + sim.strikes.flights[0].pos += DVec3::Z * 8.0; + smoke.update(&live_scene(&sim)); + let segments = smoke.segments(&live_scene(&sim)); + assert_eq!( + segments.len(), + 1, + "a live strike must emit the cruise trail also seen in replay" + ); + assert_eq!(segments[0].az, from.z); + assert_eq!(segments[0].bz, from.z + 8.0); + let expected = drone_look(Class::Cruise, DroneKind::Kalibr.spec().trail_scale()); + assert_eq!(segments[0].density, expected.density); + // The same sampled poses now represent a physical cold-ejected round. + sim.strikes.flights[0].carrier = Some(99); + let mut smoke = Smoke::default(); + sim.tick = 1; + smoke.update(&live_scene(&sim)); + sim.tick = 2; + sim.strikes.flights[0].pos += DVec3::Y; + smoke.update(&live_scene(&sim)); + assert!( + smoke.segments(&live_scene(&sim)).is_empty(), + "ejection is not rocket exhaust" + ); + sim.tick = crate::sim::strike::EJECT_TICKS; + smoke.update(&live_scene(&sim)); + sim.tick += 1; + sim.strikes.flights[0].pos += DVec3::Y * 8.0; + smoke.update(&live_scene(&sim)); + let segments = smoke.segments(&live_scene(&sim)); + assert_eq!(segments.len(), 1); + assert_eq!( + segments[0].soot, 0.02, + "a physical strike uses the rocket style in both sources" + ); + assert!( + segments + .iter() + .all(|s| s.born >= crate::sim::strike::EJECT_TICKS as f64) + ); + sim.strikes.flights.clear(); + sim.tick = 100; + smoke.update(&live_scene(&sim)); + assert!( + smoke.last.is_empty(), + "finished strikes must not leave source bookkeeping behind" + ); + } + + #[test] + fn recorded_physical_strike_trail_starts_at_ignition_not_at_the_muzzle() { + use crate::sim::record::{Recording, What}; + let ignite = 10.0 + crate::sim::strike::EJECT_TICKS as f64; + let mut rec = Recording::blank(120); + rec.straight(1, What::StrikeCarrier(77), 10, 90, DVec3::ZERO, DVec3::Z * 80.0); + assert!(replay_smoke(&rec, 14.0).is_empty()); + assert!(replay_smoke(&rec, (ignite - 2.0).max(30.0)).is_empty(), "no trail before the ignition"); + let segments = replay_smoke(&rec, ignite + 12.0); + assert_eq!(segments.len(), 2); + assert!((segments[0].az - 20.0).abs() < 1e-6 && (segments[0].bz - 24.0).abs() < 1e-6, "the trail begins at the recorded ignition"); + assert!((segments[1].az - 24.0).abs() < 1e-6 && (segments[1].bz - 32.0).abs() < 1e-6); + } #[test] fn trails_follow_one_rule() { @@ -291,11 +626,22 @@ mod tests { let pantsir = missile_look(MissileKind::M57e6).unwrap(); assert!(pantsir.r1 < rocket_look.r1); let mut s = seg(0, DVec3::ZERO, DVec3::ZERO, 0, 0); - for class in [Class::Drone, Class::Jet, Class::Cruise, Class::Supersonic, Class::Ballistic] { + for class in [ + Class::Drone, + Class::Jet, + Class::Cruise, + Class::Supersonic, + Class::Ballistic, + ] { style(&mut s, drone_look(class, 1.0)); assert_eq!(s.heat, 0.0); } - for k in [MissileKind::Pac3, MissileKind::M57e6, MissileKind::M77n6, MissileKind::Amraam] { + for k in [ + MissileKind::Pac3, + MissileKind::M57e6, + MissileKind::M77n6, + MissileKind::Amraam, + ] { style(&mut s, missile_look(k).unwrap()); assert_eq!(s.heat, 0.0); } diff --git a/airdef-mod/src/wave.rs b/airdef-mod/src/wave.rs index ce5e1d1..0a8c99f 100644 --- a/airdef-mod/src/wave.rs +++ b/airdef-mod/src/wave.rs @@ -2,20 +2,8 @@ //! where, how far out and at what; and ready-made raids to start from. use crate::sim::{Aim, Approach, DroneKind, RaidOrder}; -/// The kinds a raid can have, in the menu's order. -pub const KINDS: [DroneKind; 11] = [ - DroneKind::Gerbera, - DroneKind::Shahed, - DroneKind::Geran3, - DroneKind::Lancet, - DroneKind::Kh101, - DroneKind::Kalibr, - DroneKind::Kh22, - DroneKind::Zircon, - DroneKind::Iskander, - DroneKind::Kinzhal, - DroneKind::Oreshnik, -]; +/// The historical raid order is owned by the shared content catalog. +pub use crate::sim::catalog::RAID_KINDS as KINDS; /// What a click on a kind's row steps its number through. const STEPS: [u32; 12] = [0, 1, 2, 3, 4, 6, 8, 10, 15, 20, 30, 40]; @@ -177,22 +165,65 @@ impl WaveConfig { } /// A combined strike of about `n`, as `/airdef wave mix n` orders it. pub fn mix(n: u32, dist: f64) -> RaidOrder { - let n = n.max(4); - let mut kinds = vec![ - (DroneKind::Gerbera, n / 4 + 1), - (DroneKind::Shahed, n / 3 + 1), - (DroneKind::Geran3, n / 8 + 1), - (DroneKind::Kh101, n / 8 + 1), - (DroneKind::Kalibr, n / 10 + 1), - (DroneKind::Kh22, n / 16), - (DroneKind::Zircon, n / 20), - (DroneKind::Iskander, n / 12 + 1), - (DroneKind::Kinzhal, n / 16), - ]; - if n >= 30 { - kinds.push((DroneKind::Oreshnik, 1)); - } - kinds.retain(|k| k.1 > 0); - RaidOrder { kinds, approach: Approach::TwoSectors, dist, aim: Aim::Objects } + RaidOrder::mix(n, dist) + } +} + +#[cfg(test)] +mod catalog_tests { + use super::*; + use crate::sim::catalog::{AIRCRAFT, UNITS}; + + fn assets() -> std::path::PathBuf { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets/airdef") + } + fn item_exists(item: &str) { + let name = item.strip_prefix("airdef:").expect("item namespace"); + for path in [format!("items/{name}.json"), format!("models/item/{name}.json"), format!("textures/item/{name}.png")] { + assert!(assets().join(&path).is_file(), "{item}: missing {path}"); + } + let definition = std::fs::read_to_string(assets().join(format!("items/{name}.json"))).unwrap(); + assert!(definition.contains(&format!("airdef:item/{name}")), "{item}: wrong item model"); + let model = std::fs::read_to_string(assets().join(format!("models/item/{name}.json"))).unwrap(); + assert!(model.contains(&format!("airdef:item/{name}")), "{item}: wrong icon reference"); + let png = std::fs::read(assets().join(format!("textures/item/{name}.png"))).unwrap(); + assert!(png.starts_with(b"\x89PNG\r\n\x1a\n"), "{item}: invalid PNG signature"); + } + fn model_exists(model: &str) { + let name = model.strip_prefix("airdef:").expect("model namespace"); + let path = assets().join(format!("models/block/{name}.json")); + assert!(path.is_file(), "missing model {}", path.display()); + } + + #[test] + fn catalog_resources_exist() { + for aircraft in AIRCRAFT { + model_exists(aircraft.kind.spec().model()); + if let Some(item) = aircraft.launch_item { item_exists(item); } + } + for unit in UNITS { + item_exists(unit.deploy_item); + let p = &unit.kind.spec().parts; + model_exists(p.body); + for model in [p.launcher, p.pack, p.dish.map(|v| v.0), p.spinner.map(|v| v.0)].into_iter().flatten() { model_exists(model); } + for (model, _, _) in p.fixed { model_exists(model); } + } + } + + #[test] + fn inventory_entries_match_catalog_and_scenario_order_round_trips() { + for item in AIRCRAFT.iter().filter_map(|a| a.launch_item).chain(UNITS.iter().map(|u| u.deploy_item)) { + assert_eq!(crate::ITEMS.iter().filter(|i| **i == item).count(), 1, "{item}"); + } + for item in crate::ITEMS.iter().filter(|i| i.starts_with("airdef:launch_") || i.starts_with("airdef:deploy_")) { + assert!(AIRCRAFT.iter().any(|a| a.launch_item == Some(*item)) || UNITS.iter().any(|u| u.deploy_item == *item), "uncatalogued item {item}"); + } + let mut wave = WaveConfig::default(); + for (i, count) in wave.counts.iter_mut().enumerate() { *count = i as u32; } + let text = wave.text(); + let mut restored = WaveConfig::default(); + restored.parse(&text.split_whitespace().collect::>()); + assert_eq!(restored.counts, wave.counts); + assert_eq!(restored.order().kinds, KINDS.iter().copied().zip(wave.counts).filter(|(_, n)| *n > 0).collect::>()); } } diff --git a/airdef-mod/src/why_view.rs b/airdef-mod/src/why_view.rs new file mode 100644 index 0000000..8950a96 --- /dev/null +++ b/airdef-mod/src/why_view.rs @@ -0,0 +1,66 @@ +//! Text for diagnostics produced by the simulation. +use crate::sim::why::{CheckReason, PartEffect}; + +pub fn reason(reason: &CheckReason) -> String { + match reason { + CheckReason::Text(text) => text.clone(), + CheckReason::Parts(parts) => format!("узлы: {}", parts.iter().map(|(part, value)| { + if *value <= 0.0 { format!("{} отказ", part.short()) } + else { format!("{} {:.0}%", part.short(), value * 100.0) } + }).collect::>().join(", ")), + CheckReason::PartEffect(effect) => match effect { + PartEffect::SensorFailed => "РЛС отказала: своих измерений нет, стволы не стреляют, ракеты — по данным сети".into(), + PartEffect::SensorDamaged { fit } => format!("РЛС повреждена: дальность {:.0}%, обзор вдвое реже при худшем", fit * 100.0), + PartEffect::MountFailed { yaw_degrees } => format!("привод наведения заклинил: стоит на {yaw_degrees:.0}°, не доворачивается"), + PartEffect::MountDamaged { fit } => format!("привод наведения повреждён: поворот {:.0}% от нормы", fit * 100.0), + PartEffect::LauncherFailed => "пусковая отказала: пуски невозможны".into(), + PartEffect::LauncherDamaged { cells, capacity, reload_seconds } => format!("пусковая повреждена: {cells} ячеек из {capacity}, перезарядка {reload_seconds:.0} с"), + PartEffect::GunFailed => "орудие заклинило: не стреляет".into(), + PartEffect::GunDamaged { fit } => format!("орудие повреждено: меткость {:.0}%, заклинивает", fit * 100.0), + PartEffect::TracksFailed => "ходовая отказала: на марш не выйдет".into(), + PartEffect::TracksDamaged { speed } => format!("ходовая повреждена: скорость марша {:.0}%", speed * 100.0), + PartEffect::CommsFailed => "связь отказала: сети нет, цели только свои".into(), + PartEffect::CommsDamaged { range } => format!("связь повреждена: линия {range:.0} м"), + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sim::Part; + #[test] + fn damaged_simulation_produces_player_diagnostics_in_order() { + use crate::sim::{Sam, SamKind, Sim}; + let mut sim = Sim::new(1); + let mut sam = Sam::new(1, SamKind::Pantsir, glam::DVec3::ZERO, 0.0); + sam.comps.0[Part::Sensor as usize] = 0.4; + sam.comps.0[Part::Tracks as usize] = 0.0; + sim.sams.push(sam); + let lines: Vec = sim.why(0).iter().map(|c| reason(&c.reason)).collect(); + let summary = lines.iter().position(|s| s == "узлы: РЛС 40%, Ход отказ").unwrap(); + let sensor = lines.iter().position(|s| s.starts_with("РЛС повреждена: дальность ")).unwrap(); + let tracks = lines.iter().position(|s| s == "ходовая отказала: на марш не выйдет").unwrap(); + assert!(summary < sensor && sensor < tracks, "{lines:?}"); + } + + #[test] + fn renders_damage_diagnostics() { + assert_eq!(reason(&CheckReason::Parts(vec![(Part::Sensor, 0.4), (Part::Tracks, 0.0)])), "узлы: РЛС 40%, Ход отказ"); + let cases = [ + (PartEffect::SensorFailed, "РЛС отказала: своих измерений нет, стволы не стреляют, ракеты — по данным сети"), + (PartEffect::SensorDamaged { fit: 0.4 }, "РЛС повреждена: дальность 40%, обзор вдвое реже при худшем"), + (PartEffect::MountFailed { yaw_degrees: 42.0 }, "привод наведения заклинил: стоит на 42°, не доворачивается"), + (PartEffect::MountDamaged { fit: 0.3 }, "привод наведения повреждён: поворот 30% от нормы"), + (PartEffect::LauncherFailed, "пусковая отказала: пуски невозможны"), + (PartEffect::LauncherDamaged { cells: 2, capacity: 4, reload_seconds: 12.0 }, "пусковая повреждена: 2 ячеек из 4, перезарядка 12 с"), + (PartEffect::GunFailed, "орудие заклинило: не стреляет"), + (PartEffect::GunDamaged { fit: 0.5 }, "орудие повреждено: меткость 50%, заклинивает"), + (PartEffect::TracksFailed, "ходовая отказала: на марш не выйдет"), + (PartEffect::TracksDamaged { speed: 0.2 }, "ходовая повреждена: скорость марша 20%"), + (PartEffect::CommsFailed, "связь отказала: сети нет, цели только свои"), + (PartEffect::CommsDamaged { range: 123.0 }, "связь повреждена: линия 123 м"), + ]; + for (effect, expected) in cases { assert_eq!(reason(&CheckReason::PartEffect(effect)), expected); } + } +} diff --git a/airdef-mod/src/world_view.rs b/airdef-mod/src/world_view.rs new file mode 100644 index 0000000..070347c --- /dev/null +++ b/airdef-mod/src/world_view.rs @@ -0,0 +1,34 @@ +//! Player-facing world-cache report, also used by the headless scenario driver. +use airdef_core::sim::world::WorldCache; + +pub fn text(world: &WorldCache) -> String { + let s = &world.stats; + format!( + "мир: {} секций ({} КБ), запросов {}, неизвестно {}, лучей {} (ударов {}, неизв. {}), клеток {}; взрывы в блоках: {} (подтверждено {}, без перемен {}, нет чанка {}), чужих ответов {}", + world.len(), world.bytes() / 1024, s.sections_asked, s.sections_unknown, + s.sweeps, s.hits, s.unknown_sweeps, s.cells, s.blasts_asked, + s.blasts_confirmed, s.blasts_unchanged, s.blasts_unloaded, s.late_dropped + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reports_cache_counts_in_existing_format() { + let mut world = WorldCache::default(); + world.stats.sections_asked = 1; + world.stats.sections_unknown = 2; + world.stats.sweeps = 3; + world.stats.hits = 4; + world.stats.unknown_sweeps = 5; + world.stats.cells = 6; + world.stats.blasts_asked = 7; + world.stats.blasts_confirmed = 8; + world.stats.blasts_unchanged = 9; + world.stats.blasts_unloaded = 10; + world.stats.late_dropped = 11; + assert_eq!(text(&world), "мир: 0 секций (0 КБ), запросов 1, неизвестно 2, лучей 3 (ударов 4, неизв. 5), клеток 6; взрывы в блоках: 7 (подтверждено 8, без перемен 9, нет чанка 10), чужих ответов 11"); + } +} diff --git a/airdef-mod/tests/budgets/wasm_budget.toml b/airdef-mod/tests/budgets/wasm_budget.toml new file mode 100644 index 0000000..3fa8027 --- /dev/null +++ b/airdef-mod/tests/budgets/wasm_budget.toml @@ -0,0 +1,292 @@ +# Opt-in heavy contract scenario. Run through `test-budgets` so the routine +# `test-wasm` remains quick. This drives the real .hmod in Horizon's sandbox; +# no GPU or rendered-frame correctness is implied by missing query replies. +[[step]] +ticks = 2 +frames = 2 +expect.commands = ["airdef"] + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy patriot" +expect.feedback = "Patriot развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy pantsir" +expect.feedback = "Панцирь-С1 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy gepard" +expect.feedback = "Гепард развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy nasams" +expect.feedback = "NASAMS развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy irist" +expect.feedback = "IRIS-T SLM развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy tor" +expect.feedback = "Тор-М2 развёрнут (integrated, multi)" + +[[step]] +command = "airdef deploy s400" +expect.feedback = "С-400 развёрнут (integrated, multi)" + +# The next raid can only be ordered after the first plan is complete. +[[step]] +command = "airdef wave kh101 80 4000" + +[[step]] +ticks = 450 +frames = 225 +expect.chat = ["Противник готовит налёт: ищет площадки, местность подгружается", "Площадок за пределами загруженной карты: 1: оттуда пуск без площадки, с высоты полёта", "ВОЗДУШНАЯ ТРЕВОГА: налёт, 80 целей (один сектор)"] + +[[step]] +command = "airdef wave kh101 80 4000" +expect.chat = [] + +[[step]] +ticks = 600 +frames = 300 +expect.chat = ["Противник готовит налёт: ищет площадки, местность подгружается", "Площадок за пределами загруженной карты: 1: оттуда пуск без площадки, с высоты полёта", "ВОЗДУШНАЯ ТРЕВОГА: налёт, 80 целей (один сектор)"] + +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 12, ракет: 0, ПВО: 64, сбито: 0, поражений: 0, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 0 секций (0 КБ), запросов 652, неизвестно 0, лучей 5642 (ударов 0, неизв. 5642), клеток 5642; взрывы в блоках: 0 (подтверждено 0, без перемен 0, нет чанка 0), чужих ответов 0" + +[[step]] +ticks = 400 +frames = 200 + +[[step]] +command = "airdef clear" diff --git a/airdef-mod/tests/budgets/wasm_flat_budget.toml b/airdef-mod/tests/budgets/wasm_flat_budget.toml new file mode 100644 index 0000000..bbaadbb --- /dev/null +++ b/airdef-mod/tests/budgets/wasm_flat_budget.toml @@ -0,0 +1,1064 @@ +[[step]] +render-ground-y = 64 +ticks = 2 +frames = 2 +expect.commands = ["airdef"] + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef wave oreshnik 40 2600" + +[[step]] +ticks = 100 +frames = 50 + +[[step]] +command = "airdef wave oreshnik 40 2600" +expect.chat = [] + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +command = "airdef shader hd" +expect.feedback = "Эффекты: HD" + +[[step]] +command = "airdef grid" + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +ticks = 10 +frames = 10 + +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 184, ракет: 10, ПВО: 64, сбито: 777, поражений: 1499, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 5353 секций (250 КБ), запросов 14405, неизвестно 0, лучей 327428 (ударов 6746, неизв. 213), клеток 2282312; взрывы в блоках: 1586 (подтверждено 0, без перемен 175, нет чанка 1330), чужих ответов 0" + +# The Windows #122 path: a Smart order through the UI launch function (`airdef raid` +# is the wave screen's ПУСК button handler) with a fat intel log and EW up — twice. +[[step]] +command = "airdef deploy ew" + +[[step]] +command = "airdef deploy ew" + +[[step]] +command = "airdef deploy ew" + +[[step]] +command = "airdef deploy ew" + +[[step]] +command = "airdef ew on" + +[[step]] +command = "airdef preset 1" +expect.feedback = "Готовый: Массированный удар" + +[[step]] +command = "airdef raid" +expect.chat = [] + +[[step]] +ticks = 400 +frames = 50 + +[[step]] +command = "airdef preset 1" +expect.feedback = "Готовый: Массированный удар" + +[[step]] +command = "airdef raid" +expect.chat = [] + +[[step]] +ticks = 800 +frames = 100 + +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 11, ракет: 3, ПВО: 68, сбито: 936, поражений: 1853, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 6643 секций (311 КБ), запросов 17355, неизвестно 0, лучей 393828 (ударов 9283, неизв. 978), клеток 2953257; взрывы в блоках: 1961 (подтверждено 0, без перемен 234, нет чанка 1727), чужих ответов 0" diff --git a/airdef-mod/tests/core_boundary.rs b/airdef-mod/tests/core_boundary.rs new file mode 100644 index 0000000..6a77f71 --- /dev/null +++ b/airdef-mod/tests/core_boundary.rs @@ -0,0 +1,37 @@ +//! The adapter re-exports the actual core types and keeps deterministic combat traces. +use glam::DVec3; + +macro_rules! trace { + ($sim:path) => {{ + use $sim::{DroneKind, Ground, Out, Sam, SamKind, Sim}; + let mut sim = Sim::new(0x87); + let id = sim.id(); + sim.sams.push(Sam::new(id, SamKind::Patriot, DVec3::new(0.0, 64.0, 0.0), 0.0)); + let mut out = Out::default(); + for (i, kind) in [DroneKind::Shahed, DroneKind::Kh101, DroneKind::Iskander].into_iter().enumerate() { + sim.launch(kind, DVec3::new(i as f64 * 100.0, 110.0, 800.0), -DVec3::Z, DVec3::new(0.0, 64.0, 0.0), &mut out); + } + let mut ground = Ground::default(); + let mut trace = Vec::new(); + for _ in 0..600 { + let wanted = ground.take_wanted(); + ground.answer(wanted.into_iter().map(|(x, z)| (x, z, 64)).collect(), sim.tick); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + trace.push(format!("{}:{}:{}:{}:{:?}:{:?}:{:?}:{:?}", sim.tick, sim.launched, sim.kills, sim.hits, + sim.sams.iter().map(|s| (s.id, s.missiles, s.fired, s.health)).collect::>(), + sim.drones.iter().map(|d| (d.id, d.pos, d.vel)).collect::>(), + sim.missiles.iter().map(|m| (m.id, m.pos)).collect::>(), out.messages)); + } + (trace, sim.sams.iter().map(|s| s.fired).sum::()) + }}; +} + +#[test] +fn adapter_reexports_core_and_combat_trace_is_deterministic() { + let _: airdef_core::sim::Sim = airdef::sim::Sim::new(1); + let adapter = trace!(airdef::sim); + let core = trace!(airdef_core::sim); + assert_eq!(adapter, core); + assert!(adapter.1 > 0, "the trace must include actual interceptor launches"); +} diff --git a/airdef-mod/tests/wasm_contract.toml b/airdef-mod/tests/wasm_contract.toml new file mode 100644 index 0000000..c7b5440 --- /dev/null +++ b/airdef-mod/tests/wasm_contract.toml @@ -0,0 +1,121 @@ +# Actual .hmod in the engine sandbox, with default fuel/memory limits. +# Missing render replies exercise fallback paths, not GPU correctness. +[[step]] +ticks = 2 +frames = 2 +expect.commands = ["airdef"] + +[[step]] +command = "airdef capabilities" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef ground-cancel 2" +expect.error = "Нет ожидающей заявки RM48U" + +# Exercise the typed inventory through the actual WASM command boundary. +[[step]] +command = "airdef ground-load 2" +expect.error = "Нет живого склада с RM48U рядом (60 блоков)" + +[[step]] +command = "airdef deploy depot" + +[[step]] +command = "airdef ground-load 2" +expect.error = "Склад №3 рядом, но RM48U: 0. Задайте запас: /airdef ground-stock 3 <количество>" + +[[step]] +command = "airdef ground-stock 3 2" +expect.feedback = "Sandbox: задан начальный запас склада №3: RM48U 2" + +[[step]] +command = "airdef ground-load 2" +expect.feedback = "С-400 №2: загружена RM48U (игровой профиль), ударных 1, ПВО 3; на складе RM48U 1, снятых ракет ПВО 1" + +[[step]] +command = "airdef ground-unload 2" +expect.feedback = "С-400 №2: выгружена RM48U; ударных 0, ПВО 4" + +# Split battery configuration crosses the same saved command boundary. +[[step]] +command = "airdef battery 2 launcher" +expect.feedback = "№2 С-400: режим launcher" + +[[step]] +command = "airdef deploy radar_track" + +[[step]] +command = "airdef radar 4 search" +expect.feedback = "№4 РЛС: роль search" + +[[step]] +command = "airdef radar 4 engagement" +expect.feedback = "№4 РЛС: роль engagement" + +[[step]] +command = "airdef radar 4 off" +expect.feedback = "№4 РЛС дальнего обнаружения: излучение РЛС off" + +[[step]] +command = "airdef radar 4 on" +expect.feedback = "№4 РЛС дальнего обнаружения: излучение РЛС on" + +[[step]] +command = "airdef radar 2 off" +expect.error = "У комплекса нет активной РЛС" + +[[step]] +command = "airdef deploy s500_launcher" + +# The wave screen's preset button through the command boundary; an empty wave cannot launch. +[[step]] +command = "airdef preset 0" +expect.feedback = "Готовый: Рой Шахедов" + +[[step]] +command = "airdef preset 6" +expect.feedback = "Готовый: Пусто" + +[[step]] +command = "airdef raid" +expect.error = "Налёт пуст: выберите типы и число" + +[[step]] +command = "airdef wave mix 40 1500" + +[[step]] +ticks = 240 +frames = 120 + +[[step]] +command = "airdef clear" + +[[step]] +ticks = 140 +frames = 10 + +[[step]] +command = "airdef replay stop" + +[[step]] +title-screen = true +ticks = 2 +frames = 2 + +[[step]] +player = { x = 100.5, y = 64.0, z = 100.5 } +clock = { game-time = 500, day-time = 6000 } +ticks = 3 +frames = 3 + +[[step]] +reload = true +ticks = 2 +frames = 2 +expect.commands = ["airdef"] diff --git a/catalog/content.json b/catalog/content.json new file mode 100644 index 0000000..7c72aa8 --- /dev/null +++ b/catalog/content.json @@ -0,0 +1,394 @@ +{ + "aircraft": [ + { + "kind": "Gerbera", + "item": null, + "raid": true + }, + { + "kind": "Shahed", + "item": "launch_shahed", + "raid": true + }, + { + "kind": "Geran3", + "item": "launch_geran3", + "raid": true + }, + { + "kind": "Lancet", + "item": "launch_lancet", + "raid": true + }, + { + "kind": "Kh101", + "item": "launch_kh101", + "raid": true + }, + { + "kind": "Kalibr", + "item": "launch_kalibr", + "raid": true + }, + { + "kind": "Kh22", + "item": "launch_kh22", + "raid": true + }, + { + "kind": "Zircon", + "item": "launch_zircon", + "raid": true + }, + { + "kind": "Iskander", + "item": "launch_iskander", + "raid": true + }, + { + "kind": "Kinzhal", + "item": "launch_kinzhal", + "raid": true + }, + { + "kind": "Oreshnik", + "item": "launch_oreshnik", + "raid": true + }, + { + "kind": "OreshnikBlock", + "item": null, + "raid": false + }, + { + "kind": "OreshnikSub", + "item": null, + "raid": false + } + ], + "units": [ + { + "kind": "Patriot", + "item": "deploy_patriot" + }, + { + "kind": "Gepard", + "item": "deploy_gepard" + }, + { + "kind": "Pantsir", + "item": "deploy_pantsir" + }, + { + "kind": "S400", + "item": "deploy_s400" + }, + { + "kind": "S500", + "item": "deploy_s500" + }, + { + "kind": "IrisT", + "item": "deploy_irist" + }, + { + "kind": "Nasams", + "item": "deploy_nasams" + }, + { + "kind": "Buk", + "item": "deploy_buk" + }, + { + "kind": "Tor", + "item": "deploy_tor" + }, + { + "kind": "P1sun", + "item": "deploy_p1sun" + }, + { + "kind": "Radar", + "item": "deploy_radar" + }, + { + "kind": "Depot", + "item": "deploy_depot" + }, + { + "kind": "Tzm", + "item": "deploy_tzm" + }, + { + "kind": "Ew", + "item": "deploy_ew" + }, + { + "kind": "Light", + "item": "deploy_light" + } + ], + "items": [ + { + "id": "designator", + "title": "Целеуказатель", + "color": [ + 220, + 50, + 50 + ] + }, + { + "id": "launch_shahed", + "title": "Пуск: Шахед-136", + "color": [ + 90, + 92, + 96 + ] + }, + { + "id": "launch_geran3", + "title": "Пуск: Герань-3", + "color": [ + 30, + 30, + 34 + ] + }, + { + "id": "deploy_patriot", + "title": "Развернуть Patriot", + "color": [ + 76, + 86, + 58 + ] + }, + { + "id": "deploy_gepard", + "title": "Развернуть Гепард", + "color": [ + 104, + 84, + 54 + ] + }, + { + "id": "deploy_pantsir", + "title": "Развернуть Панцирь-С1", + "color": [ + 82, + 96, + 60 + ] + }, + { + "id": "deploy_s400", + "title": "Развернуть С-400", + "color": [ + 60, + 80, + 50 + ] + }, + { + "id": "deploy_s500", + "title": "Развернуть С-500", + "color": [ + 108, + 110, + 80 + ] + }, + { + "id": "launch_lancet", + "title": "Пуск: Ланцет-3", + "color": [ + 70, + 78, + 66 + ] + }, + { + "id": "launch_kh101", + "title": "Пуск: Х-101", + "color": [ + 84, + 88, + 92 + ] + }, + { + "id": "launch_kalibr", + "title": "Пуск: Калибр", + "color": [ + 150, + 154, + 156 + ] + }, + { + "id": "launch_kh22", + "title": "Пуск: Х-22", + "color": [ + 226, + 228, + 222 + ] + }, + { + "id": "launch_zircon", + "title": "Пуск: Циркон", + "color": [ + 52, + 54, + 52 + ] + }, + { + "id": "launch_iskander", + "title": "Пуск: Искандер-М", + "color": [ + 108, + 110, + 80 + ] + }, + { + "id": "launch_kinzhal", + "title": "Пуск: Кинжал", + "color": [ + 60, + 62, + 60 + ] + }, + { + "id": "launch_oreshnik", + "title": "Пуск: Орешник", + "color": [ + 230, + 120, + 40 + ] + }, + { + "id": "deploy_irist", + "title": "Развернуть IRIS-T SLM", + "color": [ + 76, + 86, + 58 + ] + }, + { + "id": "deploy_nasams", + "title": "Развернуть NASAMS", + "color": [ + 76, + 86, + 58 + ] + }, + { + "id": "deploy_buk", + "title": "Развернуть Бук-М2", + "color": [ + 82, + 96, + 60 + ] + }, + { + "id": "deploy_tor", + "title": "Развернуть Тор-М2", + "color": [ + 108, + 110, + 80 + ] + }, + { + "id": "deploy_p1sun", + "title": "Развернуть P1-SUN", + "color": [ + 52, + 54, + 52 + ] + }, + { + "id": "deploy_radar", + "title": "Развернуть РЛС дальнего обнаружения", + "color": [ + 108, + 110, + 80 + ] + }, + { + "id": "deploy_depot", + "title": "Развернуть склад боеприпасов", + "color": [ + 92, + 78, + 48 + ] + }, + { + "id": "deploy_tzm", + "title": "Развернуть ТЗМ", + "color": [ + 82, + 96, + 60 + ] + }, + { + "id": "deploy_ew", + "title": "Развернуть станцию РЭБ", + "color": [ + 108, + 110, + 80 + ] + }, + { + "id": "deploy_light", + "title": "Развернуть прожектор", + "color": [ + 52, + 54, + 52 + ] + }, + { + "id": "mover", + "title": "Пульт перемещения", + "color": [ + 60, + 140, + 220 + ] + }, + { + "id": "flare_gun", + "title": "Осветительная ракета", + "color": [ + 255, + 240, + 180 + ] + }, + { + "id": "remover", + "title": "Свернуть ПВО", + "color": [ + 200, + 200, + 200 + ] + } + ] +} diff --git a/catalog/sounds.json b/catalog/sounds.json new file mode 100644 index 0000000..c3da9ae --- /dev/null +++ b/catalog/sounds.json @@ -0,0 +1,107 @@ +[ + { + "event": "drone.shahed", + "distance": 220, + "far": false + }, + { + "event": "drone.geran3", + "distance": 260, + "far": false + }, + { + "event": "drone.lancet", + "distance": 120, + "far": false + }, + { + "event": "drone.cruise", + "distance": 320, + "far": false + }, + { + "event": "drone.rocket", + "distance": 450, + "far": false + }, + { + "event": "missile.flight", + "distance": 380, + "far": false + }, + { + "event": "drone.ballistic", + "distance": 700, + "far": false + }, + { + "event": "boom.sonic", + "distance": 900, + "far": true + }, + { + "event": "launch.patriot", + "distance": 650, + "far": true + }, + { + "event": "launch.s400_eject", + "distance": 400, + "far": true + }, + { + "event": "launch.s400", + "distance": 900, + "far": true + }, + { + "event": "launch.s500_eject", + "distance": 500, + "far": true + }, + { + "event": "launch.s500", + "distance": 1100, + "far": true + }, + { + "event": "launch.pantsir", + "distance": 500, + "far": true + }, + { + "event": "gun.gepard", + "distance": 400, + "far": true + }, + { + "event": "gun.pantsir", + "distance": 380, + "far": true + }, + { + "event": "blast.big", + "distance": 1000, + "far": true + }, + { + "event": "blast.heavy", + "distance": 1300, + "far": true + }, + { + "event": "blast.huge", + "distance": 1700, + "far": true + }, + { + "event": "blast.medium", + "distance": 700, + "far": true + }, + { + "event": "blast.air", + "distance": 800, + "far": true + } +] diff --git a/catalog/specs.json b/catalog/specs.json new file mode 100644 index 0000000..5731175 --- /dev/null +++ b/catalog/specs.json @@ -0,0 +1,869 @@ +{ + "shared": { + "mobility": { + "drive_scale": 0.4, + "drive_ramp": 8.0, + "trainable": [0.12, 0.0], + "vertical": [0.15, 0.0], + "other": [0.05, 0.05] + }, + "flight": { + "gravity": 0.05, + "block_split": 300.0 + }, + "logistics": { + "depot_reach": 60.0, + "handover": 10.0, + "truck_load": 2, + "stock_limit": 1000000 + } + }, + "drones": [ + { + "kind": "Shahed", + "key": "shahed", + "name": "Шахед-136", + "model": "airdef:shahed", + "speed": 0.6, + "turn": 0.035, + "cruise": 45.0, + "clearance": 18.0, + "dive": 32.0, + "warhead": 3, + "scale": 1.3, + "engine": {"type": "prop", "back": 1.34, "size": 1.0}, + "decoy": false, + "stealth": 1.0, + "sound": {"event": "drone.shahed", "pitch": 1.0, "interval": 48}, + "class": "drone", + "ballistic": null, + "rcs": 0.3, + "launch_sound": "launch.s400_eject" + }, + { + "kind": "Geran3", + "key": "geran3", + "name": "Герань-3", + "model": "airdef:geran3", + "speed": 1.6, + "turn": 0.03, + "cruise": 60.0, + "clearance": 18.0, + "dive": 50.0, + "warhead": 2, + "scale": 1.15, + "engine": {"type": "jet", "up": 0.34, "back": 1.55, "size": 1.0}, + "decoy": false, + "stealth": 1.1, + "sound": {"event": "drone.geran3", "pitch": 1.0, "interval": 40}, + "class": "jet", + "ballistic": null, + "rcs": 0.4, + "launch_sound": "launch.s400_eject" + }, + { + "kind": "Lancet", + "key": "lancet", + "name": "Ланцет-3", + "model": "airdef:lancet", + "speed": 1.1, + "turn": 0.07, + "cruise": 25.0, + "clearance": 10.0, + "dive": 38.0, + "warhead": 1, + "scale": 0.9, + "engine": {"type": "prop", "back": 0.93, "size": 0.55}, + "decoy": false, + "stealth": 1.4, + "sound": {"event": "drone.lancet", "pitch": 1.0, "interval": 32}, + "class": "drone", + "ballistic": null, + "rcs": 0.08, + "launch_sound": "launch.s400_eject" + }, + { + "kind": "Gerbera", + "key": "gerbera", + "name": "Гербера", + "model": "airdef:gerbera", + "speed": 0.5, + "turn": 0.04, + "cruise": 40.0, + "clearance": 15.0, + "dive": 30.0, + "warhead": 0, + "scale": 1.1, + "engine": {"type": "prop", "back": 1.03, "size": 0.9}, + "decoy": true, + "stealth": 0.9, + "sound": {"event": "drone.shahed", "pitch": 1.3, "interval": 40}, + "class": "drone", + "ballistic": null, + "rcs": 0.4, + "launch_sound": "launch.s400_eject" + }, + { + "kind": "Kh101", + "key": "kh101", + "name": "Х-101", + "model": "airdef:kh101", + "speed": 2.4, + "turn": 0.025, + "cruise": 0.0, + "clearance": 12.0, + "dive": 26.0, + "warhead": 6, + "scale": 1.6, + "engine": {"type": "jet", "up": -0.27, "back": 1.4, "size": 0.6}, + "decoy": false, + "stealth": 1.5, + "sound": {"event": "drone.cruise", "pitch": 1.0, "interval": 50}, + "class": "cruise", + "ballistic": null, + "rcs": 0.05, + "launch_sound": "launch.patriot" + }, + { + "kind": "Kalibr", + "key": "kalibr", + "name": "Калибр", + "model": "airdef:kalibr", + "speed": 2.1, + "turn": 0.025, + "cruise": 0.0, + "clearance": 15.0, + "dive": 26.0, + "warhead": 5, + "scale": 1.5, + "engine": {"type": "jet", "up": -0.2, "back": 1.5, "size": 0.6}, + "decoy": false, + "stealth": 1.3, + "sound": {"event": "drone.cruise", "pitch": 1.2, "interval": 50}, + "class": "cruise", + "ballistic": null, + "rcs": 0.15, + "launch_sound": "launch.patriot" + }, + { + "kind": "Kh22", + "key": "kh22", + "name": "Х-22", + "model": "airdef:kh22", + "speed": 4.0, + "turn": 0.03, + "cruise": 140.0, + "clearance": 40.0, + "dive": 150.0, + "warhead": 7, + "scale": 1.8, + "engine": {"type": "rocket", "back": 1.5, "size": 1.3}, + "decoy": false, + "stealth": 1.4, + "sound": {"event": "drone.rocket", "pitch": 0.8, "interval": 46}, + "class": "supersonic", + "ballistic": null, + "rcs": 1.0, + "launch_sound": "launch.patriot" + }, + { + "kind": "Zircon", + "key": "zircon", + "name": "Циркон", + "model": "airdef:zircon", + "speed": 5.5, + "turn": 0.025, + "cruise": 90.0, + "clearance": 30.0, + "dive": 110.0, + "warhead": 6, + "scale": 1.4, + "engine": {"type": "rocket", "back": 1.5, "size": 1.1}, + "decoy": false, + "stealth": 2.0, + "sound": {"event": "drone.rocket", "pitch": 1.35, "interval": 46}, + "class": "supersonic", + "ballistic": null, + "rcs": 0.4, + "launch_sound": "launch.s400" + }, + { + "kind": "Iskander", + "key": "iskander", + "name": "Искандер-М", + "model": "airdef:iskander", + "speed": 5.0, + "turn": 0.04, + "cruise": 0.0, + "clearance": 0.0, + "dive": 0.0, + "warhead": 8, + "scale": 1.8, + "engine": {"type": "rocket", "back": 1.5, "size": 1.6}, + "decoy": false, + "stealth": 1.6, + "sound": {"event": "drone.rocket", "pitch": 0.7, "interval": 46}, + "class": "ballistic", + "ballistic": 1000.0, + "rcs": 0.6, + "launch_sound": "launch.s500" + }, + { + "kind": "Kinzhal", + "key": "kinzhal", + "name": "Кинжал", + "model": "airdef:kinzhal", + "speed": 7.5, + "turn": 0.03, + "cruise": 0.0, + "clearance": 0.0, + "dive": 0.0, + "warhead": 8, + "scale": 1.7, + "engine": {"type": "rocket", "back": 1.5, "size": 1.5}, + "decoy": false, + "stealth": 2.2, + "sound": {"event": "drone.rocket", "pitch": 0.6, "interval": 46}, + "class": "ballistic", + "ballistic": 1500.0, + "rcs": 0.5, + "launch_sound": "launch.s500" + }, + { + "kind": "Oreshnik", + "key": "oreshnik", + "name": "Орешник", + "model": "airdef:iskander", + "speed": 9.0, + "turn": 0.0, + "cruise": 0.0, + "clearance": 0.0, + "dive": 0.0, + "warhead": 0, + "scale": 2.6, + "engine": {"type": "rocket", "back": 1.5, "size": 2.4}, + "decoy": false, + "stealth": 3.0, + "sound": {"event": "drone.rocket", "pitch": 0.5, "interval": 46}, + "class": "ballistic", + "ballistic": 2600.0, + "rcs": 2.0, + "launch_sound": "launch.s500" + }, + { + "kind": "OreshnikBlock", + "key": "oreshnik_block", + "name": "Орешник: блок", + "model": "airdef:reentry", + "speed": 9.0, + "turn": 0.0, + "cruise": 0.0, + "clearance": 0.0, + "dive": 0.0, + "warhead": 4, + "scale": 1.6, + "engine": {"type": "rocket", "back": 1.0, "size": 1.0}, + "decoy": false, + "stealth": 3.5, + "sound": {"event": "drone.ballistic", "pitch": 0.8, "interval": 9999}, + "class": "ballistic", + "ballistic": null, + "rcs": 0.4, + "launch_sound": "launch.s500" + }, + { + "kind": "OreshnikSub", + "key": "oreshnik_sub", + "name": "Орешник: суббоеприпас", + "model": "airdef:reentry", + "speed": 9.0, + "turn": 0.05, + "cruise": 0.0, + "clearance": 0.0, + "dive": 0.0, + "warhead": 3, + "scale": 1.1, + "engine": {"type": "rocket", "back": 1.0, "size": 1.0}, + "decoy": false, + "stealth": 4.0, + "sound": {"event": "drone.ballistic", "pitch": 1.0, "interval": 9999}, + "class": "ballistic", + "ballistic": null, + "rcs": 0.08, + "launch_sound": "launch.s500" + } + ], + "units": [ + { + "kind": "Patriot", + "key": "patriot", + "name": "Patriot", + "missile": {"kind": "Pac3", "magazine": 4, "min": 10.0, "max": 450.0, "interval": 16}, + "gun": null, + "reload": 220, + "scale": 1.3, + "rest_pitch": 0.66, + "roles": 30, + "family": "trainable", + "parts": { + "body": "airdef:patriot_trailer", + "launcher": "airdef:patriot_launcher", + "pack": null, + "dish": null, + "fixed": [["airdef:patriot_truck", 0.0, -3.0], ["airdef:patriot_radar", 0.0, 3.6]], + "spinner": null + }, + "mount": [0.69, 1.2], + "pivot": [0.0, -0.5, 1.375], + "muzzle": {"type": "patriot"}, + "sensor": 585.0, + "mast": 5.0 + }, + { + "kind": "Gepard", + "key": "gepard", + "name": "Гепард", + "missile": null, + "gun": {"range": 135.0, "track": 195.0, "p": 0.3, "bursts": 160, "shell": 10.0, "slew": [0.22, 0.16]}, + "reload": 160, + "scale": 1.25, + "rest_pitch": 0.0, + "roles": 7, + "family": "gun", + "parts": { + "body": "airdef:gepard_hull", + "launcher": "airdef:gepard_turret", + "pack": "airdef:gepard_guns", + "dish": ["airdef:gepard_dish", 0.95, 0.6, 0.0], + "fixed": [], + "spinner": null + }, + "mount": [1.55, 0.25], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "none"}, + "sensor": 255.0, + "mast": 3.0 + }, + { + "kind": "Pantsir", + "key": "pantsir", + "name": "Панцирь-С1", + "missile": {"kind": "M57e6", "magazine": 12, "min": 25.0, "max": 225.0, "interval": 14}, + "gun": {"range": 90.0, "track": 135.0, "p": 0.25, "bursts": 140, "shell": 9.0, "slew": [0.25, 0.18]}, + "reload": 280, + "scale": 1.3, + "rest_pitch": 0.0, + "roles": 15, + "family": "turret", + "parts": { + "body": "airdef:pantsir_truck", + "launcher": "airdef:pantsir_turret", + "pack": "airdef:pantsir_pack", + "dish": ["airdef:pantsir_dish", 0.85, 0.3, 0.3], + "fixed": [], + "spinner": null + }, + "mount": [1.35, 0.69], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "pantsir"}, + "sensor": 292.0, + "mast": 4.0 + }, + { + "kind": "S400", + "key": "s400", + "name": "С-400", + "missile": {"kind": "M48n6", "magazine": 4, "min": 30.0, "max": 720.0, "interval": 20}, + "gun": null, + "reload": 420, + "scale": 1.5, + "rest_pitch": {"half_pi": -0.02}, + "roles": 30, + "family": "vertical", + "parts": { + "body": "airdef:s400_chassis", + "launcher": "airdef:s400_tubes", + "pack": null, + "dish": null, + "fixed": {"ref": "battery_radar"}, + "spinner": {"ref": "battery_spinner"} + }, + "mount": [0.7, 1.45], + "pivot": {"ref": "tube_pivot"}, + "muzzle": {"type": "grid", "cols": 2, "rows": 2, "size": 7.0}, + "sensor": 936.0, + "mast": 5.0 + }, + { + "kind": "S500", + "key": "s500", + "name": "С-500", + "missile": {"kind": "M77n6", "magazine": 2, "min": 40.0, "max": 1050.0, "interval": 20}, + "gun": null, + "reload": 520, + "scale": 1.6, + "rest_pitch": {"half_pi": -0.02}, + "roles": 24, + "family": "vertical", + "parts": { + "body": "airdef:s500_chassis", + "launcher": "airdef:s500_tubes", + "pack": null, + "dish": null, + "fixed": {"ref": "battery_radar"}, + "spinner": {"ref": "battery_spinner"} + }, + "mount": [0.7, 1.45], + "pivot": {"ref": "tube_pivot"}, + "muzzle": {"type": "grid", "cols": 2, "rows": 1, "size": 9.5}, + "sensor": 1365.0, + "mast": 5.0 + }, + { + "kind": "IrisT", + "key": "irist", + "name": "IRIS-T SLM", + "missile": {"kind": "IrisT", "magazine": 8, "min": 15.0, "max": 600.0, "interval": 10}, + "gun": null, + "reload": 300, + "scale": 1.4, + "rest_pitch": {"half_pi": -0.02}, + "roles": 15, + "family": "vertical", + "parts": { + "body": "airdef:irist_chassis", + "launcher": "airdef:irist_tubes", + "pack": null, + "dish": null, + "fixed": {"ref": "battery_radar"}, + "spinner": {"ref": "battery_spinner"} + }, + "mount": [0.7, 1.45], + "pivot": {"ref": "tube_pivot"}, + "muzzle": {"type": "grid", "cols": 4, "rows": 2, "size": 4.0}, + "sensor": 780.0, + "mast": 5.0 + }, + { + "kind": "Nasams", + "key": "nasams", + "name": "NASAMS", + "missile": {"kind": "Amraam", "magazine": 6, "min": 10.0, "max": 450.0, "interval": 14}, + "gun": null, + "reload": 260, + "scale": 1.3, + "rest_pitch": 0.5, + "roles": 14, + "family": "trainable", + "parts": { + "body": "airdef:nasams_trailer", + "launcher": "airdef:nasams_launcher", + "pack": null, + "dish": null, + "fixed": [["airdef:radar_trailer", 0.0, 3.4]], + "spinner": ["airdef:radar_panel", 0.9, 3.4, 0.6, -0.3] + }, + "mount": [0.62, 0.4], + "pivot": {"ref": "tube_pivot"}, + "muzzle": {"type": "grid", "cols": 3, "rows": 2, "size": 4.0}, + "sensor": 585.0, + "mast": 5.0 + }, + { + "kind": "Buk", + "key": "buk", + "name": "Бук-М2", + "missile": {"kind": "M9m317", "magazine": 4, "min": 20.0, "max": 675.0, "interval": 18}, + "gun": null, + "reload": 400, + "scale": 1.4, + "rest_pitch": 0.0, + "roles": 14, + "family": "turret", + "parts": { + "body": "airdef:buk_chassis", + "launcher": "airdef:buk_turret", + "pack": "airdef:buk_rails", + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [1.55, 0.2], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "rails", "xs": [-9.0, -3.0, 13.0, 19.0]}, + "sensor": 878.0, + "mast": 4.0 + }, + { + "kind": "Tor", + "key": "tor", + "name": "Тор-М2", + "missile": {"kind": "M9m338", "magazine": 8, "min": 8.0, "max": 240.0, "interval": 8}, + "gun": null, + "reload": 300, + "scale": 1.3, + "rest_pitch": 0.0, + "roles": 15, + "family": "vertical", + "parts": { + "body": "airdef:tor_chassis", + "launcher": "airdef:tor_turret", + "pack": null, + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [1.5, 0.0], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "cells"}, + "sensor": 315.0, + "mast": 3.5 + }, + { + "kind": "P1sun", + "key": "p1sun", + "name": "P1-SUN", + "missile": {"kind": "P1sun", "magazine": 8, "min": 5.0, "max": 675.0, "interval": 12}, + "gun": null, + "reload": 240, + "scale": 1.1, + "rest_pitch": 0.35, + "roles": 1, + "family": "trainable", + "parts": { + "body": "airdef:nasams_trailer", + "launcher": "airdef:p1sun_launcher", + "pack": null, + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [0.6, 0.3], + "pivot": {"ref": "tube_pivot"}, + "muzzle": {"type": "grid", "cols": 4, "rows": 2, "size": 3.0}, + "sensor": 0.0, + "mast": 2.0 + }, + { + "kind": "Radar", + "key": "radar", + "name": "РЛС дальнего обнаружения", + "missile": null, + "gun": null, + "reload": 0, + "scale": 1.5, + "rest_pitch": 0.0, + "roles": 0, + "family": "static", + "parts": { + "body": "airdef:radar_truck", + "launcher": null, + "pack": null, + "dish": null, + "fixed": [], + "spinner": ["airdef:radar_panel", 1.2, 1.2, 1.5, -0.25] + }, + "mount": [0.0, 0.0], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "none"}, + "sensor": 2550.0, + "mast": 16.0 + }, + { + "kind": "Depot", + "key": "depot", + "name": "Склад боеприпасов", + "missile": null, + "gun": null, + "reload": 0, + "scale": 1.5, + "rest_pitch": 0.0, + "roles": 0, + "family": "static", + "parts": { + "body": "airdef:depot", + "launcher": null, + "pack": null, + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [0.0, 0.0], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "none"}, + "sensor": 0.0, + "mast": 1.0 + }, + { + "kind": "Tzm", + "key": "tzm", + "name": "ТЗМ", + "missile": null, + "gun": null, + "reload": 0, + "scale": 1.3, + "rest_pitch": 0.0, + "roles": 0, + "family": "truck", + "parts": { + "body": "airdef:tzm_truck", + "launcher": null, + "pack": null, + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [0.0, 0.0], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "none"}, + "sensor": 0.0, + "mast": 2.0 + }, + { + "kind": "Ew", + "key": "ew", + "name": "Станция РЭБ", + "missile": null, + "gun": null, + "reload": 0, + "scale": 1.3, + "rest_pitch": 0.0, + "roles": 0, + "family": "static", + "parts": { + "body": "airdef:ew_truck", + "launcher": null, + "pack": null, + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [0.0, 0.0], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "none"}, + "sensor": 0.0, + "mast": 3.0 + }, + { + "kind": "Light", + "key": "light", + "name": "Прожектор", + "missile": null, + "gun": null, + "reload": 0, + "scale": 1.2, + "rest_pitch": 0.6, + "roles": 0, + "family": "light", + "parts": { + "body": "airdef:searchlight", + "launcher": "airdef:searchlight_head", + "pack": null, + "dish": null, + "fixed": [], + "spinner": null + }, + "mount": [0.85, 0.0], + "pivot": [0.0, 0.0, 0.0], + "muzzle": {"type": "none"}, + "sensor": 0.0, + "mast": 1.5 + } + ], + "missiles": [ + { + "kind": "Pac3", + "model": "airdef:pac3", + "speed": 5.5, + "accel": 0.35, + "turn": 0.3, + "boost": 8, + "cold": 0, + "eject": 0.0, + "eject_drag": 0.0, + "pk": 0.9, + "fuse": 4.0, + "life": 240, + "scale": 1.0, + "flame": 1.0, + "rear": 28.0, + "seeker": 110.0, + "launch_sound": "launch.patriot", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 1.0} + }, + { + "kind": "M57e6", + "model": "airdef:m57e6", + "speed": 5.0, + "accel": 0.5, + "turn": 0.32, + "boost": 5, + "cold": 0, + "eject": 0.0, + "eject_drag": 0.0, + "pk": 0.82, + "fuse": 3.0, + "life": 160, + "scale": 0.85, + "flame": 0.65, + "rear": 28.0, + "seeker": 0.0, + "launch_sound": "launch.pantsir", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 1.12} + }, + { + "kind": "M48n6", + "model": "airdef:m48n6", + "speed": 7.0, + "accel": 0.5, + "turn": 0.22, + "boost": 8, + "cold": 20, + "eject": 2.4, + "eject_drag": 0.07, + "pk": 0.9, + "fuse": 5.0, + "life": 300, + "scale": 1.5, + "flame": 1.4, + "rear": 30.0, + "seeker": 120.0, + "launch_sound": "launch.s400_eject", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 0.88} + }, + { + "kind": "M77n6", + "model": "airdef:m77n6", + "speed": 9.0, + "accel": 0.6, + "turn": 0.22, + "boost": 8, + "cold": 24, + "eject": 2.6, + "eject_drag": 0.07, + "pk": 0.95, + "fuse": 6.0, + "life": 320, + "scale": 1.7, + "flame": 1.6, + "rear": 32.0, + "seeker": 120.0, + "launch_sound": "launch.s500_eject", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 0.8} + }, + { + "kind": "IrisT", + "model": "airdef:irist_missile", + "speed": 7.0, + "accel": 0.5, + "turn": 0.35, + "boost": 6, + "cold": 8, + "eject": 1.8, + "eject_drag": 0.1, + "pk": 0.9, + "fuse": 4.0, + "life": 260, + "scale": 0.95, + "flame": 0.9, + "rear": 28.0, + "seeker": 90.0, + "launch_sound": "launch.s400_eject", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 1.05} + }, + { + "kind": "Amraam", + "model": "airdef:amraam_missile", + "speed": 6.0, + "accel": 0.45, + "turn": 0.3, + "boost": 6, + "cold": 0, + "eject": 0.0, + "eject_drag": 0.0, + "pk": 0.85, + "fuse": 4.0, + "life": 260, + "scale": 0.9, + "flame": 0.9, + "rear": 28.0, + "seeker": 130.0, + "launch_sound": "launch.patriot", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 1.05} + }, + { + "kind": "M9m317", + "model": "airdef:m48n6", + "speed": 6.5, + "accel": 0.5, + "turn": 0.25, + "boost": 8, + "cold": 0, + "eject": 0.0, + "eject_drag": 0.0, + "pk": 0.85, + "fuse": 5.0, + "life": 300, + "scale": 1.1, + "flame": 1.2, + "rear": 32.0, + "seeker": 90.0, + "launch_sound": "launch.patriot", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 0.9} + }, + { + "kind": "M9m338", + "model": "airdef:m57e6", + "speed": 5.5, + "accel": 0.6, + "turn": 0.4, + "boost": 4, + "cold": 8, + "eject": 1.6, + "eject_drag": 0.1, + "pk": 0.9, + "fuse": 3.0, + "life": 160, + "scale": 1.0, + "flame": 0.8, + "rear": 28.0, + "seeker": 70.0, + "launch_sound": "launch.s400_eject", + "voice": {"event": "missile.flight", "gain": 1.0, "pitch": 1.15} + }, + { + "kind": "P1sun", + "model": "airdef:p1sun", + "speed": 2.8, + "accel": 0.12, + "turn": 0.3, + "boost": 4, + "cold": 0, + "eject": 0.0, + "eject_drag": 0.0, + "pk": 0.8, + "fuse": 3.0, + "life": 900, + "scale": 0.8, + "flame": 0.0, + "rear": 10.0, + "seeker": 60.0, + "launch_sound": "drone.lancet", + "voice": {"event": "drone.lancet", "gain": 0.8, "pitch": 1.6} + } + ], + "named": { + "battery_radar": { + "kind": "fixed", + "value": [["airdef:radar_trailer", 0.0, 4.2]] + }, + "battery_spinner": { + "kind": "spinner", + "value": ["airdef:radar_panel", 0.9, 4.2, 0.9, -0.3] + }, + "tube_pivot": { + "kind": "pivot", + "value": [0.0, -0.5, 1.5] + } + } +} diff --git a/docs/ACOUSTICS.md b/docs/ACOUSTICS.md index 4f3aa3f..9b4a3ab 100644 --- a/docs/ACOUSTICS.md +++ b/docs/ACOUSTICS.md @@ -1,7 +1,14 @@ -# Акустика AirDef: разбор Sound Physics Remastered и план переноса (#96, этап 1) +# Акустика AirDef: реализация, проверки и разбор Sound Physics Remastered (#96) -Это результат первого этапа: чтение исходников и план. Кода движка и мода этот этап не меняет. Если код -разойдётся с текстом, прав код. +4 ноября добавлен opt-in массовый offline capture fixture: 100 движущихся двигателей и до +16 импульсных источников, настоящие `Audio::tick_acoustics`/`film_mix`, две одинаковые дорожки, +сброс хвоста при новом capture. Release на Apple M5: p50 3.55 мс, p95 4.30 мс, max 5.57 мс +на 50 мс аудио. Геометрия и сэмплы синтетические; это не полный игровой CPU/GPU/FPS и не MP4. +[Команда, результат и оставшаяся приёмка](CONTINUATION-2026-11-04.md), +[JSON и SHA дорожки](measurements/mass-acoustics-2026-11-04.json). + +Сначала приведён текущий статус реализации. Разбор SPR и первоначальный план ниже сохранены как +история решений; формулировки будущего времени в плане не заменяют текущий статус. При расхождении прав код. - Изучено: https://github.com/henkelmax/sound-physics-remastered, ветка `master`, коммит `453654ece086f40c97712d9016baa4dee7c4f6d2` (2026-10-29), лицензия GPL-3.0 (`LICENSE`). Клон лежит только в @@ -27,9 +34,12 @@ GPL-файлы три: `acoustics/spr_occlusion.rs`, (стадия 2) `acoustics - Дальность 512 блоков (SPR: ~64), счёт клеток, не времени: 40 тыс. клеток на тик встроенно (новый звук, 1 путь, чтобы взрыв не начинался «ярким»), 400 тыс. на задание рабочего потока (9 путей, одно задание за раз); при съёмке всё встроенно, 600 тыс. клеток на тик. Порядок: по громкости, затем по id; сумма голосов в `mix` теперь тоже по id (раньше порядок HashMap делал дорожку - недетерминированной при нескольких голосах). + недетерминированной при нескольких голосах). При выходе за дальность/порог слышимости или потере геометрии микшеру явно + возвращают обычные `gain=1` и воздушный `cutoff` без старой стены; посылки и эхо отзываются, при возвращении считаются заново. - Кеш по ячейкам 2x2x2 источника и слушателя, 4096 записей; сброс по секциям `World.sound_changes` (как `mod_changes`), - по эпохе мира и при переполнении; результат задания, начатого до изменения в его коробке, отбрасывается. + по эпохе мира и при переполнении; результат задания, начатого до изменения в его коробке, отбрасывается. Вставка, замена + и выгрузка чанка меняют residency-счётчик и инвалидируют затронутые секции и старые worker-задания. Трасса, встретившая + Unknown, пересчитывается не позднее чем через 40 тиков, поэтому приехавшая геометрия применяется без движения слушателя. - Воздух: `0.994^расстояние` на верха (EFX по умолчанию, не проверено по OpenAL Soft) — иначе при `acoustics=on` пропадала бы дальность, которую раньше давали `_far`-сэмплы. - Мод: API 21, `render.acoustics()`; при включённой акустике мод играет ближние варианты до 480 блоков (`ACOUSTIC_FAR`), дальше @@ -45,6 +55,43 @@ GPL-файлы три: `acoustics/spr_occlusion.rs`, (стадия 2) `acoustics выстрел 1.5 с на 60 блоках, стена камня в 2 блока: ниже 300 Гц -5.2 дБ (SPR: gain 0.55), 300-1500 -7.4, 1500-5000 -19.7, выше 5000 -40.0 дБ. Тесты: `cargo test -p horizon-client --lib acoustics` (формулы, неизвестное, щель, кеш, инвалидация, детерминизм, рабочий поток) и `audio`. +### Настоящий MP4 с включённой акустикой (4 ноября, pin `73b9d3070`, API 29) + +Пары настоящих экспортов одного и того же повтора, с акустикой и без: кнопка мода «Акустика +(преграды, эхо)», затем тот же `/airdef replay export` (640×360, 30 fps, crf 20), offscreen Metal, +ffmpeg 9.0.1, scratch `HORIZON_HOME`/мир (не сохранения игрока). `HORIZON_ACOUSTICS=1` без +`HORIZON_NO_AUDIO`. В сохранённом мире значение акустики хранится в состоянии мода, поэтому env +задаёт только начальное значение: в проверке её включали и выключали кнопкой. + +Статусы в игре во время и после экспорта: `Акустика: API=true, включено=true, запрос=true, +подготовлено=true, ready`; `Захват этого мода: запрос=true, подготовлено=true, ready`. Контроль +`HORIZON_ACOUSTICS=1` + `HORIZON_NO_AUDIO=1`: `включено=true, запрос=true, подготовлено=false, +awaiting-backend` — ожидаемое отсутствие offline-микшера, не замена звуковому MP4. + +| Сцена | ON: кадры/с | OFF: кадры/с | RMS ON/OFF | пик ON/OFF | L/R corr ON/OFF | centroid ON/OFF | HF>4k ON/OFF | +|---|---|---|---|---|---|---|---| +| поле, 3 события, `sec=4` | 123/4.100 | 124/4.133 | −23.53/−23.26 | −7.18/−5.60 | 0.675/0.627 | 1089/1382 Гц | 0.071/0.095 | +| поле, 1 событие, весь фильм | 351/11.700 | 352/11.733 | −32.18/−16.19 | −12.41/−2.65 | 0.829/0.963 | 1157/1862 Гц | 0.075/0.138 | +| каменный двор 80×100×30 (стены), весь фильм | 430/14.333 | 430/14.333 | −22.06/−20.09 | −5.97/−5.76 | 0.915/0.985 | 232/652 Гц | 0.005/0.039 | + +Все пары — `h264` + AAC 48 кГц stereo, без обрезки звука; файлы `airdef-films/{ac,one,court}_{on,off}.mp4` +в scratch. Уже это показывает, что акустика реально обрабатывает дорожку фильма: в каменном дворе +OFF почти моно (L/R 0.985–1.000), ON декоррелирован (0.915); centroid и доля >4 кГц падают, как и +ожидается от окклюзии/воздушного поглощения; в поле ON тише OFF до 16 дБ по RMS за рельефом. + +Реверб/эхо: в каменном дворе отрезок 9–14 с (после склейки) описывается ON ≈ g·OFF, g = −2.03 дБ +(лучшее совмещение −32 сэмпла = 0.67 мс). Остаток −25.18 dBFS при сигнале −17.61 dBFS (на 7.6 дБ +ниже), centroid 546 Гц, доля >4 кГц 0.009, L/R corr 0.495: низкочастотный диффузный +декоррелированный компонент — это wet-путь (реверб/эхо), а не масштаб сухого сигнала. В открытом +поле send реверба равен нулю по построению (`acoustics/room.rs`: `a field has no send`), поэтому +поле показывает только окклюзию/поглощение, а реверб — только в замкнутой геометрии. + +Ограничения: чистый хвост/RT60 в настоящем MP4 не выделен — в замкнутом зале ON-дорожка была на +30–40 дБ тише (сильная окклюзия), а в открытом дворе длинный сэмпл взрыва (`blast_big`, 15 с; его +21 Гц тон виден в дорожке) маскирует хвост; камеру в комнате с источником и тишиной после него в +этом проходе получить не удалось. Ранние отражения отдельно не измерены. Субъективного +прослушивания не было; 10-минутный экспорт, Windows/Linux и полный бой не проверялись. + ### Стадия 2: эхо (сделано) Что игрок получает: взрыв в ущелье или между домами отвечает настоящим эхо от тех стен, что там есть, с задержкой по @@ -64,8 +111,9 @@ GPL-файлы три: `acoustics/spr_occlusion.rs`, (стадия 2) `acoustics - Срез близких отражений: отвод ближе 30 мс к прямому звуку отбрасывается, до 50 мс плавно входит (иначе стена рядом с игроком даёт гребёнку, а не эхо). Из найденных плоскостей берутся до 6 сильнейших не ближе 40 мс друг к другу. - Кому: `deserves_echo` в `audio.rs`: не петля, не относительный, громкость >= 0.5, дальность (linear) >= 96, длина 0.1-12 с, - прошло меньше 70% звука; не более `ECHO_VOICES = 8` одновременно (самые громкие). Сотня двигателей эха не получает - (петли), сотня выстрелов: восемь самых громких. + прошло меньше 70% звука; не более `ECHO_VOICES = 8` активных наборов (самые громкие). При смене приоритетов старый набор + отзывается и затухает ~50 мс, поэтому во время ротации кратковременно звучат до 16, затем снова 8. Сотня двигателей эха + не получает (петли), сотня выстрелов: восемь самых громких. - Планирование: счёт по клеткам, не по времени. Живьём трассировка идёт на том же рабочем потоке (в задании до 4 трасс и 240 тыс. клеток), кеш по блочным ячейкам источника и слушателя (центры ячеек, 256 записей), сброс по секциям, которых касаются точки опоры отводов, и по эпохе; в съёмке трасса синхронная, 480 тыс. клеток на тик. Микшер получает отводы @@ -147,6 +195,34 @@ GPL-файлы три: `acoustics/spr_occlusion.rs`, (стадия 2) `acoustics получают относительные звуки (интерфейс, музыка) и звуки дальше 512 блоков; эхо-отводы в шину не идут. `gain_hf` слотов, `density`, отражения слота SPR не перенесены. + +### Повторная проверка на закреплённом движке (2026-11-02) + +Движок `209559080` (baseline v0.2.97), macOS arm64/Apple M5. Три ignored export fixtures +действительно выполнены через Acoustics и Mixer, затем проверены `tools/acoustics_check.py`. +Это процедурные тестовые сигналы без аудиоустройства, не субъективное прослушивание игры. + +- Два блока камня: изменение энергии ниже 300 Гц **−5.2 dB**, 300–1500 **−7.4 dB**, + 1500–5000 **−19.7 dB**, выше 5000 **−40.0 dB**. Проверка spectral wall/open прошла. +- Echo fixture: ущелье **158.9 и 242.1 ms**, комната **39.9 ms**, поле — без отражений. + Задержки совпали с расчётными в допуске **1 ms**. +- Reverb fixture: измеренный RT60 ущелья **0.10 s**, тоннеля **0.87 s**, комнаты **1.39 s**; + в поле хвост не обнаружен. Для тоннеля/комнаты прогноз по sends **0.97/1.67 s**. + Проверка использовала текущий допуск анализатора **35%**, а не целевые 15% из раннего плана. + +Экспорт echo и reverb нужно выполнять **в разные каталоги**: оба пишут field.raw/gorge.raw/room.raw, +иначе один эксперимент перезапишет сигналы другого. Пример (из horizon/): + +```sh +mkdir -p /tmp/airdef-echo +HORIZON_ACOUSTICS_OUT=/tmp/airdef-echo cargo test -p horizon-client --lib audio::tests::export_echo_tracks -- --ignored +# Из корня AirDef: +python3 tools/acoustics_check.py --echo-dir /tmp/airdef-echo +``` + +Лицензии/атрибуция включены в `docs/licenses/`; game bundle workflows копируют GPL text, +Horizon MIT notice и THIRD_PARTY_SPR.md в архив рядом с build-identity.json. + ## 1. Как SPR идёт от источника к результату Сигнал: Minecraft вызывает `Channel.play` → `mixin/SourceMixin.play` → `SoundPhysics.onPlaySound` → @@ -454,9 +530,10 @@ GPL-файлы три: `acoustics/spr_occlusion.rs`, (стадия 2) `acoustics ### Стадия 4 (по #96): движущиеся источники и интеграция с #95/cinema -Пересчёт по интервалам (§3.5), `acoustics=on` ⇒ мод играет близкие варианты (§3.7), категории и слушатель реплея. -Зависит от #95 (постоянные источники): ждать его слияния, `audio.rs` сейчас правит другой агент — не начинать -стадию 1 до него, чтобы не конфликтовать по `Channel`/`mix`. +Постоянные источники #95 реализованы: live и replay объявляют движущиеся engine voices; +мод передаёт их host через sound-sources, listener фильма берётся из его камеры. Общий DSP находится +в движке. Необходимая оставшаяся приёмка — массовый живой бой, прослушивание и настоящий MP4 с +проверкой смены listener/мира и бюджета; наличие общего кода не заменяет эти проверки. ## Что оказалось неверным или неточным в тексте #96 diff --git a/docs/AIRCRAFT-MODELS.md b/docs/AIRCRAFT-MODELS.md new file mode 100644 index 0000000..c5aad53 --- /dev/null +++ b/docs/AIRCRAFT-MODELS.md @@ -0,0 +1,34 @@ +# Модели летательных аппаратов (#115) + +Это промежуточные игровые стилизации. У «Ланцета» две пары Х-образных крыльев получили ступенчатые стреловидные кромки, переработан нос и добавлены детали корпуса. У Х-101 изменена стреловидность развёрнутых крыльев; корпус, хвост и подфюзеляжный элемент сохранены. Идентификаторы, положение винта и состав частей не изменены. Число элементов: «Ланцет» 17 → 49, Х-101 22 → 30. Влияние на производительность не измерено. + +| Модель | До | После | +|---|---|---| +| Ланцет | ![Ланцет до](media/aircraft-pass/lancet-before-top.png) | ![Ланцет после](media/aircraft-pass/lancet-after-top.png) | +| Х-101 | ![Х-101 до](media/aircraft-pass/kh101-before-top.png) | ![Х-101 после](media/aircraft-pass/kh101-after-top.png) | + +Дополнительные ракурсы: [Ланцет до](media/aircraft-pass/lancet-before-nose.png), [Ланцет после](media/aircraft-pass/lancet-after-nose.png), [Х-101 до](media/aircraft-pass/kh101-before-nose.png), [Х-101 после](media/aircraft-pass/kh101-after-nose.png). + +## Проверка игрового инстанса + +| До | После | +|---|---| +| ![Инстанс до](media/aircraft-pass/lancet-instance-before-nose.png) | ![Инстанс после](media/aircraft-pass/lancet-instance-after-nose.png) | + +[Ракурс сверху до](media/aircraft-pass/lancet-instance-before-top.png) · [ракурс сверху после](media/aircraft-pass/lancet-instance-after-top.png). + +Проверочный мод `tools/model-preview` выставляет модель через игровой `render.set-models` с `glow=false` и HD-освещением в открытом воздухе. На этих кадрах видны цвет, крылья и нос; это статичный инстанс, полёт и анимация не проверены. Различия яркости наклонных сегментов на старых кадрах исправлены в #123. + +## Исправление нормалей (#123) + +| До | После | +|---|---| +| ![Осевые нормали](media/aircraft-pass/lancet-instance-normal-before.png) | ![Геометрические нормали](media/aircraft-pass/lancet-instance-normal-fix.png) | + +[Сверху после исправления](media/aircraft-pass/lancet-instance-normal-fix-top.png) · [ночь после исправления](media/aircraft-pass/lancet-instance-normal-fix-night.png). + +Пара «до/после» снята в одном scratch-мире, с одинаковыми координатами камеры, временем и погодой на Apple M5/Metal. Динамический block-model путь теперь вычисляет нормаль из неколлинеарных вершин квада и использует прежний осевой fallback только для вырожденной геометрии. Unit-тест покрывает оба знака наклона 45°, другие углы, осевые и вырожденные квады. Массовая производительность и полёт этой проверкой не измерялись. + +Остальные снимки выше показывают модели как установленные блоки в изолированных мирах Horizon, offscreen, без звука, при времени 6000 и ясной погоде, со светящейся платформой и одинаковыми координатами камеры. В пакетах одинаковы код и остальные ресурсы; для варианта «до» заменены два JSON модели на версии d410e2c. Оба блока после установки проверены через `block`. Ландшафт и состояние подгрузки различаются; снимки не ретушированы. Непрозрачный установленный блок местами выглядит почти чёрным, поэтому его снимки нельзя использовать для оценки освещения игровых инстансов. В исходной серии ночное освещение не проверялось; ночной кадр Lancet после #123 представлен выше. Массовая производительность не проверена; художественная приёмка остаётся открытой. + +Редактируемые источники — `airdef-mod/source-assets/assets/airdef/models/block/{lancet,kh101}.json`. Для переэкспорта запустите `python3 tools/aircraft_models.py`, затем `python3 tools/gen_models.py` для установки ресурсов и пересчёта габаритов. diff --git a/docs/BATTERIES-AND-COLD-LAUNCH.md b/docs/BATTERIES-AND-COLD-LAUNCH.md new file mode 100644 index 0000000..548cdba --- /dev/null +++ b/docs/BATTERIES-AND-COLD-LAUNCH.md @@ -0,0 +1,198 @@ +# Раздельная батарея и холодный пуск + +## Из исследования в игру + +Вместо «всё на одной машине» добавлена конфигурация с отдельными пусковыми и общими РЛС. +Высокоуровневое разделение обзорной и огневой РЛС соответствует публичным описаниям системы; +это не полная модель конкретной штатной батареи. + +| Публично описываемый элемент С-400 | Его смысл | Игровая адаптация | +|---|---|---| +| 91Н6: обзор/обнаружение | Общая воздушная картина | `radar_search`: больший игровой обзор, передача наблюдений | +| 92Н6: сопровождение/управление огнём | Отдельный радар огневого комплекса | `radar_track`: более частые собственные измерения, поддержка нескольких ПУ | +| Дополнительная 96Л6 и антенные мачты | Дополнительные средства обнаружения | `radar_height`: условная мачтовая роль, улучшенный игровой радиогоризонт; это не копия 96Л6 | +| Командный пункт 55К6 | Управление и обмен данными | Пока абстрагирован существующей сетью и распределением целей, отдельной машины КП нет | +| Пусковая с ТПК | Боекомплект и физический пуск | `s400_launcher` / `s500_launcher`, без навесной РЛС | + +Источники для архитектуры: оригинальная техническая публикация +[Bundesheer, «Radars für Raketenabwehr»](https://www.bmlv.gv.at/pdf_pool/publikationen/7_rosmann.pdf) +разделяет обзорный 91N6 и огневой 92N6; статья об С-400 в официальном +[Memorial de Artillería 174/2, Ministerio de Defensa](https://publicaciones.defensa.gob.es/media/downloadable/files/links/m/e/memorial_artilleria_174-2.pdf) +перечисляет КП, радары, пусковые и дополнительные средства. Это публичные обзоры, +не эксплуатационная документация изготовителя. У чисел времени, дальности и качества в моде +собственная игровая шкала; из этих публикаций они не калибровались. + +Для наглядности холодного старта найден официальный выпуск +[«С-400. Секрет ракеты», «Военная приёмка»](https://rutube.ru/video/37a2ef7d5e83ffa9ec4ba222bfd8ce5f/) +с участием завода «Авангард». Доступная страница выпуска не содержит стенограммы; +форма шарнира крышки и длительность игровых стадий не объявляются воспроизведением механизма прототипа. +Для С-500 применяется стилизация существующего игрового профиля, без утверждения о механике всех реальных ракет. + +## Как собрать + +Через N → «Разместить элементы батареи…» можно расставить элементы в разных местах. +Командные эквиваленты: + +```text +/airdef deploy radar_search +/airdef deploy radar_track +/airdef deploy s400_launcher +/airdef deploy s500_launcher +/airdef deploy radar_height +/airdef battery +``` + +Нумерацию существующей техники показывает `/airdef battery`. Чтобы переоборудовать её: + +```text +/airdef battery <номер С-400 или С-500> launcher +/airdef radar <номер РЛС> search +/airdef radar <номер другой РЛС> engagement +``` + +Можно оставить одну многофункциональную РЛС (`multi`) и несколько пусковых. +Поисковая/мачтовая РЛС даёт подсказку, но не разрешает огонь сама. РЛС сопровождения принимает +подсказку и должна **сама измерить** цель в своей игровой досягаемости и с учётом рельефа. +Чужой трек не превращается в собственное измерение. После этого связанные пусковые используют +общую картину и существующее распределение целей. Если РЛС сопровождения уничтожена, свёрнута, +потеряла связь или её измерение устарело, новые пуски прекращаются. Уже вылетевшие ракеты +продолжают штатную игровую логику связи/памяти/самонаведения. + +Старые миры и обычные `/airdef deploy s400` / `s500` сохраняют сборную конфигурацию `integrated`. +Режимы, роли, боезапас и использованные контейнеры сохраняются с миром и сценариями. +У прежнего игрового С-500 был магазин на четыре ракеты при двух нарисованных ТПК; +теперь он согласован с двумя физическими ячейками. При загрузке старой расстановки +его загруженный остаток ограничивается новой вместимостью, как при обычном восстановлении боезапаса. + +## Что видно при пуске + +1. Подготовка резервирует конкретную ячейку. Отдельная крышка ТПК открывается; ракета ещё не расходуется. +2. При потере цели или неисправности/сворачивании носителя до выброса подготовка отменяется. +3. Ракета выбрасывается из фактического muzzle. Боекомплект уменьшается один раз; использованный ТПК остаётся открытым. +4. Выброс идёт без двигателя: ракета поднимается и линейно теряет скорость под газовым сопротивлением. + Длительность считается от выброса, а не от открытия крышки: у С-400 (`M48n6`) 20 тиков — 1,00 с, + у С-500 (`M77n6`) 24 тика — 1,20 с, у IRIS-T и Тора (`IrisT`/`M9m338`) 8 тиков — 0,40 с. + В этой фазе нет факела, непрерывного звука двигателя, моторного света и powered smoke; остаётся + только отдельный газовый хлопок/клуб. +5. Зажигание — отдельное событие в текущей точке ракеты, а не у оставленной ПУ: там же появляются + вспышка, факел, звук и запись `MotorIgnition`. Оно наступает после `cold` тиков выброса. +6. После зажигания начинается departure: тяга плавно нарастает от 45% к 100%, а допустимый темп + поворота — от 20% к полному за `boost` тиков. Полное наведение (включая самонаведение и облёт + рельефа) включается только при `age >= cold + boost`; при увеличении `cold` оно не может + включиться раньше мотора. Максимальная скорость, `pk`, `turn` и правила столкновений не менялись. + +Это действует для зенитных С-400/С-500 и подготовленного ударного RM48U у С-400. +У ручного удара носитель и ячейка резервируются до пуска; изменение состояния отменяет подготовку +без расхода. RM48U использует ту же 20-тиковую хореографию: неосвещённый выброс, зажигание в текущей +точке, затем разгон; насчитанное время полёта включает выброс, а записанный дым начинается у точки +зажигания. Задержанный ручной приказ не возобновляется после загрузки мира: незапущенный боеприпас +остаётся в носителе. Ракеты с рельсовым (горячим) пуском тоже получили плавное нарастание тяги за +свой `boost`, но не холодный выброс; электрический P1-SUN сохраняет постоянный разгон и не получает +ракетную струю. + +В записи сохраняются конфигурация машины, использованные ячейки и положение крышки; +повтор восстанавливает их по своей временной шкале. Записанный дым начинается у точки зажигания, +а не рисуется задним числом от горловины контейнера. + +## Ограничения + +Роли РЛС и мачта пока используют общую модель машины, с разным поведением/отображением антенны. +Отдельные точные модели 91Н6/92Н6/96Л6, машина КП, ручная принадлежность к батарее, отдельные +каналы сопровождения и кабельное снабжение пока не добавлены. Подробные режимы и параметры реальных +комплексов не моделируются. В следующей серии добавлены конечная работа двигателя/coast (#120), +доставка RM48U через ТЗМ (#118), переключение излучения (#121) и укрытия от непрямого blast (#119). +Текущий объём и ограничения описаны в [техническом журнале](CODEX-HANDOFF.md). + +## Проверка в игре и тестами + +Историческая контрольная точка батареи и команд — `966b52f`: 169 тестов ядра и 4 сценария +(7 ignored), 53 теста адаптера, проверка границы с ядром и ещё 4 сценария; настоящий WASM-пакет +прошёл 21 шаг `wasm_contract.toml`, генератор моделей не нашёл различий. + +Контрольная точка #129 — `4b013fb7f30dbaa87a266b27a31911937fc523b4` (ветка +`agent/issue-129-cold-launch`): core release **206 + 8 сценариев** (8 ignored), адаптер +**67 + 1 boundary + 5 сценариев**, настоящий WASM **30 шагов**, `gen_catalog.py --check` без +расхождений, `gen_models.py --check` без различий, `test_gen_assets.py` — 10 тестов. + +Отдельные тесты проверяют открытие до расхода, отмену при повреждении, однократное списание при +выбросе, отсутствие голоса/факела до зажигания, записанное зажигание через `cold` тиков после +выброса, плавные ограничения скорости и поворота для всех профилей, а также RM48U без powered +эффектов до зажигания. Тест повтора проверяет перемотку состояния крышек и конфигурации ПУ туда и +обратно и отсутствие дыма до записанного зажигания. + +Сравнительная выборка #129 (те же seed, что у сетки #120; 6 вариантов × 8 seed, 2208 целей на +сторону): сбито 1988 → 1939, прорывов 216 → 258, ракет 3461 → 3467; `energy_lost` 0 → 0, +`expired` 1 → 0, сорванных о рельеф 2 → 4. Близкие (`near200`) 318 → 311 сбитых, дальние +(`far1500`) 338 → 334, холмы (`hills20`) 334 → 319. Отдельные 12 seed `SAMS=20 SIZE=12`: +203 → 201 сбитых, 1 → 3 прорыва, 355 → 360 ракет. Это ожидаемое следствие более длинного старта, +а не скрытая компенсация: `pk`, максимальные скорости и столкновения не менялись. Данные: +[сетка](measurements/issue-129-grid.csv), [рейды](measurements/issue-129-balance.csv). + +Ниже — исходные кадры крышек из изолированного тестового мира Horizon, Metal, 2560×1440. +На первом четыре малые крышки ТПК закрыты; на втором одна поднята, остальные закрыты. +Большая прямоугольная панель позади труб — навесная РЛС конфигурации `integrated`. + +![Четыре закрытые крышки ТПК](media/cold-launch/s400-closed.png) + +![Одна использованная ячейка с поднятой крышкой](media/cold-launch/s400-one-cell-used.png) + +Для этих кадров использовалась промежуточная сборка SHA-256 +`3fae616902563b01e6463a32687c062d5983c4e94bd357f8f8129744d859f4d7`; +геометрия крышек и их преобразования совпадают с опубликованной версией. +Сохранение после пуска содержит 3 оставшиеся ракеты С-400 и маску использованной ячейки `1`. + +### Парная съёмка #129: до и после + +Оба ролика сняты на одном и том же scratch-мире `ColdLaunch129` (плоский тестовый рельеф, +`--creative`), одном сохранении мира, одном сценарии `pair129` и одном seed +`965460218386186365`. Сценарий: С-400 в точке `1.5 72 8.5`, роли `31`, один Х-22, налёт с 900. +Оба прогона — один и тот же клиент/движок `278f356aa` (API30), одни ресурсы, Metal/offscreen, +`HORIZON_NO_AUDIO=1`, одна команда режиссёра +`/airdef replay export <имя> best fps=30 size=1280x720 crf=16 sec=24`. + +| | До (`dc36d67`) | После (`4b013fb`; hmod `00f0209`) | +|---|---|---| +| `.hmod` SHA-256 | `2d828aa8…` | `ca3bdd30…` | +| исходный MP4 | 17 268 968 Б, 403 кадра | 17 832 718 Б, 412 кадров | +| длительность | 13,433 с | 13,733 с | +| поток | h264 1280×720 30 fps + AAC 48 кГц | то же | +| план «Пуск» | кадры 94–151 | кадры 94–160 | +| первое освещение центра | кадр 101 = 0,233 с | кадр 121 = 0,900 с | +| `cold` профиля | 5 тиков = 0,25 с | 20 тиков = 1,00 с | + +«Первое освещение» — прокси зажигания/факела: первый кадр, где средняя яркость центрального +окна 100×120 px (`x 590..689`, `y 300..419`) превышает 100/255. До (`dc36d67`) это кадр 101, через 7 кадров +после начала плана «Пуск» (кадр 94) — 0,233 с при `cold=5` (0,25 с). После — кадр 121, через +27 кадров — 0,900 с при `cold=20` (1,00 с); к кадру 124 окно выходит на устойчивую яркость +≈198. Разница 0,667 с совпадает с разницей `cold` 15 тиков = 0,75 с в пределах 2–3 кадров +(30 fps). Аудиодорожка даёт тот же сдвиг: RMS (окно 50 мс) впервые превышает 0,03 на 3,45 с +до и 4,15 с после при старте плана 3,10 с, то есть через 0,35 и 1,05 с; это атака звука, а не +кадр зажигания. Полные числа и хэши: +[issue-129-pair.json](measurements/issue-129-pair.json). + +Оба MP4 лежат в `docs/media/cold-launch`: [до](media/cold-launch/cold-launch-129-before.mp4) и +[после](media/cold-launch/cold-launch-129-after.mp4), в перекодировке h264 CRF26 для хранения; +число кадров, 30 fps, длительность и AAC сохранены. Исходные файлы с CRF16 и их SHA-256 +записаны в JSON. +Это **replay/export**, не live-видео: живые кадры в MP4 не писались (`--offscreen` не даёт окна +для сторонней записи, встроенной live-записи у мода нет). Звук сведён офлайн, живое +прослушивание не выполнялось. + +Парные кадры (слева — до, справа — после), один и тот же кадр фильма: + +![Общий план перед пуском, кадр 60](media/cold-launch/cold-launch-129-pair-general.png) + +![0,53 с после начала плана «Пуск»: до уже с факелом, после ещё без него](media/cold-launch/cold-launch-129-pair-0.5s.png) + +![1,0 с: у «после» факел уже горит (начало — кадр 121)](media/cold-launch/cold-launch-129-pair-1.0s.png) + +![1,53 с: обе ракеты под тягой](media/cold-launch/cold-launch-129-pair-1.5s.png) + +**Ограничение приёмки.** Агент, выполнивший прогон, не имеет ввода изображений и не может сам +оценить композицию, читаемость и красоту кадров. Проверены факт и параметры съёмки, одинаковый +старт плана «Пуск» (кадр 94), яркость центра и её сдвиг по времени; парные кадры приложены для +человеческого просмотра. Кадры до/после не пиксельно идентичны: это два отдельных живых прогона, +камера общего плана движется, листва/трава анимированы, поэтому в предпусковых кадрах остаётся +разница в несколько уровней яркости. Совпадают мир, сохранение, сценарий, seed, метод камеры и +команда экспорта, а не каждый пиксель. Отдельный очень крупный статичный план выброса у самой ПУ +не снят; план «Пуск» — боковой с 46 блоков. Это не полная визуальная приёмка #129. diff --git a/docs/CODEX-HANDOFF.md b/docs/CODEX-HANDOFF.md new file mode 100644 index 0000000..376e331 --- /dev/null +++ b/docs/CODEX-HANDOFF.md @@ -0,0 +1,646 @@ +# Передача разработки AirDef + +Это журнал фактически сделанных изменений и ограничений проверки, а не список обещанных функций. +Рабочая ветка `kimi-work`; issues не закрывать, публиковать комментарии с коммитом и проверками. +Движок — отдельный `chexit/horizon-airdef`, ветка `airdef`, подключён точным gitlink `horizon/`. + +## Текущее состояние: 4 ноября 2026 + +Последнее продолжение пяти задач: [CONTINUATION-2026-11-04.md](CONTINUATION-2026-11-04.md). +Движок `57f8b2853c4443f7e3ff5701600cbb262b53118f`, API29. В этом файле ниже сохранён +исторический журнал предыдущих серий; старые SHA и числа тестов не заменяют новую контрольную точку. + +След #91 продолжен: `catalog/specs.json` — единственная копия числовых строк дронов, ракет и +комплексов; `python3 tools/gen_catalog.py` генерирует `specs_generated.rs` и +`unit_specs_generated.rs`, `--check` (CI) валит расхождение. Числа не менялись; сверка с литералами +подтвердила 1.6 как фактический голос P1-SUN (недостижимый `P1sun => 1.7` в старом match не в счёт); +на это добавлена Rust-регрессия. Остаток: варианты комплексов и игровая приёмка. + +### Продолжение #129: стадии холодного пуска и разгон (6 ноября 2026) + +Ветка `agent/issue-129-cold-launch`, кодовый коммит `4b013fb7f30dbaa87a266b27a31911937fc523b4` +(документация, измерения и кадры — следующим коммитом этой же ветки). +Игра/движок не менялись: pin `278f356aa604fa21c2d15d0081823bbe0f891df2`, API30. Push не выполнялся, +issues не закрывались. + +**Контракт стадий.** У `MissileSpec` появились `eject` и `eject_drag`; `cold` — тики физического +выброса, `boost` — тики departure. `guided(age) = age >= cold + boost`; основная ветка наведения, +самонаведение и облёт рельефа больше не могут включиться до зажигания (раньше было `age >= boost`, +что при большом `cold` включало наведение до мотора). Зажигание — по-прежнему `age > cold`; +событие `MotorIgnition`, звук, факел и голос используют текущую позицию ракеты. `departure_ramp` +растёт 0→1 за `boost`; темп поворота в departure — `turn·(0.2 + 0.8·ramp)`. Тяга ракетных +профилей — `accel·(0.45 + 0.55·ramp)`, на guidance — полная. P1-SUN (`flame=0`) сохраняет +полный разгон без ракетной струи. + +**Профили.** `M48n6`: `cold=20`, `eject=2.4`, `eject_drag=0.07` — 1,00 с; `M77n6`: `cold=24`, +`eject=2.6`, `eject_drag=0.07` — 1,20 с; `IrisT`/`M9m338`: `cold=8`, `eject=1.8/1.6`, +`eject_drag=0.1` — 0,40 с. Рельсовые профили (`cold=0`) получили только spool тяги; ранний +поворот на рельсовом пуске был проверен и отменён: он уводил Pac3 в землю на холмах и валил +`an_interceptor_over_rolling_land_does_not_hit_the_ground`. `speed`, `accel`, `turn`, `pk`, +`fuse` и правила столкновений не менялись. + +**RM48U.** `EJECT_TICKS` 5 → 20. Выброс неосвещённый и замедляется; зажигание на `EJECT_TICKS`, +факел/звук/голос с этого момента; насчитанное время полёта включает выброс; дым в записи +начинается от точки зажигания, а не от горловины. Live, repeat и export читают одно и то же +состояние `lit`/записанное `MotorIgnition`. + +**Проверки.** core release **206 + 8 сценариев**, 8 ignored; адаптер **67 + 1 boundary + +5 сценариев**; настоящий `.hmod` — **30 шагов** `wasm_contract.toml`; `gen_catalog.py --check` и +`gen_models.py --check` без расхождений; `test_gen_assets.py` — 10 тестов; `git diff --check` чист. +Новые регрессии: стадии по порядку и отсутствие powered-эффектов до зажигания; `guided` не раньше +`cold` для всех профилей; непрерывность скорости/поворота и throttled первый powered тик; P1-SUN +без факела; записанное зажигание через `cold` тиков и `missile_is_lit` по нему; RM48U без +голоса/факела до зажигания и ровно один звук зажигания. + +**Баланс.** Сетка #120 (6 вариантов × 8 seed, 2208 целей на сторону, baseline `dc36d67`): +сбито 1988 → 1939, прорывов 216 → 258, ракет 3461 → 3467; `near200` 318 → 311, `far1500` +338 → 334, `hills20` 334 → 319; `energy_lost` 0 → 0, `expired` 1 → 0, scrub 2 → 4. +`SAMS=20 SIZE=12`, 12 seed: 203 → 201 сбитых, 1 → 3 прорыва, 355 → 360 ракет. Отдельные CSV: +[сетка](measurements/issue-129-grid.csv), [рейды](measurements/issue-129-balance.csv). +Это намеренное следствие более длинного старта; скрытого повышения `pk` или максимальной скорости нет. + +**Парная съёмка.** Один scratch-мир `ColdLaunch129`, одно сохранение, сценарий `pair129`, +seed `965460218386186365`, один клиент/движок API30, один режиссёр +(`/airdef replay export … best fps=30 size=1280x720 crf=16 sec=24`), Metal/offscreen. +Сценарий: С-400 `1.5 72 8.5`, роли `31`, один Х-22 с 900; мод до — `dc36d67`, после — `4b013fb`. +Оба MP4: h264 1280×720 30 fps + AAC 48 кГц; до — 403 кадра/13,433 с, после — 412/13,733 с. +План «Пуск» стартует на кадре 94 в обоих; первое освещение центра (окно 100×120, порог 100) +до — кадр 101 (0,233 с после старта, `cold=5` = 0,25 с), после — кадр 121 (0,900 с, +`cold=20` = 1,00 с); к 124 кадру устойчиво ≈198. Это подтверждает сдвиг холодной паузы на +≈0,67 с между сборками. MP4 в `docs/media/cold-launch` перекодированы в CRF26 для хранения; +исходные файлы, их SHA-256, ffprobe и логика измерения — в +[issue-129-pair.json](measurements/issue-129-pair.json). **Ограничение:** у агента нет ввода +изображений; проверены параметры, старт плана, яркость центра и парные кадры, но композицию и +красоту кадров человеком ещё нужно оценить. Предпусковые кадры не пиксельно идентичны (живой +прогон, движение камеры, анимация листвы); совпадают мир/сейв/сценарий/seed/метод камеры. +Отдельный very-close статичный план и live-MP4 не сняты (`--offscreen` без окна). Это не полная +визуальная приёмка. + +### Продолжение #122 (второй налёт из UI) + +Точный лог Windows: `stopped in UI event (fuel budget 1000000000, remaining Some(0))`, +backtrace `known_units ← order_raid ← launch_wave ← ui_select` — другой класс, чем тик +`1f28f83`. Причина: `order_raid` пересчитывал снимок известных комплексов на каждом дроне, +а сам снимок был квадратичен по журналу и группировке. Исправление: один снимок на приказ +(+ O(журнал+группировка) через множества, `price_grid_on` переиспользует свой снимок); +семантика целеуказания и выбора носителей не менялась, сверка с прежним предикатом в +нативном тесте, `/stats` в WASM совпал побайтово. Лимиты не трогали, HD/запись не отключали, +размер волны не ограничивали. + +Воспроизведение в настоящем WASM (измеренный fuel): до — второй приказ 111 дронов исчерпывает +ровно 1e9 в событии (failures=1), тот же приказ 11 дронов — 247805865; после — тот же сценарий +целиком: max command 32946177/1e9, tick 236581288/300M, ошибок 0. Диагностический гость +(`trigger.patch` вызывает обработчик `ui_select(BTN_LAUNCH)`) и точный сценарий: +[issue-122-ui-fuel](measurements/issue-122-ui-fuel/summary.json). Воспроизвести удалось только +с разнесённой группировкой: стек в одной точке гибнет от одной боеголовки (`known=0`, приказ +дёшев) — у пользователя силы дожили до второго приказа. + +Закреплённые регрессии: нативные «эквивалентность снимка» и «второй умный приказ с толстым +журналом принят и запущен»; тяжёлый WASM fixture расширен двумя умными приказами через +`airdef preset` + `airdef raid` (функция кнопки ПУСК) — 4 принятых приказа, SHA fixture +обновлён; лёгкий контракт 30 шагов с `preset` и отказом пустой волны. Новая команда +`airdef preset ` — script-эквивалент кнопки готового состава. + +Последний общий прогон: core release **195 + 8 сценариев** / 7 ignored; адаптер **65 + 1 +boundary + 5 сценариев**; лёгкий WASM **30 шагов**; тяжёлый WASM **283 шага** (tick max +182413686/300M, command max 6939810/1e9, failures=0), отчёт +[flat-wasm-raid-budget](measurements/flat-wasm-raid-budget.json); 6 тестов отчёта budget +и build-тест прошли. Ни одно issue не закрыто; пуш не выполнялся. + +### Контрольная точка 3 ноября + +Код предыдущих четырёх задач интегрирован до `6e2d661`. Продолжение #102/обратного вывоза +опубликовано до `aab3e78`; последнее продолжение расследования налётов — `2932364`. +Текущий движок — `f95f03c08ec2916a0e5bd3b2d4a2a790e7937b47`, API 27. +README описывает доступное поведение; `IMPLEMENTATION-STATUS.md` отделяет реализацию от приёмки. +Ни одно issue не закрыто. Участники: Codex (интеграция, #119, ревью, документация) и три +GPT-6.1 Terra (энергетика, типизированная доставка, излучение). Новые числа — только игровые. + +| Issue | Опубликованные контрольные коммиты | +|---|---| +| #119 | `c96676f`: укрытия, read-only geometry, четыре проверки | +| #118 | `c775f31`, `952a10f`: доставка ТЗМ, возврат снятой ракеты ПВО, реальное движение и save/restore | +| #121 | `26b258f`, `0df225a`, `88f9405`, `9e1e0f5`, `b377a5d`, `ce76f20`: излучение, UI/save/replay, наблюдения, last-known SEAD | +| #120 | `143d9b2` → `e38ca0d`, затем `a7b8cb9`: конечное горение, coast, swept impact, диагностика, звук, исправленное ветвление фаз | +| WASM | `6e2d661`: проверка On/Off и запрета управления отсутствующим радаром, 24 шага | + +Для обновления существующей установки сначала `git switch kimi-work`, затем `git pull --ff-only`, +`python3 tools/airdef.py all`, `python3 tools/airdef.py play`. `all` обновляет точный сабмодуль, +собирает игру/мод и устанавливает пакет. На Windows обычно используется `python` вместо `python3`. + +### Продолжение #102 и обратного вывоза RM48U + +- `d3378b3`: `fork()` синхронизирует URL из `.gitmodules` даже на fast path совпавшего pin. + Реальная Git-регрессия воспроизводит старый upstream URL и новый коммит, отсутствующий в локальном clone. + Это исправляет наблюдавшееся на Windows `upload-pack: not our ref 86b1014…`; пользователь подтвердил + ручное исправление через `git submodule sync --recursive` до публикации автоматизации. +- `bb931ab`, `e9fbaba`, `b0085cb`: `ground-unload` вдали от склада резервирует одну RM48U и вызывает + ТЗМ. `ground-cancel` отменяет ещё ожидающий вывоз/доставку; после забора груз продолжает путь на склад. + Проверены физическое движение и полный save/restore до забора и после забора, без телепортации запаса. +- `910f02c`: потеря зарезервированной ракеты вместе с ячейками сбрасывает заявку на вывоз. + Иначе можно было создать сохранение, которое loader сам отвергал как несовместимое состояние. +- Двигатель: `f4c1131d3`, затем `c89c78c47` опубликованы в `horizon-airdef:airdef` до обновления gitlink. + API27 добавляет acoustics-status и capture-owner-status, scoped к запуску мода и поколению мира. + Остановка capture проверяет владельца; смена мира снимает незапущенный запрос или посылает cancel. +- `910f02c`: команда `/airdef capabilities`; дальний звуковой fallback сохраняется до подготовки + акустического пути, экспорт принимает только своё подтверждённое состояние; Arming ждёт до 600 тиков. + `prepared` — milestone хоста, не GPU fence, не результат каждой акустической трассы и не готовый MP4. +- `4dddea2`, `8d5d0d1`: opt-in `test-budgets`, отдельный ручной workflow и JSON provenance; + тесты ошибок сборки, stale identity и ненулевого runtime exit. Native success не заменяет actual WASM. +- `aab3e78`: repo-local packer использует target-cache закреплённого движка; `CARGO_TARGET_DIR` + не переопределяется для сборки самого мода и не уводит `.hmod` в чужое дерево. + +Проверки объединённого состояния: core release **190 + 4 сценария**, 7 ignored; +адаптер **60 + 1 boundary + 4 сценария**; лёгкий WASM **26 шагов**, тяжёлый **69 шагов**; +Git-регрессия и три теста failure report прошли. Исходный профиль запрашивал 64 ПВО + 160 целей, +602 tick / 300 frame; JSON фиксирует clean `aab3e78`, pin API27 и совпадающий build identity. +Полные данные: [budget report](measurements/api27-wasm-budget.json). +**Исправление оценки покрытия:** второй запрос исходного 69-шагового сценария отклонялся во время +подготовки первого. Его успешный exit не подтверждает 160 запущенных или одновременно летящих целей. +В архивный JSON добавлена поправка; исправленный сценарий проверяет ответы. Для настоящего stress +также нужны ответы render-world: прежний sandbox их не давал и часто проверял лишь ожидание земли. + +Дополнительно два свежих профиля игры на Metal/no-audio: smoke off не ready, HD smoke ready; +при HORIZON_ACOUSTICS=1 + HORIZON_NO_AUDIO=1 акустика enabled/request=true, prepared=false, +awaiting-backend. Выход через quit подтверждён. Это не прослушивание, не benchmark и не новый MP4. +Правила API, пределы проверок и команды: [ENGINE.md](ENGINE.md). + +## Двойные налёты и плотность следов + +Перед продолжением читать [RAID-STABILITY.md](RAID-STABILITY.md): два соответствующих класса +отключения теперь воспроизведены и исправлены — тик (`1f28f83`) и событие UI на втором +приказе (#122, `order_raid`/known_units); приёмка конкретного Windows-сеанса остаётся. +Текущий исправленный WASM stress проверяет две принятые волны, обслуживаемую плоскую геометрию +и 184 воздушные цели в конкретный момент; новый [JSON](measurements/flat-wasm-raid-budget.json) +заменяет прежнее недостаточное доказательство покрытия. Лимиты fuel/memory не увеличивались. +`86e1d272e` в движке улучшает callback/fuel/error-chain diagnostics; `ed7615d36` добавляет только +необязательную flat-world среду тестов и отказывает неожиданным ошибкам команд в Script. +Основная клиентская логика сцены этим test helper не заменяется. + +`692ae77`: HD-треки ракет оптически плотнее на 12% через общий `rocket()` и cruise style. +SAM/RM48U, крылатые/сверхзвуковые/баллистические треки покрыты; сегментов, радиусов и времени не добавлено. +Пожары и дымка винтовых дронов не менялись. Обычные ванильные fallback-частицы этим множителем не меняются. +Пять существующих smoke-тестов прошли; прирост FPS и приёмка новой плотности на Windows не заявлены. + +Последний общий прогон: core release190 + 6 сценариев, 7 ignored; +адаптер60 + 1 boundary + 4 сценария; лёгкий WASM26; тяжёлый WASM271; 5 тестов отчёта. +Нативные overlap-тесты — `airdef-core/tests/scenarios.rs`: +два стандартных массовых пресета и два налёта с разделением «Орешников», оба со встроенной записью. + +Продолжение по пользовательскому HD-отключению: `1f28f83` заменяет полный `known.retain` при +каждом взрыве на удаление прежнего квадрата; семантическая эквивалентность проверена. +В диагностическом WASM max-menu ×2 раньше был trap tick1835 на 300M; после фикса полный +3602-tick прогон прошёл, max135273690. Гость отличается от baseline только временным trigger hook +состава меню и исправлением. Это измерение fuel, не прирост FPS. Артефакты/ограничения — в RAID-STABILITY. +`cc48302` добавляет публичный native max-menu regression; `3b470dd` закрепляет опциональный +engine `test --fuel-profile` (не новый SDK API и не увеличение лимитов). + +`cbd6067` исправляет сообщение о пустом соседнем складе, показывает типизированные запасы на M, +уточняет подсказку разведки и убирает устаревшую строку README о якобы отсутствующей доставке. +[GROUND-STRIKES.md](GROUND-STRIKES.md) — инструкция с различием номеров своего С-400/склада и вражеской ПУ. +Default RM48U=0; `ground-stock` задаёт остаток, `ground-load` грузит одну; поздний трек не даёт fix. +`ab737e7`: actual WASM теперь 27 шагов с проверкой ошибки пустого соседнего склада. + +После фикса полный core: 192 passed + 6 сценариев / 7 ignored; новый max-menu сценарий отдельно +прошёл седьмым. Адаптер60 + 1 boundary + 4 сценария; light WASM27; engine /spin fuel-profile test прошёл. + +## Исходная контрольная точка этой серии + +Исходное состояние этой серии: `2689d93`; движок закреплён на +`86b1014a9218e68041444cbf52db02a85107c815`, уровень API 26. Рабочее дерево было чистым, +коммиты опубликованы. Текущий точный pin всегда смотреть в `git ls-files --stage horizon`, +а происхождение собранного пакета — в `airdef-mod/target/horizon-mod/build-identity.json`. + +Четыре задачи были заказаны владельцем отдельно: + +| Issue | Заказанный остаток | +|---|---| +| #120 | Конечная работа двигателя перехватчика, coast, энергия манёвра, согласованные эффекты | +| #118 | Типизированная доставка RM48U через ТЗМ; не создавать неподтверждённые носители | +| #121 | Переключение излучения, сохранение разрешённого внешнего целеуказания, разведка излучателей | +| #119 | Ослабление непрямого урона известным укрытием, ограниченная политика Unknown | + +Исполнители работали в отдельных worktree; их проверенные коммиты перенесены в `kimi-work`. +В ветках `codex/issue-*` сохранены промежуточные версии; продолжать нужно от `kimi-work`, +где разрешены конфликты общего формата сохранений и сигнатур боевых функций. + +## Что уже сделано в предыдущей серии Codex + +- `airdef-core` компилирует исходники `airdef-mod/src/sim/mod.rs` через `#[path]`. + Это единый источник симуляции, но разделение домена и представления ещё не завершено (#87). +- Исправлены остаточные терминальные коллизии ответных ударов и скачок после world-query (#106/#117). + Восстановление ответов мира не даёт права телепортироваться к плановой точке. +- Доработаны запись ответных ударов, launch one-shot и live-след (#109), lifecycle показа и часы + отложенного звука (#88/#89). Полная унификация всех потребителей сцены ещё не закончена. +- Движок вынесен в отдельную репу; сборщик берётся из закреплённого форка, а не случайного PATH. + Есть build identity, настоящий WASM contract и handshake доступности дыма API26 (#102/#104). +- Обновлён upstream Horizon 0.2.97, исправления очереди/инвалидации акустики сохранены. + Измерительные fixtures, notices и лицензии добавлены; прослушивание и массовый CPU-тест ещё нужны (#96). +- Pipeline поддерживает authored overrides, manifest и экспорт итоговых габаритов в + `sim/generated_bounds.rs`. Навигация учитывает footprint. Generated output вручную не редактировать. +- Улучшены модели наземной техники; степень сходства и материалы ещё промежуточные (#115). + Галерея: `MODEL-PILOTS.md`, `VEHICLE-MODELS.md`; это не приёмка всех летательных аппаратов. +- `966b52f`: раздельные ПУ С-400/С-500, роли Radar Search/Engagement/Height/Multi. + Чужая подсказка разрешает огневому радару уточнение, но не подменяет собственное измерение. +- `966b52f`: резерв конкретной ячейки, отдельные крышки ТПК, выброс до включения двигателя. + RM48U — условный игровой профиль С-400; AA и ground rounds делят четыре физические ячейки. + Боеприпас списывается при выбросе, не при выдаче отложенного приказа; отмена не расходует его. +- Конфигурация, маска использованных ячеек и положение крышек включены в save/replay. + Smoke начинается в точке зажигания. С-500 ограничен двумя ракетами по числу ТПК; + старое сохранение с четырьмя загруженными ракетами при загрузке ограничивается двумя. +- `2689d93`: документированы источники архитектуры батареи, команды, скриншоты и границы визуальной проверки. + +## Проверки и воспроизведение + +На `966b52f`: 169 тестов ядра + 4 сценария прошли, 7 ignored; адаптер — 53 теста, +1 boundary и 4 сценария прошли; WASM contract — 21 шаг. Это исходная контрольная точка, +не результат новых четырёх задач. На `2689d93` был собран чистый пакет (`airdef_dirty=false`). + +На итоговом объединённом коде `6e2d661` прошли: + +- `airdef-core --release`: **187 passed, 7 ignored**, плюс **4 сценария**. +- `airdef-mod`: **56 passed**, **1 boundary**, **4 сценария**. +- Настоящий WASM-пакет: **24 шага**, включая команды излучения и typed ammunition. +- `gen_models.py --check`: различий нет; в этой серии геометрия ассетов не менялась. + +Полный core здесь запускался с `--release`; это не прогон всех семи ignored-тестов. +Отдельно выполнен сравнительный ignored-тест баланса (см. ниже). + +```sh +cargo test --release --manifest-path airdef-core/Cargo.toml +cargo test --manifest-path airdef-mod/Cargo.toml +python3 tools/airdef.py test-wasm +python3 tools/gen_models.py --check +python3 tools/test_gen_assets.py +git diff --check +``` + +На этой macOS cargo находится в `~/.cargo/bin/cargo`. GUI-проверки запускать с отдельным +`HORIZON_HOME` и рабочим каталогом тестового мира; не использовать реальные сохранения владельца. +Metal-проверки не подтверждают Windows/Linux, HDR всех платформ или прослушивание звука. +Скриншоты крышек подтверждают только их позу: полного видео выброса/зажигания пока нет. + +## Технические детали новой серии + +### #120: энергетика и единственный исход перехвата + +`MissileKind::burn_ticks` задаёт конечное время горения: простой профиль равен 2/3 игрового +`spec.life`; для электрического P1-SUN используется весь срок жизни. Это не отдельная модель +топлива и не реальные кривые тяги. `Missile::engine_lit` — общий источник состояния мотора. + +Три ветки скорости разделены явно: cold (`age <= cold`, множитель 0.97 с прежним минимумом 0.4), +powered (`+accel`, ограничение `spec.speed`), coast (множитель 0.995 без холодного минимума). +После выгорания доступный поворот масштабируется текущей скоростью; при падении ниже 25% +игрового максимума перехват заканчивается диагностикой `MotorEnergyLost`. Срок жизни остаётся +отдельным ограничением. Полноценной аэродинамики, гравитационной баллистики и расхода топлива по тяге нет. + +Swept fuse учитывает движение обоих объектов за тик; `Impact` получает точку closest approach +и относительную скорость того же проверенного отрезка, включая удержание на Unknown. +Геометрию не заменяет отдельный бонус «встречная/догонная». Один остаточный pk зависит от +фактической скорости перехватчика и существующего состояния носителя/дальности. +Повторный штраф по stealth и старый штраф по абсолютной скорости цели удалены из этого броска; +обнаружение/сопровождение по-прежнему учитывают stealth в своих местах. + +`MotorBurnout` и `MotorEnergyLost` записываются в scene journal; tally отличает потерю энергии. +Live и replay прекращают факел и новый дым по одному состоянию. `Out::engine_stops` немедленно +снимает голос мотора на burnout, не ждёт обычной трёхтиковой grace пропавшего источника; +при переходе на replay/export live-команды остановки отбрасываются вместе с live-выводом. +Перемотка восстанавливает моторное состояние из записи, включая возврат до выгорания. + +При ревью был найден и исправлен недостижимый coast: ранний прототип проверял `!lit`, +что перехватывало и cold, и coast. Не переносить один `143d9b2` без последующих исправлений. +`a7b8cb9` содержит отдельный тест точных правил скорости всех трёх фаз. + +Сравнительная выборка **только изменения #120**, отдельно от #119/#118/#121: +baseline `2689d93`, агентская итоговая ветка `508f9a0` (её исправление перенесено как `a7b8cb9`). +12 одинаковых seed, `SAMS=20 SIZE=12`: 204 цели в каждой версии; +baseline — 202 сбитых, 2 прорыва, 367 ракет; новое — 203, 1, 355. +Это небольшой регрессионный ориентир, не доказательство баланса всех типов/дальностей. +Данные каждого seed: [CSV](measurements/issue-120-balance.csv). + +Расширенная сетка остатка #120 (ветка `agent/issue-120-balance`, коммит см. в git): +baseline `2689d93` против текущего `e6a7000` (то есть с #118/#119/#121 и сменами движка, не только #120), +6 вариантов × 8 одинаковых seed, рейд `SIZE=36` (46 целей: Gerbera 10, Shahed 13, Geran3 5, Х-101 5, +«Калибр» 4, Х-22 2, «Циркон» 1, «Искандер» 4, «Кинжаль» 2; «Орешник» harness исключает, как в `one_raid`): +`batt10/20/50` (10/20/50 ЗРК, дистанция 500), `near200` (20 ЗРК, пуск в 200), `far1500` (20 ЗРК, пуск в 1500), +`hills20` (20 ЗРК, холмы, 500). Кластер тот же `MIX` из 10 систем: `SAMS=10` — по одному комплексу каждого типа. +Итого 2208 целей на сторону, 96 строк: [CSV](measurements/issue-120-grid.csv). Команда (игнорируемый тест +`sim::balance::issue_120_grid`, только harness-поля в `Result`, поведение симуляции не менялось): + +```sh +SEEDS=1-8 cargo test --release --manifest-path airdef-core/Cargo.toml issue_120_grid -- --ignored --nocapture +``` + +Факты сетки (суммы по 8 seed каждого варианта, baseline → текущий): сбитые 297→301 (batt10), 331→334 (batt20), +357→363 (batt50), 313→318 (near200), 328→338 (far1500), 328→334 (hills20); прорывы 70→66, 37→34, 11→5, +53→48, 40→30, 39→33. Промахов по взрывателю заметно меньше (например far1500: 70→38). Расход ракет близок +(516→504 … 597→583). На холмах потери у рельефа: разбито 14 и 14, самоуничтожений у хребта 6→2. +Прорывы во всех вариантах — преимущественно баллистические и быстрые классы («Искандер», «Кинжаль», Х-22, +«Циркон»); у медленных Shahed/Gerbera прорывов почти нет (0–2 на вариант), у ложных Gerbera — ноль ни разу. +`MotorEnergyLost` не сработал ни в одном из 48 сценариев текущего кода, `expired` ≤ 1 на вариант: coast +существует, но бои на этих дистанциях заканчиваются раньше, чем сопротивление 0.995/тик опустит скорость +ниже порога 25% (и раньше выработки ресурса). Это измеренное свойство профиля, не подтверждённый дефект, +поэтому параметры не менялись. Путь потери энергии покрыт юнит-тестом регресса (`regress.rs`), а не этой сеткой. + +```sh +SAMS=20 SIZE=12 cargo test --release --manifest-path airdef-core/Cargo.toml sim::balance::one_raid -- --ignored --nocapture +``` + +### #118: один физический груз и восстанавливаемая заявка + +`ground_request` у С-400 — заявка на **одну** RM48U. Рядом со складом остаётся прямая передача; +вне радиуса склада та же команда `ground-load` создаёт заявку, если есть запас и живая ТЗМ. +ТЗМ забирает одну ракету из конечного запаса, едет к исправному развёрнутому носителю и передаёт +её в общие ячейки. Если вытеснена ракета ПВО, ТЗМ везёт её обратно на склад. Generic reload +не создаёт RM48U. Ударная заявка имеет приоритет перед generic reload. + +Поля `ground_stock` / `air_48n6_stock` на складе — остатки, на ТЗМ — физический груз двух типов. +Заявка, груз и марш сохраняются; оперативный `job` реконструируется после загрузки. +Передача запрещена во время движения, подготовки пуска, перезарядки, отказа ПУ и при отсутствии +вместимости. Разрушение очищает заявку/груз; repair не восстанавливает уничтоженный typed stock. +Проверены настоящее движение `Sim.step` по плоскому WorldSource, передача/возврат, истощение, +прерывание, несовместимость, заполненные ячейки, unload и полный `state_text → apply_state` +с продолжением маршрута. GUI-доставка на сложном рельефе ещё не проверена. + +Новых носителей вроде Бука не добавляли: один публичный игровой профиль не даёт основания +приписывать его всем ЗРК. Обратный вывоз из удалённого носителя автоматизирован в продолжении выше. +Новая команда `ground-cancel` отменяет ожидающую заявку, но не возвращает уже забранный груз в ПУ. +ТЗМ вмещает два типизированных места RM48U/48Н6; прежние абстрактные generic reload-пакеты учитываются отдельно. +Вывезенная ракета возвращается в конечный запас; ПВО восстанавливается при заборе только из реально +имеющейся у ТЗМ ракеты 48Н6 и свободной ячейки. Резерв одной RM48U учитывается при выборе носителя удара. + +### #121: излучение отдельно от сети + +`radar_on` — сохраняемая настройка, default=true для старых миров. Команда +`/airdef radar <номер> on|off` и кнопка карты меняют её; у launcher-only нет своего активного радара. +`radar_up` учитывает этот флаг. Off запрещает новые own measurements, но старые треки стареют +обычно и разрешённый сетевой приём сохраняется. Это не выключение корпуса/связи/ГСН ракеты. +Огневой радар, обязательный для отдельной ПУ, в Off не авторизует новые пуски. + +Условный наблюдатель-противник раз в 20 тиков обнаруживает работающие радары в 1600 блоках. +Это упрощённый игровой датчик по расстоянию, без LOS/радиопеленгации и ошибки оценки позиции. +Контакт хранится как последняя наблюдённая точка и стареет за 6000 тиков. Off не стирает +контакт, не телепортирует его вслед за носителем и не предотвращает раскрытие пуском/РЭБ. + +В `raid::radar_strikes` удалён отдельный обход: раньше SEAD выбирал любую Radar и её настоящий +`pos`, даже если общий planner уже работал по наблюдениям. Теперь SEAD берёт те же last-known +контакты и их возраст. Явный sandbox-режим `Aim::ObjectsAndSams` по-прежнему выбирает расстановку +напрямую; не путать его с умным противником. Проверки покрывают необнаруженную молчащую РЛС, +смену позиции без наблюдения, повторное раскрытие, старение, relay freshness и seek. + +### Совместимость текстового состояния + +Сохраняем строки `sam` расширением справа; индексы ниже включают слово `sam` как поле 0. + +| Индекс | Значение | +|---|---| +| 22–24 | ground_rounds, ground_stock, air_48n6_stock | +| 25–27 | mode, radar_role, spent_cells | +| 28 | ground_request: 0/1, только С-400 | +| 29 | radar_on: true/false, отсутствие означает true | +| 30 | ground_return: true/false, резерв вывоза одной RM48U из С-400 | +| 31 | return_cargo: true/false, возвращаемая RM48U на ТЗМ | + +Две агентские ветки сначала заняли поле 28 одновременно; при интеграции излучение перенесено +в 29, опубликованная совместимость заявки сохранена. Проверка полного roundtrip одновременно +содержит ожидающую доставку и выключенный радар. Новые значения входят в durable mark; +настройка излучения также в `SamKey` и ghost replay. Не переиспользовать эти позиции для нового поля. + +### #119: непрямой взрыв и укрытия + +`WorldCache::blast_cover` читает текущую occupancy без запросов мира и без загрузки чанков. +Три луча к репрезентативным точкам корпуса дают общий множитель для корпуса и узлов; +для отмеченного объекта считается своя тройка. Известное укрытие оставляет 15% игрового +воздействия по перекрытому лучу, открытый — 100%; края/проёмы дают промежуточное значение. +Все лучи этого вызова читаются до отправки разрушения в движок. Исходный воксель пропускается, +чтобы контакт на поверхности стены не закрывал взрыв сразу во все стороны. + +Unknown, stale и слишком старые секции считаются **неразрешёнными**. Ожидание равно нулю: +для них сохраняется прежний неослабленный урон как консервативная игровая политика, +а не утверждение, что там воздух. После ответа мира этот взрыв не пересчитывается. +Нет очереди отложенного урона и повторного применения на world reply. Частичные блоки +на этом этапе считаются занятым кубом; тонкая collision shape для blast отдельно не запрашивается. +Прямой вооружённый контакт не ослабляется этим множителем. + +Проверки `sim::damage::cover_tests`: стена/проём для корпуса, узлов и объекта; +Unknown и поздний ответ без повторного урона/запросов; прямое попадание; устаревшая геометрия; +пропуск исходного блока. Все четыре прошли в объединённом наборе. + +### #127: устойчивая очередь маршрутов после массового перенацеливания + +Реализована только узкая часть issue: `sim::ew::RouteJob` — задание с ID дрона, новой целью, +ID цели-юнита, версией знаний и общим для тика снимком `known_units` (`Arc`). `adapt` по-прежнему +выбирает новые цели раз в 40 тиков, но маршруты теперь достраивает `replan_retargets` **каждый +тик**: одно задание или несколько, пока не исчерпан `WORK_PER_TICK`. Незавершённое задание +остаётся в `Sim::retargets` (FIFO) и не теряется; повторный выбор того же дрона заменяет задание +на месте, поэтому очередь не растёт. + +Старый маршрут не очищается при перенацеливании: дрон следует готовому безопасному участку, +пока новое задание не получит маршрут. Если задание отменяется (дрон погиб, цель изменена снова, +юнит уничтожен), маршрут очищается как fallback — дрон летит прямо до следующего адаптивного тика. +Lancet, баллистика и decoy, как и раньше, летят прямо: для них задание не создаётся. В полёте +следующая известная цель берётся только если до неё есть разворот; цель сзади внутри радиуса +разворота пропускается, и дрон завершает текущий заход, а не получает невыполнимый маршрут. +Повторный план использует снимок разведки исходного выбора цели и известный рельеф **той же версии +задания**: `KnownRelief` (сетка и высоты) лежит в `Arc` внутри `RouteJob`, `Sim::known_relief` хранит +тот же `Arc` только для новых заданий. Завершение другой умной волны заменяет глобальный указатель, +но не снимок ожидающего job. Выборка рельефа, цены и кэш маршрутов в `replan_retargets` ключуются +версией знаний целиком — adapt tick плюс указатели на снимки known units и рельефа, — а не только +рельефом: два job с одинаковым рельефом, но разными known units, не делят price/route grid. За +пределами известной сетки — Unknown, новая карта не раскрывается. + +Очередь этапа 1 была рантайм-состоянием планировщика: дроны и очередь запуска в `state_text` не +входили, поэтому durable-очередью она не была. Этап 2 (ниже) сохраняет её вместе с дронами, очередью +запуска, картиной и версиями рельефа. Регрессии этапа 1 остаются: `mass_retarget_finishes_its_route_jobs_over_the_budget` +(40+ ретаргетов, прогресс каждый бюджетный тик, старый маршрут до нового, нет пустых маршрутов) +и `mass_retarget_is_deterministic_and_never_duplicates_a_drone` (тот же seed — те же цели и +маршруты; повторный `adapt` не создаёт дублей). + +### #127, этап 1: честная EnemyPicture + +Журнал `intel.revealed: Vec<(id, место, тик)>` заменён картой контактов: `Intel.contacts: BTreeMap`, +одна запись на юнита, последнее наблюдение побеждает. Контакт пишется только в момент раскрытия и +читает истину только тогда: пуск (mod.rs/strike.rs) → `Seen::Launch`; фиксация излучения + радаром рядом с чужим аппаратом (sight.rs, каждые 20 тиков, 1600 блоков) → `Seen::Emission` +(отдельно помечается `emission` — вера в собственный сенсор опирается только на неё); работающая +РЭБ (ew.rs) → `Seen::Jamming`; четвёртый канал — BSA: падение собственной боеголовки рядом с +известным юнитом (`Sim::observe_outcomes`, радиус 16) фиксирует `destroyed` либо обновляет «жив» +и место. Пока юнит незамечен, его перемещения, выключение радара, смена ролей/режима, расход БК, +урон и гибель картину не меняют — это метаморфные тесты `sim::ew::picture_tests`. + +Из контактов решается всё: снимок `picture_of_defences()` на приказ и на адаптивный тик (newest first, +при равном тике — больший id, как у старого reversed-журнала), цели `choose_target` и SEAD +(`radar_strikes` — только Probable и свежее), ретаргет в полёте (цель «мертва» лишь по контакту с +`destroyed`; забытого контакта дрон не замечает), живость задания очереди (`job_live` — по картине, +не по истине). Уверенность — функция одного возраста: Confirmed ≤ 600, Probable ≤ 3000, Doubtful до +TTL 6000 тиков (`Confidence`, диагностика и SEAD её используют, цены — нет: консервативно считаются +все живые контакты). + +Модель возможностей в `price_grid_known` читает только картину: роли — published `kind.spec().roles` +(смена `/airdef roles` невидима), дальности — `ew::published_reach` (без урона, фактора роли и +мачт истины), донор — любой живой контакт вида Radar в link range от наблюдённого места (роль +донора не наблюдаема: рабочий контакт РЛС предполагается кормящим сеть — это допущение, а не +знание; разрыв связи/гибель донора незамеченными картину не меняют). Собственный сенсор верится, +пока жива фиксация эмиссии; пуск при нуля эмиссионных фиксаций у С-400/С-500 читается как +launcher-only: без донорского контакта такая ПУ в ценовой сетке «слепа», с ним бьёт на дальность +донора. Горизонт РЛС игрока (`self.horizons`) из чужого планировщика убран: маскировка рельефа — +тайна обороны, цены по ней консервативнее и объяснимее. + +Диагностика без GUI: `Sim::enemy_picture() -> EnemyPictureReport` (англ.): по каждому контакту — +наблюдение (канал, место, возраст), уверенность, живость, наблюдённое уничтожение, вера в сенсор, +наличие донора и текстовое «что планировщик из этого делает». Адаптация по-прежнему ограничена +статистикой секторов (`best_bearing` по kills/hits): новых всеведущих данных и усиления +характеристик нет. + +Форматы этапа 1 не менялись: intel оставался рантаймом (save хранил Layout, replay — траектории); +durable-очередь появилась на этапе 2, ниже. Измерения до/после на одних seed: `docs/measurements/issue-127-enemy-picture-baseline.json` +(0b645d77e) и `issue-127-enemy-picture-result.json` (эта ветка), bench — `cargo run --release --example intel_bench`. +big-smart и logistics побайтово совпали с базой; ew-adaptive изменился (kills 62→59/61→59/61→56, +hits 3→7/5→7/7→8, потери ЗРК 1→3/4→3/4→4) — фиксируется как факт перехода на честную картину, +балансные параметры не трогали. Регрессии: метаморфные `hidden_changes_do_not_move_the_picture_or_its_decisions`, +`a_new_observation_is_what_updates_the_picture`, `launcher_only_is_blind_in_the_picture_without_a_donor` +(донор любой роли; разрыв связи наблюдённый/нет), `destruction_moves_the_picture_only_when_watched`, +`confidence_is_a_function_of_age_alone`, `enemy_picture_report_describes_observation_and_belief`, +`an_impact_watches_the_kill_and_updates_only_the_picture` (BSA end-to-end), обновлённые SEAD/#4/#21/#122 +и обе регрессии очереди. Остаток #127 после этапа 1: durable save queue, structured intent, GUI. + +### #127, этап 2: structured intent, durable queue и объяснимость + +**Намерение отделено от цели.** У дрона теперь два поля: `ew::Intent` — почему он летит туда — и +`ew::Aim` — юнит, когда цель выбрана, сколько раз менялась и уверенность контакта. `Intent`: +`Plan` (собственная цель плана: объект или юнит, выбранный при заказе), `Sead` (плановый удар по РЛС +из `radar_strikes`), `React` (реакция на раскрытие: станция показала себя уже в воздухе) и `Replace` +(картинка увидела гибель/забвение прежней цели). Случайный выбор цели (`choose_target`) больше не +смешивается с реакцией: при заказе налёта intent фиксируется, а `adapt` помечает свои решения явно. +Приоритет очереди — из намерения: Plan 3, Sead 2, React 1, Replace 0. + +**Throttle.** `SWITCH_HOLD = 160` тиков (8 с): свежее раскрытие может сменить цель только после +выдержки; `SWITCH_CAP = 3` смены на дрон за жизнь; `ASSIGN_PER_TICK = 16` новых целей за адаптивный +тик и `ASSIGN_PER_CONTACT = 6` на один контакт за тик — массовый ретаргет растягивается на следующие +адаптивные тики, а не переписывает все маршруты сразу. Смена возможна, только если контакт показался +позже текущей цели (`tick > aim.since`), он не Doubtful, и он либо достовернее текущего, либо ближе +на `SWITCH_GAIN = 120` блоков: смена выбрасывает готовый маршрут и стоит планировщику нового, поэтому +маргинальная не делается. Замена убитой цели выдержку не ждёт (цели уже нет), но упирается в тот же +cap. В полёте, как и раньше, цель меняется только когда картинка её убила или забыла. + +**Явные игровые реакции без скрытых данных.** Реакции читают только поля контакта, записанные каналом +наблюдения: выключенный радар остаётся в картине таким, каким его видели (эмиссия не фиксировалась — +собственный сенсор не верится), разделённая батарея читается как пуск без эмиссии (launcher-only, +без донора «слепа»), конечный БК наблюдается только как пуски (`Contact.launches`: счётчик увиденных +пусков; остаток магазина в картину не входит никогда), энергия/нагрев РЭБ — своё состояние станции, +а не раскрытие. Метаморфный тест `hidden_changes_do_not_move_the_picture_or_its_decisions` расширен: +скрытые перемещение, off, роли, режим, БК, урон, гибель и нагрев/замок/выключение РЭБ не двигают ни +картинку, ни цены, ни намерения/aims/очередь заданий. Повторное наблюдение двигает всё — как и должно. + +**Неизвестная земля не бесплатна.** `raid::Relief` получил поле `unknown`; у плана умной волны оно +0 (поведение этапа 1 сохранено), у повторного планирования задания — `UNKNOWN_LAND = 6` сверх базовой +цены клетки 1. Задание, чья версия рельефа ничего не знает, планируется по полностью неизвестной +земле с той же ценой, а не «как по свободной»: маршрут держится размеченной части, если она есть. +Тест `unknown_land_is_not_free_in_a_replanned_route` сравнивает маршрут с ценой 0 и с ценой 6 на одной +сетке: бесплатный срезает через неразмеченное, платный идёт по размеченному. + +**Durable queue: формат save.** `state_text` (автосейв) дописывает версионированную секцию +(`sim::durable`, формат `air 1`): + +``` +air 1 <такт> <номер следующего задания> <дронов> <в очереди> <заданий> <контактов> <версий рельефа> +airintel <12 kills> <12 hits> +airglobal <версия рельефа|-> +airrelief <номер> <клетка> <сторона> | <высота>x ...> +aircontact <тип> <такт наблюдения> <канал> <эмиссия|-> <гибель|-> <пусков> +airdrone <тип> ... <полёт> <намерение> <юнит> <смены> <уверенность> ... <маршрут: n x y z ...|-> +airqueue <такт запуска> <то же тело дрона> +airjob <дрон> <юнит> <версия> <приоритет> <намерение> <рельеф|-> +``` + +Секция пишется только автосейвом: сценарий — это стартовое состояние, его тело её не несёт +(`scenario_text` → `state_text_with(..., air = false)`). Старые saves без секции читаются как прежде +(пустое небо); старые сборки новые saves читают как Layout, игнорируя `air*` (Layout пропускает +неизвестные строки). Всё проверяется до применения: битая строка отказывает save целиком с номером +строки и полем, счётчики в заголовке сверяются с числом строк (обрезанный файл отвергается), чужая +строка внутри секции — тоже. Границы: 512 дронов, 512 точек маршрута, 4096 контактов, 8 версий +рельефа, 40 000 клеток; координаты пишутся до 0.01 блока (полёт этого не замечает), рельеф — RLE +целых высот. Секция дополнительно ограничена 900 КБ (хост разрешает 1 МиБ на одну запись): дрон, +который не влез, не пишется, и его задание тоже (восстановить его было бы нельзя). Измеренный размер секции в bench: 4.0–8.8 КБ для logistics/ew-adaptive и 61–62 КБ для +big-smart (38 дронов в воздухе). + +**Что именно восстанавливается.** Дроны в воздухе (живые; падающие обломки — эффект, их нет), +очередь запуска с тактами, задания (`id`, дрон, цель, юнит, версия, приоритет, намерение, версия +рельефа), контакты картины (счётчики секторов, пуски), глобальный снимок рельефа и `job_next`. +Картина задания восстанавливается «на свою версию»: контакты, наблюдённые не позже версии, плюс +собственная цель задания (её последнее сохранённое наблюдение заменяет более позднее — иначе цель +исчезла бы из своей же картины). Устаревшие задания отменяются при загрузке (`prune_air`): дрон +исчез/погиб, цель уже другая, юнит наблюдённо уничтожен или забыт, дубль по дрону, версия старше +`JOB_TTL = 1200` тиков (60 с). Маршрут отменённого «по устареванию/дублю» задания сохраняется (он +ведёт к той же цели), отменённого из-за смены цели — очищается. + +`apply_state` сначала проверяет Layout и секцию, затем применяет обе; секция заменяет воздушное +состояние, а не сливается с ним. `durable_mark` включает `air_mark` (число дронов/очереди/заданий/ +контактов и `job_next`): автосейв берётся на запуске, потере, новом/завершённом задании и новом/забытом +контакте, а не каждый тик полёта. Пока налёт *планируется*, периодический save откладывается (план не +имеет durable-формы; поиск площадок ограничен `sites::SEARCH_TICKS = 400`); при смене мира пишется +только Layout. Replay по-прежнему хранит траектории: секция пишется и читается только для живого +`Sim`, записи не смешиваются с ней. + +Остаток и ограничения: ракеты-перехватчики в воздухе, пусковые площадки (`sites`), промежуточный +план, статистика волны и состояние RNG не сохраняются — после загрузки перехваты начнутся заново, +очередь стартует без площадки (свободный слот), отчёт волны не придёт, а случайные броски идут от +нового генератора мира; сама воздушная обстановка (дроны, цели, задания, картина) восстанавливается +точно. GUI поверх отчёта не делался. + +**Объяснимость.** `EnemyPictureReport` расширен без русского текста в core: у контакта — `launches`, +`aimed_by` (сколько дронов на него нацелено) и `pending_jobs`; добавлены `aims` (дрон, юнит, intent, +since, switches, confidence) и `pending` (id задания, дрон, юнит, точка, версия, приоритет, intent, +`relief_known`, `reason` словами: ждёт бюджета / ждёт бюджета, земля неизвестна и стоит дороже / +устарело), плюс `work_per_tick`. Тест `the_report_explains_aims_and_pending_jobs_in_english` +проверяет и ASCII-only диагностику. + +**Измерения (baseline 8fc10c07a → эта ветка, `cargo run --release --example intel_bench`, seeds 1/7/42):** +`docs/measurements/issue-127-intent-queue-baseline.json` и `issue-127-intent-queue-result.json`. +big-smart: завершение у всех, launched 38, kills 38, hits 0, потери 0 — как в базе; ticks 1716→1676 +(seed 1) и 1542/1433 без изменений, fired 49→60, 48→68, 49→71 (больше пусков ПВО). logistics: все +поля совпали (27 запусков, 11/12/12 сбито, 8/7/8 прорвалось, 18 ракет, 0 потерь). ew-adaptive: +kills 59→58, 59→58, 56→63; hits 7→7, 7→9, 8→3; потери ЗРК 3→3, 3→4, 4→2; ticks 8084→7146, 8193→7650, +7916→7243; fired 69→76, 81→98, 69→82. Это следствие throttle назначений и приоритетной очереди, а не +подкрутки баланса: параметры ПВО/дронов не менялись, числа фиксируются как факт. В result-файле есть +новые поля (`retargets_max`, `air_bytes`, `switches_max`, `intents`), у baseline их нет. + +**Регрессии этапа 2.** Core: `the_plan_records_sead_separately_from_its_objective`, +`assignments_are_throttled_per_tick_and_per_contact`, `a_fresh_disclosure_switches_an_aim_only_after_the_hold`, +`a_watched_kill_replaces_the_aim_and_queues_a_lower_priority_route`, +`launched_rounds_are_observed_but_the_magazine_is_not`, `unknown_land_is_not_free_in_a_replanned_route`, +`the_route_queue_is_bounded_and_refuses_a_worthless_job`, `the_same_seed_chose_the_same_intents_and_jobs`, +`the_report_explains_aims_and_pending_jobs_in_english`, `the_air_state_round_trips_every_job_field`, +`a_pending_route_continues_after_a_save_and_a_load` (save/load посреди незавершённой очереди: та же +очередь, те же маршруты, обе кампании доводят задания, пустых маршрутов нет), +`stale_ghost_and_duplicate_jobs_are_cancelled`, `a_job_for_a_unit_the_picture_lost_is_cancelled_on_load`, +`a_damaged_air_section_is_refused_with_its_line_and_field`, `the_section_stays_inside_its_byte_bound` +(600 дронов с длинными маршрутами: секция ≤ 900 КБ, записанные задания принадлежат записанным дронам), +обновлённые массовые ретаргеты (40+ дронов: +прогресс на бюджетном тике, throttle назначений, у дрона, стартовавшего без маршрута, маршрут +появляется). Adapter: `the_air_section_of_a_save_is_written_and_read_by_the_adapter` (запись/чтение +секции, замена не слияние, старый save без секции, сценарий без воздуха). + +## Что следующему агенту проверить в первую очередь + +1. На Windows: обновление сабмодуля через `all`, сохранение/перезаход, рабочий цикл игры. +2. В GUI: полный холодный пуск крупным планом; карточка On/Off; маршрут ТЗМ по сложной местности; + разрушение стены одним взрывом и защита от того же взрыва по исходной геометрии. +3. Прослушать прекращение голоса двигателя при burnout и seek/экспорт вокруг этой границы. +4. Расширенная балансировка #120 (батареи/дальности/классы) записана в `measurements/issue-120-grid.csv`; + остаются прослушивание голоса мотора и платформенная приёмка. +5. Продолжить оставшиеся архитектурные #87/#88/#89/#91/#102 и художественный #115 по статусной таблице. + +Это незавершённая платформенная/визуальная приёмка, а не отсутствие перечисленного выше кода. +Прогон четырёх задач не запускал GUI/MP4. В продолжении API27 есть два коротких игровых smoke, +но новый MP4, прослушивание и платформенная приёмка Windows/Linux по-прежнему не выполнены. + +## Правила продолжения + +- Не закрывать issue даже после исправления; сообщать проверенный объём и остаток в комментарии. +- Не переписывать gitlink на плавающую ветку. Изменения движка сначала публикуются отдельно. +- Не путать наблюдённый трек с настоящим положением цели и `own_tick` с relay. +- Не менять live-мир при seek; replay воспроизводит последствия, не применяет урон повторно. +- Не считать Unknown свободным пространством или подтверждённой стеной без явной игровой политики. +- Новые числа — игровой масштаб, не реальные характеристики вооружения. +- Токен Gitea читать из локального файла во время запроса; не коммитить и не печатать. diff --git a/docs/CONTINUATION-2026-11-04.md b/docs/CONTINUATION-2026-11-04.md new file mode 100644 index 0000000..f362139 --- /dev/null +++ b/docs/CONTINUATION-2026-11-04.md @@ -0,0 +1,126 @@ +# Продолжение 4 ноября: #115, #87, #91, #102, #96 + +Рабочая ветка `kimi-work`. Все issues оставлены открытыми. Работа выполнена Codex с GPT-6.1 Terra. +Движок опубликован в `horizon-airdef:airdef`, текущий pin +`57f8b2853c4443f7e3ff5701600cbb262b53118f`, API **29**. + +## Изменения + +- **#115:** authored-модели «Ланцета» и Х-101: сегментные стреловидные крылья, + детали носа/корпуса. Элементы 17→49 и 22→30 соответственно; игровые параметры не менялись. + `5366988`, `cfae0c1`, `914f723`. [Кадры и ограничения](AIRCRAFT-MODELS.md). + `tools/model-preview` воспроизводит реальный `set-models`, `glow=false`; установленный + stone-template блок не подходит для оценки света динамической модели. Полосы яркости + на наклонных сегментах вынесены в [#123](https://gitea.maoniao.team/chexit/airdef/issues/123). +- **#87:** `375fdf2`, `cb73cb7`, `0c870c1`: типизированный `BatteryStatus`, отчёт кэша мира + и причины повреждений отделены от русского форматирования. Команда/карта/монитор используют + адаптеры `battery_view`, `world_view`, `why_view`. Порядок и тексты сохранены. + Это постепенное разделение: `CheckReason::Text`, сообщения `Out` и часть представления в ядре остаются. +- **#91:** `eea1b36`, `1553976`, `daa9ed7`: `catalog/content.json` — идентичности техники, + предметы и метаданные иконок; `catalog/sounds.json` — звуковые события, дальности и far-варианты. + `gen_catalog.py` создаёт Rust-константы, Python-регистрации читают тот же источник; + CI проверяет актуальность. Сохранены порядок позиционных сейвов, все 35 звуковых событий, + OGG и порядок общего RNG синтезатора. Числовые `DroneSpec`/`SamSpec` остаются типизированным + источником в Rust; полного переноса всех характеристик в JSON не было. + Продолжение переноса чисел — ниже. +- **#102:** `63aefc7`: `test-budgets` включает настоящий `--fuel-profile` и сохраняет + `measured_fuel.callbacks`, включая ошибки. Отсутствие запрошенных счётчиков запрещает success. + В движке `6230ec703`/`d8fa77b00` добавлены API28 point-lights-status и API29 sound-sources-status. + Свет: заявка → cache → обработка main-view HD списка; звук: заявка → фактический apply + source manager при live/offline output. Отдельные run/world/source epochs, clear/reload и + отсутствие output проверены настоящими Wasmtime-гостями. Это не обещание пикселя/слышимости. + `/airdef capabilities` показывает оба состояния (`c1a595b`). + `57f8b2853` также устраняет коллизию камер: texture targets, scheduler, HUD/canvas/corner и + main-view адресуются `(run_serial, local_id)`. Проверены два настоящих гостя с одинаковым ID, + поиск target и scheduler, компиляция клиента. + `efe017ffb`/`278f356aa` добавляют API30 `camera-status`: `prepared` — закодированный в target + кадр камеры или кадр мира с её main-view, причём milestone получает только тот запуск, чей выбор + экран реально использовал (чужой тот же local ID и чужая картинка его не готовят). `enabled` — + последний отмеченный кадр был кадром мира, не заголовком. Пустая `set-views` и смена мира + очищают заявку/выбор/готовность, reload начинает заново; главный вид теряет picture target до + нового кадра. Проверено настоящими Wasmtime-гостями. Итоговый engine `278f356aa` с AirDef API30 + проверен и в запущенной игре на Metal/offscreen (Apple M5, чистый `HORIZON_HOME`, копии тестовых + миров): monitor даёт `ready` с нарисованной картинкой (по снимкам), film-main-view — `ready` с + main-view (на экране карточка подготовки фильма), пустая заявка + и смена мира — `not-requested` с новым поколением, после deploy+monitor камера снова готова; + команды и ограничения — в [ENGINE.md](ENGINE.md#gpu-приёмка-картинки-камер-metaloffscreen). +- **#96:** engine `e44ad82a9`: opt-in `audio::tests::export_moving_mass_raid` проводит + 100 движущихся двигателей и 16 импульсных источников через настоящие `Audio::tick_acoustics` + и `film_mix`: эхо/реверб, камера, инвалидация и смена epoch, новый capture без старого хвоста. + Два 6-секундных прогона дают одинаковые float-сэмплы; ненулевая дорожка, finite/peak и reset + проверяются assertions. Сэмплы и геометрия синтетические, устройство звука не открывается. + Release на Apple M5: step+mix на 50 мс аудио — p50 **3.55 мс**, p95 **4.30 мс**, max **5.57 мс**. + [Отчёт и SHA дорожки](measurements/mass-acoustics-2026-11-04.json). + Это не замер полного игрового CPU/GPU, не Windows FPS и не готовый MP4. + +## Продолжение #91: один источник чисел + +`catalog/specs.json` теперь единственная копия числовых строк симуляции: 13 дронов +(`DroneKind::spec`), 9 ракет (включая `seeker`, звук пуска и голос мотора), 15 комплексов +(`SamKind::spec`, включая части моделей и именованные наборы), общие константы гравитации, +высоты раскрытия «Орешника», приводов и логистики. `tools/gen_catalog.py` генерирует +`airdef-mod/src/sim/specs_generated.rs` и `unit_specs_generated.rs`; `include!` встраивает их в +`mod.rs` и `units.rs`, поэтому мод и `airdef-core` компилируют одни и те же строки. Прежние +`match`-литералы удалены; `DroneKind::spec`, `MissileKind::spec`, `SamKind::spec` и их производные +(RCS, seeker, звуки, голос) читают сгенерированные константы. + +`load_specs` проверяет схему, звуковые ссылки, совпадение набора kinds с `content.json`, ссылки на +именованные части и то, что каждая строка кем-то используется; `gen_catalog.py --check` уже в +CI-шаге «Проверка generated assets», поэтому расхождение JSON и Rust валит сборку. + +Сверка перед переносом: JSON сравнивался с литералами `632061d` по всем полям. Голос P1-SUN — +тонкое место: фактически играет ранний возврат `flame == 0` в старом `voices.rs` +(`(DRONE_LANCET, 0.8, 1.6)`), а `P1sun => 1.7` в `match` недостижим. Первая версия сверки ошибочно +приняла недостижимую ветку за факт и поставила в JSON 1.7 — это меняло звук перехватчика; fixup +вернул 1.6 и добавил регрессию `missile_engine_voices_keep_their_played_values` (P1-SUN и M48N6). +После генерации строки сверены с прежними литералами поле-в-поле (включая части моделей и +именованные refs) — расхождений нет. Порядок raid/save, ключи `sam`/дронов, RNG и тексты не менялись. + +Проверки этого этапа: `cargo test --release` ядра — **194 passed, 7 ignored** + **8 сценариев**; +адаптер — **64 unit**, **1 boundary**, **5 сценариев**; `gen_catalog.py --check`, `gen_models.py --check`, +тесты генераторов/звука/границ моделей; настоящий WASM `test-wasm` — **27 шагов**, 1 скрипт. +GUI/MP4 и варианты комплексов (разные ракеты/профили по батареям) в этот этап не входили. + +## Проверки и продолжение + +Объединённый core release: **193 unit**, 7 ignored, **8 сценариев**; адаптер: **64 unit**, +**1 boundary**, **5 сценариев**. Генераторы catalog/models, asset pipeline, sound references, +границы моделей и 6 тестов provenance/failure отчёта проверяются отдельно. Лёгкий WASM — 27 шагов. +Тяжёлый default fixture — 271 шаг с двумя принятыми налётами; его текущий отчёт пишет +`python tools/airdef.py test-budgets` в `.horizon-cache/wasm-budget-report.json`. +Чистый финальный прогон `56339b0` с API29 сохранён в +[api29-wasm-budget.json](measurements/api29-wasm-budget.json): tick max 106132646/300000000, +command max 1013387/1000000000, ошибок 0, build identity совпал. + +Не завершены: субъективное прослушивание, настоящий MP4 с акустикой, полный 10-минутный +replay/export, расширенная модельная/ночная/FPS приёмка, визуальная/Windows-GPU приёмка +camera-ready картинки и полные query/voice/peak-memory бюджеты. +Старый допуск broadband RT60 35% не ужесточался до 15%. +Попытка живого MP4 в этом проходе не дала проверенного файла и за приёмку не считается; +изолированные процессы закрыты. Продолжать с текущего pin, не с временных пакетов агентов. + +Обновление Windows прежнее: `git switch kimi-work`, `git pull --ff-only`, +`python tools/airdef.py all`, `python tools/airdef.py play`. + +## Обновление #89 (4 ноября, после этого прохода) + +Живой короткий MP4 получен и проверен на pin `57f8b2853` (API 29) на scratch `HORIZON_HOME`/мире: +h264 + AAC, отчёт мода совпадает с `ffprobe nb_frames`, отмена/ошибка SDK/ошибка энкодера/повторный +запуск проходят. Найден и исправлен дефект: выход в заголовок во время экспорта оставлял захват +записывать заголовок (запрос `sec=10` дал 3898 кадров); теперь показ завершается по кадровому хуку. +Подробности и проверенные ограничения — [HD-RENDERER](HD-RENDERER.md) (раздел про кинорежиссёра) +и [ENGINE](ENGINE.md) (раздел про захват). Очистка временных файлов при ошибке энкодера +исправлена в движке `73b9d3070`: лог удаляется, его последние строки входят в текст ошибки, а +готовый MP4 прежнего экспорта с тем же именем сохраняется. Акустика проверена отдельно в настоящем +MP4 (пары on/off одного повтора, статусы ready, диффузный wet-остаток в каменном дворе); чистый +хвост/RT60 и прослушивание остаются — [ACOUSTICS](ACOUSTICS.md). 10-минутный экспорт и Windows/Linux +по-прежнему не проверены. + +## Обновление #123 + +Engine `45b43f00d`: dynamic block models используют геометрическую нормаль квада вместо осевого +`facing`; для вырожденных вершин сохранён прежний fallback. Unit-тест покрывает `±45°`, другие +наклоны, осевые и вырожденные квады. На реальном Lancet-инстансе через `set-models`, `glow=false` +и HD получены парные кадры до/после в одном мире и ракурсе, а также вид сверху и ночью; полосы +осевого освещения устранены. [Кадры и ограничения](AIRCRAFT-MODELS.md). Массовый FPS и полёт не +измерялись. diff --git a/docs/ENGINE.md b/docs/ENGINE.md new file mode 100644 index 0000000..415734e --- /dev/null +++ b/docs/ENGINE.md @@ -0,0 +1,256 @@ +# Закреплённый движок AirDef + +Движок: https://gitea.maoniao.team/chexit/horizon-airdef, ветка `airdef`. +AirDef хранит точный SHA как gitlink `horizon/`; новый push движка не меняет старые сборки мода. +История upstream и всех 44 прежних патчей сохранена в форке. Старые patch-файлы и HORIZON_COMMIT +доступны в истории AirDef и больше не являются вторым источником движка. Baseline описан в AIRDEF-FORK.md. + +## Windows: обновление и запуск + +```powershell +git fetch origin +git switch kimi-work +git pull --ff-only +python tools/airdef.py all +python tools/airdef.py play +``` + +Первое клонирование: `git clone --recurse-submodules https://gitea.maoniao.team/chexit/airdef.git`. +Обычный clone тоже поддерживается: `python tools/airdef.py fork` или команда submodule выше. +`all` инициализирует зависимость сам, включая переход со старой папки `horizon/`. +Для чистого checkout можно отдельно выполнить `git submodule update --init --recursive`. Нужны Git, Python и Rust согласно INSTALL.md. +Сборка клиента и мод устанавливаются в обычное место; ресурсы Minecraft готовятся отдельно по INSTALL.md. + +При первом переходе со старого отдельного clone `horizon/` скрипт сохраняет его Git-историю и ветки, +переводит в сабмодуль и получает закреплённый SHA. Если там есть изменения при другом SHA, +обновление останавливается с объяснением: сначала закоммитьте/сохраните свои изменения. +Скрипт не выполняет reset --hard или clean. Чистый checkout может остаться detached HEAD — это нормально для зависимости. + +## Изменение движка + +1. В `horizon/` создать рабочую ветку от закреплённого SHA. Проверить изменение и push в репозиторий движка. +2. В AirDef выполнить `git add horizon`, проверить мод/игру, коммитить обновление gitlink отдельно. +3. Push AirDef выполняется после движка. Не коммитить Git URL с токенами. +4. Для возврата выбрать прежний commit AirDef и выполнить `git submodule update --init --recursive`. + +Упаковщик собирается repo-local в `.horizon-cache/packer//`, глобальный +`cargo-horizon-mod` не используется. Изменения tracked-исходников движка входят в identity. +Рядом с airdef.hmod записывается build-identity.json: SHA AirDef, pin/identity движка, +SHA256 WIT и артефакта. API-диагностика остаётся в `python tools/airdef.py check`. +Нативные тесты не заменяют отдельную игровую проверку графики, звука и экспорта. + +## Проверка настоящего WASM + +`python tools/airdef.py test-wasm` собирает `.hmod` и запускает `tests/*.toml` в настоящем +runtime закреплённого движка. Используется временное storage; игровые миры не нужны. +Сценарий проверяет загрузку/регистрацию команды, два комплекса, налёт, tick/frame, +выход из мира и перезагрузку мода. Действуют лимиты runtime: 64 MiB linear memory, +300 млн fuel на tick, 10 млн на frame, 1 млрд на event, 2 млрд на init. +Это проверка ABI и fallback при отсутствии render replies; она не измеряет GPU, +не подтверждает акустику/MP4 и не заменяет максимальный боевой полигон. + +В CI проверка assets входит в лёгкий build. Wasmtime tier вынесен в ручной workflow +`wasm-contract`, чтобы не компилировать тяжёлый runtime на каждом push слабого общего runner. + +## Готовность объёмного дыма (API 26) + +`render.smoke-status` разделяет доступность API, включённый режим, принятую отправку этого мода +и готовность smoke/fog пути последнего подготовленного основного кадра. Возвращаются поколение мира +и причина (`awaiting-frame`, `disabled`, `path-unavailable`, `ready`). AirDef убирает обычный дым +только когда выполнены все четыре условия; после смены мира старые сегменты и часы очищаются. +Это подтверждение выбранного пути рендера, а не завершения GPU или видимости каждого сегмента. +Сам `smoke-status` не подтверждает акустику и capture; их отдельные статусы добавлены в API 27 ниже. + +## Акустика и владение захватом (API 27) + +Текущий pin: `278f356aa604fa21c2d15d0081823bbe0f891df2` (API 30; поверх API29 — нормали моделей +и очистка неудачного захвата). Акустические +`render.acoustics-status` и `render.capture-owner-status` возвращают поколение, +`available`, `enabled`, `request-accepted`, `prepared` и причину. В игре всё это показывает +`/airdef capabilities`. Отсутствие запроса, выключение, ожидание backend и ошибка различаются. + +Для акустики `prepared` означает выполненный шаг обработки с геометрией при доступном live/offline +микшере. Шаг может поставить worker job или принять результаты; это **не** подтверждение результата +для каждого источника, завершения worker, слышимого звука или точного отражения. В режиме no-audio +без offline-микшера готовность не наступает. AirDef подтверждает текущее глобальное предпочтение, +не меняя его, и сохраняет собственные дальние варианты звука до подтверждённой подготовки. + +Для capture запрос принадлежит конкретному запуску мода и поколению мира. `prepared` соответствует +его стадиям Recording/Encoding/Done; состояние файла и ошибка по-прежнему читаются из `capture-status`. +AirDef не принимает чужие Recording/Done за свой экспорт. Ожидание первоначальной подготовки +ограничено 600 тиками. Остановка проверяет владельца; смена мира отменяет модовый захват и +сбрасывает готовность. Уже переданный writer файл на Encoding может всё же завершиться: +это запрос отмены, не гарантия удаления файла. + +Проверено на реальном экспорте 4 ноября (pin `57f8b2853`; очистка ошибки в `73b9d3070`): + +- Смена мира доходит до движка только через снимок мода (`set_snapshot`). На экране заголовка тики + не вызываются, поэтому мод без хука `frame` не сообщает о закрытии мира, и захват продолжает + писать заголовок. AirDef теперь просит `frame` и завершает показ по `game.world-id = none`. +- При ошибке энкодера временные файлы этого захвата (`<имя>.video.mp4`, `<имя>.audio.raw`, + `<имя>.ffmpeg.log`) удаляются, а последние строки лога переносятся в текст ошибки: ссылки на + уже удалённый лог больше нет. Кодировщик всегда пишет `<имя>.video.mp4`, а `<имя>.mp4` + появляется только после успешного mux или переименования, поэтому неудачный, отменённый или + беззвучный захват не перезаписывает и не удаляет готовый фильм прежнего экспорта с тем же + именем. Исправлено в `73b9d307048fc44d7f21fa3c829fc5a855d144a6` (`game/film.rs`), проверено + реальной ошибкой ffmpeg и контрольным MP4. + +## Отдельный тяжёлый WASM tier + +```sh +python3 tools/airdef.py test-budgets +python3 tools/test_budget_report.py +python3 tools/test_airdef_build.py +``` + +Сценарий `airdef-mod/tests/budgets/wasm_flat_budget.toml` запускается в изолированном временном `tests/`, +поскольку CLI читает только непосредственные `tests/*.toml`. Обычный `test-wasm` остаётся лёгким. +Ручной workflow `.gitea/workflows/wasm-budget.yml` не добавляет нагрузку на каждый push. + +Исходный профиль запрашивал 64 комплекса и две волны по 80 целей на 4000 блоках, +602 вызова tick и 300 frame, 69 шагов. Позднее обнаружено: второй заказ во время подготовки +первого отклонялся, а fixture не проверял ответ. Поэтому тот прогон **не доказывает перекрытие +160 целей**; сохранённый JSON помечен `coverage_correction`. Новый сценарий проверяет принятие +обоих заказов и отличает requested counts от неизвестного пика в воздухе. +Это не полный лимит расстановки (2000) и не нагрузочный +тест GPU, mixer voices или всех физических world replies: sandbox не выполняет настоящий рендер/звук. + +Текущий default заменён на flat render-world: ground/sections/rays отвечают для плоскости y=64, +GPU не имитируется. Проверяются два принятых налёта по 40 «Орешников» и 64 ПВО; в середине +включаются HD и grid. На тике 2102 assertion подтверждает **184 цели в воздухе**, а не пик за всё время. +Прогон `2932364` прошёл: 271 шаг, 2102 tick и 2052 вызова frame стенда. У AirDef нет собственного +frame hook; эти вызовы не являются нагрузкой на GPU или измерением 10M frame fuel мода. +Точный отчёт: [flat-world budget](measurements/flat-wasm-raid-budget.json). + +С 4 ноября (#122) fixture расширен хвостом умных приказов через UI-путь: 4 РЭБ, `airdef preset 1` +(та же `apply()`, что у кнопки готового состава) и два `airdef raid` — это та же функция +`launch_wave`, что у кнопки ПУСК (`ui_select`), с боевым журналом разведки и EW; оба приказа +приняты (`expect.chat = []`), итого 4 принятых приказа, финальный `/stats` закреплён строкой. +Прогон исправления #122: 283 шага, 3302 tick; max fuel tick 182413686/300M, command 6939810/1e9, +ошибок 0. Отчёт: [flat-world budget](measurements/flat-wasm-raid-budget.json). + +Fixture копируется из тех же байтов, чей SHA записан до сборки. Проверенные 2 принятия и точка +184/2102 заносятся только после success и совпадения `BUDGET_ASSERTED_SHA256` с точной версией файла. +После изменения fixture нужно повторно проверить её и обновить SHA/метаданные; простой список +запрошенных целей не заменяет assertions. Пик остаётся null, если он не измерен. +Отчёт `.horizon-cache/wasm-budget-report.json` содержит SHA/dirty/diff AirDef, pin/identity движка, +API/WIT/артефакт, проверку build identity, сценарий, Wasmtime и исходные memory/fuel limits. +`AIRDEF_BUDGET_REPORT` меняет путь отчёта. Stale identity запрещает запуск, ошибки сборки/тестов +записываются как failed и дают ненулевой exit code. + +Лимиты берутся из исходников: memory 64 MiB; fuel init/tick/frame/event — 2B/300M/10M/1B. +Начиная с продолжения 4 ноября `test-budgets` включает `--fuel-profile` и сохраняет +`measured_fuel.callbacks`: число вызовов, максимум guest fuel, бюджет и ошибки отдельно по callback. +Отсутствие запрошенных счётчиков делает проверку неуспешной, даже если сценарий завершился. +Это не время host/GPU и не peak memory; невызывавшиеся callbacks, включая init до включения профиля, +не получают выдуманного нуля. Первый прогон: tick 2102 вызова, max 106132646/300000000; +command 69 вызовов, max 1013370/1000000000; ошибок нет. Прогон исправления #122 (283 шага, +включая два умных UI-приказа): tick 3302 вызова, max 182413686/300M; command 79 вызовов, +max 6939810/1e9; ошибок нет. +Исторический отчёт без измерения fuel: +[API27 budget report с поправкой покрытия](measurements/api27-wasm-budget.json). +Актуальный чистый API29-прогон с измерением: +[271 шаг, fuel и build identity](measurements/api29-wasm-budget.json). +Актуальный прогон с умными UI-приказами #122 и build identity: +[flat-world budget](measurements/flat-wasm-raid-budget.json). + +Проверены реальный WASM guest с переходами состояний/владельца/мира, сборка клиента и два коротких +offscreen-профиля Metal. При `HORIZON_ACOUSTICS=1` + `HORIZON_NO_AUDIO=1` в игре получено +enabled/request=true, prepared=false, awaiting-backend; HD smoke переходит в ready после подготовки +основного кадра. Это не замер производительности и не прослушивание. + +API28/29 добавляют отдельные `point-lights-status` и `sound-sources-status`: свет подтверждается +после cache/main-view HD обработки, звук — после source-manager apply с live/offline output. +Заявки привязаны к запуску мода и поколению мира; звук также к source epoch. Clear/reload/выход +снимают подтверждение. Это не GPU fence, загрузка сэмпла, наличие голоса или слышимость. +Камеры и их targets изолированы по `(run_serial, local_id)` вместо глобального ID. + +API30 добавляет `camera-status` для камер `set-views` вызывающего запуска: доступность API, +включение пути картинок в последнем отмеченном кадре, принятие заявки поколения и поштучная +готовность каждой камеры. `prepared` означает, что рендерер закодировал кадр камеры в её +texture target, либо кадр мира отрендерен с неё как main-view **этого** запуска: если одинаковый +local ID выбрали два мода, экран использует выбор первого, и milestone получает только он; чужая +картинка с тем же ID чужую камеру не готовит. `enabled` — последний отмеченный кадр был кадром +мира (не заголовок/загрузка). Это не регистрация target и не GPU fence: видимый пиксель, +представление кадра и обход ротации per-frame бюджета не гарантируются. Пустая `set-views` +снимает заявку и готовность; смена мира очищает заявку, выбор main-view и готовность (камеры +другого мира не рисуются), перезагрузка мода начинает заново, а повторная отправка и камера, +отданная в main-view, ждут нового кадра (у такой камеры нет своего picture target). +Проверено двумя настоящими Wasmtime-гостями с одинаковым local ID, пустой заявкой, сменой мира +и reload. `/airdef capabilities` показывает строку камер. + +### GPU-приёмка картинки камер (Metal/offscreen) + +Итоговый engine `278f356aa` с AirDef API30 проверен в запущенной игре, а не только в песочнице: +macOS, Apple M5 (Metal, IntegratedGpu), `--offscreen`, `HORIZON_NO_AUDIO=1`, чистый +`HORIZON_HOME=/tmp/airdef-camera-accept` и копии тестовых миров из scratch-папки прежних прогонов +(`AirDefenseLab`, `Polygon`), ресурсы лаунчера v0.2.91. `.hmod` — из этого дерева +(`python3 tools/airdef.py mod`, API30), не из основного дерева. + +``` +tools/airdef.py install # HORIZON_HOME= +horizon-ctl --socket $H/ctl.sock launch --game horizon/target/fast/horizon-client \ + --world AirDefenseLab --creative --offscreen --wait --resources <26.3> +# мод-команды клиентские: ctl command/chat уходят в консоль сервера, поэтому — через поле чата +horizon-ctl key t tap; horizon-ctl type "/airdef capabilities"; horizon-ctl key enter tap +horizon-ctl screenshot +``` + +Наблюдения (строки `/airdef capabilities`, `поколение` = generation): + +| Когда | Камеры | +| --- | --- | +| камер нет | `API=true, включено=true, запрос=false, подготовлено=false, поколение=1, not-requested, main=нет, []` | +| `/airdef deploy s400` + `/airdef monitor` | `... запрос=true, подготовлено=true, поколение=1, ready, main=нет, [32:ready]` | +| `/airdef monitor` (пустая заявка) | `... запрос=false, ... not-requested, main=нет, []` | +| `/airdef replay film best` | `... main=1996488705, [1996488705:ready]`, затем `main=1996488707` на следующем плане | +| другой мир (AirDefenseLab → Polygon) | `поколение=2, запрос=false, ... not-requested, main=нет, []` | +| там же deploy+monitor | `поколение=2, запрос=true, подготовлено=true, ready, [32:ready]` | + +Снимки подтверждают, что `prepared` — нарисованная картинка, а не регистрация (число различных +цветов, выборка каждого 6-го пикселя): при взгляде в небо (кадр однородный) включение монитора +добавляло панель и picture-область — 477→4867 цветов на кадре; в том же мире A/B +«монитор выключен/включён» в области панели: 199→2080 цветов, +доля тёмной панели 1.2%→48.7%, а в области картинки 110→1771 цветов с доминирующим небом (рисуется +сцена с камеры С-400, а не плоская заглушка «НЕТ СВЯЗИ»). В film-режиме экран показывал карточку +«Готовлю кадры…» (фильм готовит планы), при этом main-view milestone был `ready` — это API-отметка +и отрисовка кадров рендерером, не проверка пикселей фильма. + +`title → тот же мир` не меняет поколение: заявка и milestone сохраняются (смена поколения — это +смена мира), а `enabled` сбрасывается кадром заголовка (note с `world=false`), поэтому `ready` +требует нового кадра мира после возврата; он и наступает (наблюдалось). 8 из 8 запросов при +работающем мониторе дали `ready`. `set_views`/`set_main_view` вызываются модом каждый tick, то есть каждый +tick начинает ожидание нового кадра: выборка между tick и note может честно показать +`awaiting-frame` по порядку вызовов — это не flicker, а порядок tick → render → note. + +Остаток этой приёмки: визуальная оценка человеком, Windows/другие GPU, два запуска с одинаковым +local ID на GPU (покрыто Wasmtime-гостями), reload мода на GPU (покрыт host-тестом), fence/представление +кадров не проверялись. + +Остаток #102: полные query/voice/peak budgets, +GPU/готовый MP4 и платформенная приёмка. Эти результаты текущими тестами не объявляются выполненными. + +## Измеренный fuel и массовые взрывы + +В runtime добавлен выключенный по умолчанию профиль вызовов. У тестового CLI флаг +`test --fuel-profile` выводит mod/callback, число вызовов, максимальный израсходованный guest fuel, +бюджет и число ошибок; результат печатается и при неуспехе сценария. Init до включения профиля +и предыдущие поколения после reload в эти значения не входят. Это гостевые инструкции, +не время CPU, не heap peak и не работа GPU/host. Обычный `test-budgets` без этого флага по-прежнему +не выдаёт свои исходные caps за измеренный расход. + +С профилем воспроизведён остановившийся tick на двух максимальных составах меню и HD: +полный обход height-cache при каждом взрыве доводил его до 300M. `1f28f83` заменяет обход +удалением только затронутого квадрата. В контрольном диагностическом WASM-прогоне после исправления +3602 тика прошли, max tick fuel = 135273690/300M, failures=0. Лимиты не менялись. +Диагностический гость содержит только временный способ запуска точного меню-состава; +это не исходный пользовательский Windows-сеанс. [Данные и воспроизведение](RAID-STABILITY.md). + +С профилем воспроизведён и второй класс — остановка в **событии UI** (не тике) на втором +крупном приказе: переснимок `known_units` на каждом дроне приказа давал `дроны × журнал × +группировка` за одно событие, 1e9 исчерпаны ровно (max command fuel 1000000000/1e9, +failures=1; состояние: 13172 записей журнала, 144 известных живых комплекса). Исправление +(#122, одним снимком на приказ и O(журнал+группировка) за проход) доводит тот же сценарий +до max command fuel 32946177/1e9, failures=0, `/stats` на тике 2602 совпал побайтово. +Это гостевые инструкции, не прирост FPS; приёмка Windows-сеанса внешняя. +[Данные и воспроизведение](RAID-STABILITY.md). diff --git a/docs/GROUND-STRIKES.md b/docs/GROUND-STRIKES.md new file mode 100644 index 0000000..0ae5dd5 --- /dev/null +++ b/docs/GROUND-STRIKES.md @@ -0,0 +1,74 @@ +# Как подготовить удар по вражеской ПУ + +Номера своей техники и вражеских ПУ — разные списки. На карте **M** щёлкните по своей машине: +заголовок карточки показывает её номер. `/airdef sites` выводит номера разведанных вражеских площадок. + +## Боеприпас + +У обычного склада по умолчанию **0 RM48U**. Его обычные перезарядки ПВО — другой запас. +Профиль наземного удара пока есть только у С-400; другие ЗРК таким способом не заряжаются. + +Пример: склад №3, С-400 №2: + +```text +/airdef ground-stock 3 4 +/airdef ground-load 2 +``` + +Первая команда **задаёт остаток** склада равным четырём RM48U, а не прибавляет четыре. +Это подготовка sandbox-сценария; производства этих ракет пока нет. +Вторая загружает одну ракету. Каждый повтор загружает ещё одну, пока хватает запаса и ячеек. +Четыре ТПК общие: например, после первой загрузки будет 3 ракеты ПВО + 1 RM48U. +Вытесненная ракета ПВО остаётся на складе либо возвращается туда грузом ТЗМ. + +Рядом с исправным развёрнутым С-400 и наполненным складом (менее 60 блоков) передача прямая. +Вдали тот же приказ создаёт заявку, если есть запас и живая ТЗМ. Погрузка запрещена во время +движения, перезарядки, подготовки пуска или при отказе пусковой. Нулевой запас не исправляется +перестановкой склада ближе. Карточка склада показывает RM48U отдельно от обычного БК. + +## Откуда берётся разведка + +Она автоматическая. Поставьте и включите РЛС **до налёта**. Рабочая включённая РЛС может заметить **сам пуск** с наземной площадки, +если он попадает в её игровой обзор и не закрыт рельефом. Это даёт оценку позиции; несколько +наблюдений могут её уточнить. Обнаруженная позже летящая цель даёт лишь слабое предположение +о направлении старта — оно не превращается в координаты от ожидания или от количества радаров. + +Команда `/airdef sites` должна показать, что место ПУ определено. Если там только направление +или «место неизвестно», удар не разрешён. Специальной команды, выдающей скрытые координаты, +и отдельного ручного режима разведки сейчас нет. У запусков «за картой» сам пуск не наблюдается. +Чтобы попробовать механику, используйте близкий наземный налёт и открытую местность; далёкая +траектория сама по себе не гарантирует обнаружения площадки. + +## Приказ на удар + +После определения места, например вражеской ПУ №7: + +```text +/airdef sites +/airdef strike 7 +``` + +Или **M → карточка вражеской ПУ → «Нанести удар»**. Кнопка появляется после определения позиции. +Указывать номер своего С-400 в `strike` не надо: мод сам выбирает готовый совместимый носитель +с RM48U в игровой дальности. Сначала проверяется разведка, поэтому её ошибка может появиться +раньше сообщения о незагруженной ракете. + +На носителе резервируется ячейка, открывается крышка, ракета выбрасывается; боеприпас списывается +при выбросе. Пуск остаётся игровой стилизацией, а не моделью реального регламента. + +## Возврат и отмена + +```text +/airdef ground-unload 2 +/airdef ground-cancel 2 +``` + +`ground-unload` у склада выгружает одну RM48U сразу; вдали резервирует её и вызывает ТЗМ для +вывоза. `ground-cancel` отменяет ещё ожидающую доставку/вывоз. Уже забранная ракета находится +в ТЗМ и продолжает путь на склад; команда не телепортирует её обратно в ПУ. + +Возврат имеет приоритет, только пока он выполним: ПУ исправна и развёрнута, маршрут доступен, +ракету ещё не забирает другая ТЗМ, а у склада есть место. Заявка при этом сохраняется, но +невыполнимый вывоз не блокирует ТЗМ: свободная машина исполняет независимые заявки на доставку +RM48U, а как только вывоз снова становится выполнимым, он встаёт в приоритет. Проверено +регрессиями #128, включая консервацию боеприпасов и save-restore посреди маршрута. diff --git a/docs/HD-RENDERER.md b/docs/HD-RENDERER.md index 42deb96..769c84c 100644 --- a/docs/HD-RENDERER.md +++ b/docs/HD-RENDERER.md @@ -368,6 +368,40 @@ HORIZON_HD_TAA=0 --out run-home-bench/out/taa-off` — A/B; `--baseline run-home - Мод-HUD (`set-hud`) теперь рисуется и при `set-main-view` (титры, леттербокс фильма); ванильный HUD по-прежнему скрыт. - Режиссура: `airdef-mod/src/film.rs` (план по записи, варианты ракурсов, проверка рельефа по высотам `Ground::land_state`, ускорение/замедление времени). +### Проверенный жизненный цикл MP4 (4 ноября, pin `57f8b2853`, очистка ошибки энкодера — `73b9d3070`, API 29) + +Реальный короткий экспорт на scratch `HORIZON_HOME`/мире (не на сохранениях игрока), Apple M5, Metal, +ffmpeg 9.0.1. `/airdef replay export` с записанного налёта (4 цели, 4 перехвата), 640×360: + +- готовый файл: h264 + AAC 48 кГц stereo, длительность видео = длительность звука; звук не пустой + (mean −29.4 dB, max −18.0 dB); 2 с при 30 fps — 62 кадра, `sec=1` — 32, `noaudio` — 33 кадра без + звуковой дорожки, `sec=0.01` — 3 кадра; 1920×1080 `sec=1` — 32 кадра. Число кадров в отчёте мода + всегда совпадает с `ffprobe nb_frames`: очередь не теряет кадры на остановке. +- 2–3 кадра сверх `sec` — это кадры, уже находившиеся в полёте на момент остановки (движок их + дописывает); это не потеря хвоста. Отчёт и файл сходятся. +- отмена во время записи (`/airdef replay stop`): сообщение «Экспорт отменён», файла и процессов + ffmpeg не остаётся; сразу после отмены новый экспорт проходит. +- отказ SDK (`size=10000x10000`): сообщение об ошибке, захват не остаётся, следующий экспорт проходит. +- ошибка энкодера (подставной `HORIZON_FFMPEG`, отвечающий на `-encoders`, но падающий при записи): + «Экспорт не удался: … Broken pipe …: fake encoder failed: no such codec»; временных файлов и + процесса нет, а последние строки лога теперь в самом тексте ошибки (сам лог удалён). Готовый + `keep.mp4` прежнего экспорта с тем же именем остался байт-в-байт (sha256), как и беззвучный + `keepnone.mp4` после неудачного `noaudio`-экспорта с тем же именем. Движок `73b9d3070` всегда + пишет `<имя>.video.mp4`; `<имя>.mp4` появляется только после успешного mux/переименования. + Контрольный `ctrl` на итоговой сборке `73b9d3070` — h264 + AAC, 33 кадра, временных файлов нет; + `keep` (h264 + AAC, 32 кадра) и `keepnone` (только h264, 32 кадра) проверялись на той же правке. +- акустика в MP4: пары on/off одного повтора (кнопка мода) со статусами `Акустика … ready` и + `Захват … ready`; в открытом поле send реверба нулевой по построению, в каменном дворе ON тише и + глуше, а остаток после вычитания масштабированного сухого сигнала низкочастотный и + декоррелированный (wet-путь). Цифры, статусы и ограничения — [ACOUSTICS.md](ACOUSTICS.md). +- **выход в заголовок во время экспорта** (до исправления): игра перестаёт тикать на экране + заголовка, но продолжает кадры, поэтому захват писал заголовок; запрос `sec=10` дал 3898 кадров + (129.9 с), временные `video.mp4`/`audio.raw` и ffmpeg оставались, а после возврата в тот же мир + экспорт продолжался. Мод получает кадры и вне мира (`Mod::frame`), поэтому `Session::world_left` + завершает показ с `ExitCause::WorldChange` и одним `CancelCapture`: после исправления при выходе + в заголовок ffmpeg и временные файлы исчезают, повторный вход и следующий экспорт проходят. + Пропуск мира виден по `game.world-id = none`; тик мода вне мира не вызывается. + ## Цветные тени, рябь, кино-пост: что знать преемнику - **Цветные тени.** Ближний каскад (c = 0) рисуется `fs_shadow_near` (листва `MAT_LEAVES` отброшена), потом проход `hd shadow colour` (`Hd::shadow_tint_pass`, цель `Shadows::tint`, очистка в белое) рисует листву (`shadow_tint_leaves`, твёрдые квады, `SHADOW_SOLID`) и полупрозрачные квады секций (`shadow_tint_glass`, по отсортированным индексам; вода не рисуется) тем же `fs_shadow_tint`: rgb умножаются (blend `Dst*src`), альфа = `(расстояние от света − 320)/160` с blend Min — ближайший такой квад. Приёмник в `shadow_tint` (`light.wgsl`, привязка 22) окрашивается, если квад ближе к свету, чем он; 8 точек Vogel радиусом 0.4 блока размывают листву в «пятна». Дальний каскад по-прежнему рисует листву полностью (на границе 36 блоков шов: ближе листва светлее). Цвет не доходит до лучей тумана, воды и дальнего каскада. Цена: `hd shadow colour` 0.25–0.35 мс (Mac, малая сцена). diff --git a/docs/IMPLEMENTATION-STATUS.md b/docs/IMPLEMENTATION-STATUS.md new file mode 100644 index 0000000..4e4e55b --- /dev/null +++ b/docs/IMPLEMENTATION-STATUS.md @@ -0,0 +1,64 @@ +# Состояние исходных 16 задач + +Ветка `kimi-work`, 6 ноября 2026. Issues остаются открытыми по просьбе владельца. +«Реализовано» ниже означает проверенный объём кода, а не закрытие issue и не подтверждение всех платформ. + +Новый проверенный объём: [продолжение пяти задач 4 ноября](CONTINUATION-2026-11-04.md). +#87: typed battery/world/damage diagnostics и адаптеры текста; #91: общий content/sound catalog +для Rust и Python; #102: измеренный fuel, API29 light/source milestones, API30 camera-status +и изоляция камер; #96: массовый deterministic offline capture; #115: authored Lancet/Х-101 +и кадры инстансов. +Текущий engine pin — API30; старые номера/числа ниже описывают предыдущие этапы. +Проблема нормалей модели #123 исправлена в engine `45b43f00d`; парные HD-кадры и ночная проверка Lancet записаны в галерее. + +| Issue | Реализовано | Что остаётся для полной приёмки | +|---|---|---| +| #72 | Upstream Horizon 0.2.97 объединён с форком; сохранена история; локальные тесты и Metal-полигон | Windows/Linux, native HDR, сравнительная производительность | +| #87 | Каноническое SDK-free ядро `airdef-core`, используется модом и сценариями | Дальнейшее отделение текстового/визуального вывода домена от адаптера | +| #88 | Типизированный журнал измеренных переходов; read-only Snapshot для Smoke/Shown; записанные пожары, позы, пуски и зажигание | Перевести оставшиеся model/audio adapters и режиссёра на общий снимок; замер полной 10-минутной записи | +| #89 | Session владеет Live/Replay/Export; явные часы/поколения; единый cleanup; задержанный звук следует часам показа; выход в заголовок завершает показ одним cleanup (хук `frame`, `Session::world_left`); движок освобождает temp/лог ошибки энкодера и не трогает готовый MP4 | Реальный короткий MP4 проверен 4 ноября (звук/видео, отмена, ошибка SDK и энкодера, повторный запуск, совпадение кадров и хвоста очереди, сохранность прежнего MP4). Остаётся: 10-минутный экспорт; приёмка Windows/Linux | +| #91 | Общий каталог доступности и потребители меню/инвентаря; явный RM48U-профиль; корректные подписи очередей; `catalog/specs.json` — единственный источник числовых строк, из него генерируются Rust-таблицы для мода и `airdef-core`, CI проверяет свежесть | Варианты комплексов (разные ракеты/профили по батареям) и игровая приёмка; каталог остаётся статичными данными, не динамическим ECS | +| #92 | Authored overrides, проверка ссылок/PNG/границ, manifest, безопасная транзакционная генерация; registry/texture/icon modules | Дальнейшее ручное улучшение контента относится к #115 | +| #96 | Акустические пути; измеренные синтетические occlusion/echo/reverb fixtures; лицензии и исходные notices в архивах; настоящий MP4 с акустикой: пары on/off, статусы ready, окклюзия/поглощение и диффузный wet-остаток в замкнутой геометрии | Прослушивание, игровой массовый звук/CPU, чистый хвост/RT60 в MP4; точность RT60 пока по допуску 35%, не 15% | +| #102 | Pinned engine/API30, packer/build identity; smoke/acoustics/capture-owner/light/source/owner-scoped camera milestones; fallback/владение; WASM27 и flat stress271; измеренный callback fuel; JSON provenance/failure tests; Metal/no-audio smoke; URL/Git-регрессия; camera-ready по закодированному кадру с per-run/per-world сбросом; GPU-приёмка monitor/main-view на Metal/offscreen (Apple M5) | Старое69-шаговое утверждение о перекрытии отозвано; визуальная/Windows-GPU приёмка картинки камер, полные query/voice/peak budgets, GPU/готовый MP4 и Windows-приёмка остаются | +| #104 | Движок вынесен в `chexit/horizon-airdef`, ветка `airdef`; AirDef использует точный gitlink | Отдельная Windows-проверка миграции старого standalone checkout | +| #107 | ПУ оценивается по наблюдениям: обратный курс не выдаёт точную ПУ | Повторная игровая проверка сценариев с маневрирующими целями | +| #110 | Изменения чанков инвалидируют акустические снимки/кэш; включено в pin и проверено engine-тестами | Внешняя игровая приёмка на целевых машинах | +| #111 | Вне области расчёта источник переходит на fallback вместо старой стены; проверено engine-тестами | Внешняя игровая приёмка на целевых машинах | +| #114 | Навигация учитывает footprint и окончательную authored-геометрию; остановка перед unread frontier; угловые пересечения | Массовая игровая проверка всех новых шасси на сложном рельефе | +| #115 | Три пилота с кадрами до/после, спокойные материалы; authored-проход по боевым и вспомогательным машинам | Крупные планы и точность прототипов ещё промежуточные; остальные летательные аппараты/ракеты, анимации, дальние виды и FPS | +| #116 | Streaming не освобождает busy-guard работающего acoustic worker; проверено engine-тестами | Внешняя игровая приёмка на целевых машинах | +| #118 | Физический С-400/RM48U, четыре общие ячейки, конечные запасы; доставка и обратный вывоз ТЗМ, резерв/отмена, возврат снятой ПВО; обе фазы вывозного маршрута сохраняются; carrier/крышки/выброс/зажигание/разгон в записи; фантомное ПВО удалено | Визуальная приёмка пуска и маршрутов на сложном рельефе; другие носители не добавлены без подтверждённого профиля | +| #128 | Невыполнимый возврат RM48U (ПУ движется/повреждена, маршрут недоступен, заявку уже везёт другая ТЗМ, у склада нет места) больше не блокирует ТЗМ: заявка сохраняется, свободная ТЗМ исполняет независимые заявки на доставку; выполнимый возврат снова в приоритете; консервация боеприпасов и save-restore посреди маршрута покрыты регрессиями | Игровая приёмка маршрутов на сложном рельефе остаётся за #118 | +| #129 | Единые стадии пуска: выброс без мотора (`cold` 20/24 тика у С-400/С-500, 8 у IRIS-T/Тора), зажигание в текущей позиции, departure с плавным ростом тяги/темпа поворота, наведение только после `cold + boost`; рельсовые ракеты получили spool тяги без холодного выброса, P1-SUN — без струи; RM48U согласован на 20 тиков; live/replay/export читают записанное зажигание; `pk`, максимальные скорости и коллизии не менялись | Парные replay-MP4 до/после и кадры сняты на одном мире/seed/сценарии (Metal/offscreen); измеренный сдвиг первой вспышки 0,233 → 0,900 с. Остаётся человеческая визуальная оценка композиции, very-close план выброса; live-видео, Windows/Linux и живое прослушивание не проверялись | + +Для #118 RM48U — явно условный Minecraft-профиль. Существование сочетания опирается на сообщение ведомства, +а игровые параметры не выдаются за реальные. `ground-stock` — команда подготовки sandbox-сценария, +по умолчанию запас нулевой; это не автономное пополнение и не производственная экономика. + +Новые находки внешнего ревью и причины отказа от неверных замечаний: [REVIEW-FOLLOWUP-2026-11-02.md](REVIEW-FOLLOWUP-2026-11-02.md). +Картинки и ограничения художественного прохода: [MODEL-PILOTS.md](MODEL-PILOTS.md). + +Дополнительно по просьбе владельца: [раздельная батарея и холодный пуск](BATTERIES-AND-COLD-LAUNCH.md). +Есть отдельные ПУ С-400/С-500, роли общей РЛС, цепочка подсказка → собственное измерение → пуск, +сохранение конфигурации и запись крышек. Это не закрывает оставшиеся пункты художественной и платформенной приёмки. + +## Дополнительные задачи из ревью + +| Issue | Реализовано | Границы и оставшаяся приёмка | +|---|---|---| +| #119 | Три read-only луча в известной геометрии ослабляют непрямой blast для корпуса/узлов/объектов; проёмы дают частичное воздействие; прямой контакт сохранён | Unknown/stale: прежний урон без защиты, нулевое ожидание; partial blocks считаются занятыми кубами; GUI-проверка разрушения укрытия ещё нужна | +| #120 | Конечное горение, powered/coast, сопротивление и снижение доступного манёвра, один остаточный pk по фактической скорости; swept-встреча; события burnout/energy loss; согласованное прекращение факела/дыма/звука | Игровой профиль, без аэродинамики и реальных параметров; сравнительная выборка 12 рейдов и расширенная сетка 48 рейдов (6 вариантов × 8 seed, батареи/дальности/классы) записаны в measurements; без подтверждённого дефекта параметры не менялись; прослушивание остаётся | +| #121 | On/Off в команде и карте, save/replay; Off не создаёт own measurements и сохраняет сетевой приём; раскрытие работающего радара наблюдателем; SEAD использует last-known и возраст | Условный наблюдатель по расстоянию, без радиотехнической модели и ошибки пеленга; выбор на карте ещё не проверен в GUI | +| #122 | Воспроизведены два класса: tick fuel exhaustion на двух максимальных составах, HD (полный обход height-cache на каждом взрыве заменён удалением нужных ключей; max135.3M/300M) и остановка в UI event на втором крупном приказе (переснимок known_units на каждом дроне заменён одним снимком на приказ и O(журнал+группировка); max command 32.9M/1e9, `/stats` до/после совпал); нативные тесты эквивалентности и второго приказа; WASM fixture с 4 принятыми приказами; callback diagnostics/profile | Требуется приёмка на Windows; без подтверждённого build identity нельзя утверждать ту же причину пользовательского сеанса; FPS/GPU не измерялись | +| #127 | Этапы 1–2. **Картина:** журнал раскрытий заменён картой контактов (`Intel.contacts`: последнее «пуск/излучение/глушение», место, время, метка эмиссии, наблюдённая гибель, счётчик увиденных пусков), уверенность — функция возраста (Confirmed/Probable/Doubtful, TTL 6000), угроза и цены читают только published-параметры и факты наблюдений (launcher-only без донора «слеп»), уничтожение учится только из BSA. **Structured intent:** `Intent` (Plan/Sead/React/Replace) и `Aim` (юнит, since, switches, confidence) отделены от random-выбора; throttle `SWITCH_HOLD=160`, `SWITCH_CAP=3`, `ASSIGN_PER_TICK=16`, `ASSIGN_PER_CONTACT=6`, цена смены `SWITCH_GAIN=120`; в полёте цель меняется только когда картинка её убила/забыла. **Durable queue:** автосейв пишет версионированную секцию `air 1` — дроны, очередь запуска, задания (id, дрон, цель, юнит, версия, приоритет, намерение, версия рельефа), контакты, RLE-рельеф; очередь ограничена 256, устаревшие/фантомные/дублирующие задания отменяются при загрузке (`JOB_TTL=1200`), картина задания восстанавливается на свою версию, старые saves читаются (пустое небо), сценарии воздух не несут, Live/Replay не смешиваются. **Неизвестная земля не бесплатна** (`Relief.unknown=6` у повторного плана). **Объяснимость:** `enemy_picture()` дополнен aims/pending reason/aimed_by/pending_jobs без русского текста в core. Метаморфные тесты скрытых изменений (включая нагрев/замок РЭБ), save/restore посреди незавершённой очереди, детерминизм seed, 40+ массовый ретаргет. Замеры: `docs/measurements/issue-127-intent-queue-{baseline,result}.json` | GUI поверх `enemy_picture()`, игровая приёмка сохранения налёта на Windows/Linux, оценка баланса ew-adaptive (kills 59→58/59→58/56→63, потери 3→3/3→4/4→2 — следствие throttle, параметры не менялись) | + +По #127 реализованы карта контактов, структурированное намерение с throttle и durable-очередь: +autosave хранит дронов, очередь запуска, задания с картиной и рельефом своей версии, поэтому +незавершённый ретаргет продолжается после перезахода. Старые saves без секции `air` читаются как +прежде (пустое небо), сценарии воздушное состояние не несут, replay по-прежнему хранит траектории. +Ракеты-перехватчики, площадки запуска, промежуточный план, статистика волны и RNG не сохраняются — +это ограничение записано в handoff. + +Точные коммиты, формат сохранения, тесты и порядок продолжения: +[CODEX-HANDOFF.md](CODEX-HANDOFF.md). Формальный open-статус не меняется после реализации. diff --git a/docs/INSTALL.md b/docs/INSTALL.md index e63a5bc..1693f2a 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -76,7 +76,7 @@ python3 tools/airdef.py play # или: python3 tools/airdef.py play --world " Клон по HTTPS не требует ключа. Если у тебя есть SSH-ключ в Gitea, можно и так: `git clone ssh://git@gitea.maoniao.team:2222/chexit/airdef.git`. -Шаги по отдельности: `fork` (скачать Horizon и наложить патчи `horizon-patches/`), `game` (собрать игру), +Шаги по отдельности: `fork` (инициализировать закреплённый сабмодуль `horizon/`), `game` (собрать игру), `mod` (собрать `airdef.hmod`), `install` (положить мод в папку игры и включить), `play`. `check` говорит прямо, совпадают ли форк, упаковщик мода, уровень API мода с игрой и ресурсы, и подсказывает, что запустить, если нет (`python3 tools/airdef.py check`; Windows: `py tools\airdef.py check`). Версия Rust diff --git a/docs/MODEL-PILOTS.md b/docs/MODEL-PILOTS.md new file mode 100644 index 0000000..8ded496 --- /dev/null +++ b/docs/MODEL-PILOTS.md @@ -0,0 +1,74 @@ +# Пилотный художественный проход (#115) + +Это промежуточные модели для оценки направления, а не завершённая переработка всего набора. +Изменены С-400 (шасси и ТПК), Гепард (корпус, башня и окраска пушечного блока), Шахед. +На крупных плоскостях наземных пилотов применены авторские однотонные матовые оливковые +материалы; оставлены отдельные материалы стекла, ходовой и металлических деталей. Число +элементов само по себе не является доказательством качества. + +| Пилот | До | После | +|---|---|---| +| С-400 | ![С-400 до](media/model-pilots/before-s400-day.png) | ![С-400 после](media/model-pilots/after-s400-day.png) | +| Гепард | ![Гепард до](media/model-pilots/before-gepard-day.png) | ![Гепард после](media/model-pilots/after-gepard-day.png) | +| Шахед | ![Шахед до](media/model-pilots/before-shahed-day.png) | ![Шахед после](media/model-pilots/after-shahed-day.png) | + +## Что изменено + +- С-400: округлые ступенчатые колёса без звёздчатого пересечения двух квадратов, ступицы, + узкая цельная кабина со стеклом/крышей, рама и четыре оси; четыре ТПК с округлым сечением и обвязкой. +- Гепард: наклонная лобовая часть, отдельная верхняя плита, катки на непрерывной гусенице, + скошенные стороны башни и люки. Корпус, башня и кожухи пушек используют матовый материал; + подвижные пушки и обзорная антенна используют прежние крепления. +- Шахед: более плавный контур носа/крыла, тоньше крыло, переработаны законцовки и задняя часть; + кромка крыла набрана 28 узкими ступенями; пропеллер остаётся отдельной анимируемой частью. + Это стилизация существующего игрового прототипа. +- Наземные пилоты имеют соответствующие обгоревшие варианты. + +Геометрия сделана внутри проекта; исходные игровые текстуры не заимствованы из внешних фотографий. +Визуальные референсы: [Гепард, Bundeswehr](https://www.bundeswehr.de/de/meldungen/flugabwehrkanonenpanzer-gepard-5400072), +[51П6, фотография автора Mike1979 Russia](https://commons.wikimedia.org/wiki/File:51P6_launcher_S-400_Triumf_Army-2022_2022-08-20_2416.jpg). +Это ориентиры внешнего вида, не утверждение о точном воспроизведении конкретной модификации. + +## Метод сравнения и ограничения + +Настоящий Horizon/AirDef, macOS arm64, Apple M5/Metal, offscreen 2560×1440. Временный игровой home, +ровный полигон, ясная погода, day_time 6000, spectator. Для пары используются одинаковые WASM, +движок и сцена: в пакете «до» заменены только одиннадцать JSON пилотов на их версии из `4a3500f`. +Скриншоты не ретушировались. Камера проверена по status после переноса: + +| Пилот | Позиция камеры | yaw / pitch | +|---|---|---| +| С-400 | -4, 114, 25 | -30 / 12 | +| Гепард | 20, 114, 25 | 30 / 18 | +| Шахед | 28, 115, 27 | 35 / 30 | + +РЛС продолжает вращаться; её угол и фаза облаков в паре могут различаться. Пилот Шахеда показан как +статичная модель для сравнения геометрии. Здесь не проверены все ракурсы, полёт/пуск, повреждение +в кино и массовая нагрузка. На этих ракурсах С-400 остаётся условным грузовиком с пакетом, +башня Гепарда выглядит упрощённой, а ступенчатость крыла Шахеда ещё заметна. Камеры слишком +далеки для полной оценки деталей крупного плана: художественная приёмка не завершена. +Скриншоты не показывают последующие модели других наземных систем. Native HDR не включался; HD — отдельный режим рендера мода. + +[Общий вид до, HD](media/model-pilots/before-overview-hd.png) · +[Общий вид после, HD](media/model-pilots/after-overview-hd.png) · +[После ночью, HD](media/model-pilots/after-overview-night-hd.png). + +## Бюджет и редактирование + +| Часть | Элементов до | После | +|---|---:|---:| +| s400_chassis | 35 | 66 | +| s400_tubes | 6 | 29 | +| gepard_hull | 8 | 55 | +| gepard_turret | 12 | 11 | +| gepard_guns | 6 | 6 | +| shahed | 26 | 45 | + +Новые runtime IDs или отдельные части сцены не добавлены. Рост числа граней требует замера на массовом +сценарии; отсутствие роста draw calls или сохранение FPS этим отчётом не обещается. + +Редактируемые JSON: `airdef-mod/source-assets/assets/airdef/models/block/`. +Необязательный экспортёр: `python tools/pilot_models.py` (перезаписывает только эти пилоты). +Установка/валидация: `python tools/gen_models.py`. Габариты для навигации вычисляются из итоговой +геометрии после authored overrides, включая поворот элементов. Стандартная regeneration сохраняет +авторские исходники. Полный набор и художественная приёмка остаются в #115. diff --git a/docs/RAID-STABILITY.md b/docs/RAID-STABILITY.md new file mode 100644 index 0000000..898255b --- /dev/null +++ b/docs/RAID-STABILITY.md @@ -0,0 +1,140 @@ +# Два крупных налёта: проверка стабильности + +Пользовательский отчёт: AirDef часто перестаёт работать при двух больших налётах, +резкого обвала FPS перед этим не замечено. RTX 3060 + Ryzen 5600X — около 50 FPS во время налёта. +Пользователь уточнил: игра продолжает работать, отключается только AirDef; режим эффектов — HD. +Точный build и старый лог не предоставлены. Horizon архивирует предыдущие логи в `logs/YYYY-MM-DD-N.log.gz`, +поэтому запись может сохраниться и без ручного копирования. +Задача: [#122](https://gitea.maoniao.team/chexit/airdef/issues/122). + +**Воспроизведены и исправлены две конкретные причины отключения в этом классе нагрузки.** +Первая — тик и кэш высот (`1f28f83`, ниже). Вторая — событие UI и второй приказ +(`order_raid`, см. раздел «Второй налёт из UI: событие, не тик»). Без старого лога не +утверждается, что Windows-сеанс имел именно их; GPU/FPS отдельно не измерялись. + +## Второй налёт из UI: событие, не тик + +Точный лог Windows: `Horizon: mod airdef stopped in UI event (fuel budget 1000000000, +remaining Some(0))`; backtrace: `ew::Sim::known_units` ← `raid::Sim::order_raid` ← +`airdef::AirDef::launch_wave` ← `ui_select`. Прежний fix `1f28f83` — другой класс: тогда +мод останавливался в **тике** на 300M. + +**Причина.** `order_raid` для каждого дрона заново вычислял снимок известных комплексов +`known_units()` (`choose_target`), а сам `known_units()` на каждую запись журнала разведки +пересканировал весь список комплексов и список уже принятых. Журнал держит каждый пуск, +каждое раскрытие РЭБ и каждую emitting-РЛС за последние 6000 тиков. Стоимость второго +большого приказа росла как `дроны × журнал × размер группировки` внутри одного UI-события. + +**Воспроизведение** (настоящий WASM, измеренный fuel по callback, диагностический гость — +`trigger.patch` гоняет тот же обработчик `ui_select(BTN_LAUNCH)` → `launch_wave` → +`order_raid` с максимальным составом меню; метка callback — `command`, бюджет тот же 1e9): +разнесённая группировка 172 комплекса (40 кластеров × 4 тяжёлых + 12 РЭБ, игрок двигается +шагами сценария — стек в одной точке умирает от одной боеголовки и не воспроизводит), +первый налёт 111 дронов отбит, второй приказ на тике ~2600 при `revealed=13172`, +`known=144` живых. До fix: второй приказ исчерпывает ровно 1e9 и останавливает мод +(`before-trap.txt`); тот же приказ из 11 дронов в том же состоянии — 247805865 +(`before-scaling.txt`). После fix тот же сценарий проходит целиком: max command fuel +32946177/1e9, max tick 236581288/300M, ошибок 0; `/stats` на тике 2602 совпал побайтово +до/после, включая счётчики мира. + +**Фикс.** Один снимок `known_units` на весь приказ, передаётся в `radar_strikes` и +`choose_target`; сам `known_units` — один проход O(журнал + группировка) через множества; +`price_grid_on` переиспользует свой снимок. Семантика планирования не изменилась: +нативный тест сверяет новый снимок с прежним предикатом (дубликаты, погибшие, устаревшие, +чужие id, порядок «новые первыми»). Лимиты не менялись, HD/запись не отключались, +размер волны не ограничивался. + +**Регрессии.** Нативные: эквивалентность снимка и «второй умный приказ с толстым журналом +принят и запущен». Закреплённые WASM: тяжёлый fixture расширен — два умных приказа через +функцию кнопки ПУСК (`airdef preset 1` + `airdef raid` — тот же `launch_wave`, что и у +`ui_select`) с РЭБ и боевым журналом, оба приняты (`expect.chat = []`), 4 принятых приказа +в отчёте; лёгкий контракт проверяет `preset` и отказ пустой волны. Данные и точный +сценарий: [summary.json](measurements/issue-122-ui-fuel/summary.json), +[trigger.patch](measurements/issue-122-ui-fuel/trigger.patch), +[до](measurements/issue-122-ui-fuel/scenario-before.toml), +[после](measurements/issue-122-ui-fuel/scenario-after.toml). Patch — только для +диагностического гостя, в мод не входит. + +Приёмка конкретного Windows-сеанса остаётся внешней: нужны build identity и строки +`latest.log` вокруг остановки. Измерение fuel — не прирост FPS. + +## Первая причина и фикс (тик, `1f28f83`) + +Два максимальных состава меню (11 видов × 40, два принятых приказа), 64 ПВО, расстояние 300, +HD и grid, обслуживаемый плоский мир: tick 1835 исчерпал ровно 300M fuel и мод остановился. +Grid, запись и обновление дыма не были необходимы для этого spike: исследовательские абляции +не убирали остановку. В производственном исправлении эти функции не отключаются. + +`Ground::forget` при каждом взрыве делал `retain` по всему кэшу известных высот. Тот же полный +обход выполнялся при обработке просроченных blast replies. Когда приходило много взрывов, +затраты росли как произведение количества взрывов и размера/ёмкости таблицы высот. + +`1f28f83` удаляет непосредственно координаты прежнего квадрата: при радиусе 8 это 17×17 ключей, +а не просмотр всей таблицы. Вызов `world.touched` сохранён. Тест сравнивает весь результат со +старым predicate, включая отрицательные координаты и границы; правила урона и лимиты не менялись. + +Контрольный диагностический гость с полными записью, дымом, HD и grid, без phase markers: +421 шаг, 3602 тика — PASS; max tick fuel 135273690/300M, command 92658271/1B, failures=0. +Финальные показатели: 0 в воздухе, 209 сбито, 3435 поражений, 15 столкновений. +На тике 1834 до прежней остановки вывод `/stats` до/после совпал побайтово, +включая счётчики мира. Это не утверждение о равенстве каждого скрытого поля симуляции. + +Для воспроизведения огромного меню-состава использован временный hook команды mix, +вызывающий тот же `WaveConfig/order_raid`; сам hook **не входит в мод**. +Исходник — `10f24b5`, после фикса добавлен только новый `forget`. Данные, hook и точный +сценарий: [summary.json](measurements/max-raid-fuel/summary.json), +[trigger.patch](measurements/max-raid-fuel/trigger.patch), +[scenario.toml](measurements/max-raid-fuel/scenario.toml), +[до](measurements/max-raid-fuel/before.txt), [после](measurements/max-raid-fuel/after.txt). +Patch предназначен для отдельного диагностического worktree, а не для обычной игры. +Два checkpoint-лога намеренно заканчиваются несовпадением с `probe`, чтобы получить `/stats`; +это не реальные traps. Успешный полный прогон отдельно в `after.txt`. + +Публичный native-тест `two_maximum_menu_raids_with_answered_flat_terrain` использует настоящий +максимальный `RaidOrder` без hook, terrain replies и запись. Он также прошёл. + +## Найденные пробелы старых тестов + +В первом `wasm_budget.toml` два приказа отправлялись без тика между ними. `order_raid` отклоняет +второй во время подготовки первого; ответ не проверялся. Кроме того, TestGame не отвечал на +render ground/sections/rays: пуски могли ждать неизвестную землю вместо полноценного полёта. +69 успешных шагов не подтверждали 160 одновременно летящих целей. Старый отчёт сохранён с поправкой. + +## Что проверено теперь + +- Native: точный состав двух меню-пресетов «Массированный удар», 61 комплекс, плоский мир с + ответами геометрии и встроенной записью. Оба приказа приняты, все 72 цели запущены, + перекрытие проверено по числу одновременно живых целей. +- Native: два приказа 40+40 «Орешников» с разделением, многими суббоеприпасами и окончанием записи. +- Actual WASM: 64 комплекса, два принятых приказа 40+40 «Орешников», плоскость y=64 с ответами + ground/sections/rays. В середине включаются shader hd и grid. 271 шаг прошёл; + на тике 2102 проверено **184 цели в воздухе**. Пик за весь прогон не измеряется. +- Кадры чередуются с тиками. 2052 вызова frame стенда не означают GPU-рендер: + у AirDef нет собственной реализации frame hook, основная работа идёт в tick и событиях. + +Отчёт clean `2932364`, engine `ed7615d36`: +[flat-wasm-raid-budget.json](measurements/flat-wasm-raid-budget.json). +Memory 64 MiB и fuel init/tick/frame/event 2B/300M/10M/1B не увеличивались. +Отчёт фиксирует исходные лимиты, не измеренный расход fuel/peak memory. + +```sh +python3 tools/airdef.py test-budgets +cargo test --release --manifest-path airdef-core/Cargo.toml --test scenarios overlapping_ +``` + +Это проверка гостевого кода и контрактов с синтетическим host-миром. Она не воспроизводит +RTX-драйвер, реальный рельеф/чанки, звук и стоимость HDR, shadow/light/smoke на GPU пользователя. +Включение HD в fixture проверяет путь подготовки модовых данных, а не графическую производительность. + +## Диагностика следующего воспроизведения + +В `latest.log` после остановки теперь записываются mod ID, callback, заданный и оставшийся fuel, +полная цепочка ошибки. Сообщение OutOfFuel больше не объявляет тяжёлую работу бесконечным циклом. +Проверка настоящим WASM `/spin` подтверждает, что при fuel trap останавливается только нарушивший мод. +Это не доказывает, что пользовательский случай имеет ту же причину. + +Нужны build identity и строки Windows `latest.log` вокруг остановки, состав двух налётов, +HD/обычный профиль, открытые grid/monitor/replay и ответ: закрылась ли игра целиком. +До этих данных не подменять диагноз увеличением fuel, отключением записи или обрезанием целей. +Измерение FPS вести отдельно от поиска trap: стабильный FPS не исключает лимит WASM, а прошедший +WASM-тест не исключает ошибку GPU или native host. diff --git a/docs/REPO-MAP.md b/docs/REPO-MAP.md index 0e7811f..b46b24e 100644 --- a/docs/REPO-MAP.md +++ b/docs/REPO-MAP.md @@ -7,10 +7,13 @@ | Путь | Что это | |---|---| | `airdef-mod/src/`, `airdef-mod/shaders/` | Мод на Rust (WASM) для форка Horizon | +| `airdef-core/` | SDK-free сборка того же ядра из `airdef-mod/src/sim/`; не отдельная копия исходников | +| `airdef-mod/src/sim/battery.rs`, `launch.rs`, `logistics.rs` | Конфигурация батареи, физические ячейки/крышки и снабжение | +| `docs/CODEX-HANDOFF.md`, `docs/IMPLEMENTATION-STATUS.md` | Техническая передача разработки и фактический статус задач | | `airdef-mod/Cargo.toml`, `horizon-mod.toml` | Сборка мода | | `airdef-mod/tests/`, `airdef-mod/examples/` | Тесты и примеры | | `airdef-mod/NOTES-terrain-guidance.md` | Рабочие заметки по рельефу | -| `horizon/`, `HORIZON_COMMIT`, `horizon-patches/` | Форк Horizon и патчи к нему | +| `horizon/`, `.gitmodules` | Закреплённый сабмодуль отдельного форка Horizon | | `tools/airdef.py` | Сборка мода и установка в папку игры | | `tools/gen_models.py`, `tools/gen_tex.py`, `tools/gen_sounds.py` | Генераторы контента (см. ниже) | | `tools/acoustics_check.py`, `tools/hd_bench.py` | Проверочные скрипты | @@ -30,13 +33,33 @@ Команды: +- `python3 -m pip install -r tools/requirements.txt` — зависимости генератора (закреплён Pillow). +- `python3 tools/gen_catalog.py` — обновить Rust-константы после изменений `catalog/content.json`, `catalog/specs.json` или `catalog/sounds.json`; `--check` проверяет актуальность. - `python3 tools/gen_models.py` — перегенерировать. - `python3 tools/gen_models.py --check` — только сравнить, выход 1 при различиях. -`tools/gen_sounds.py` создаёт `airdef-mod/assets/airdef/sounds/*.ogg` и `sounds.json`. Этот генератор не использует manifest и не входит в проверку `gen_models.py`. +`catalog/specs.json` — единственный источник числовых профилей симуляции: строки `DroneSpec`, `MissileSpec`/`MissileKind` (включая RCS, seeker, звук пуска и голос мотора), `SamSpec` (оружие, части моделей, приводы, датчик), общие константы (гравитация, высота раскрытия «Орешника», логистика) и именованные наборы частей. `tools/gen_catalog.py` создаёт из него `airdef-mod/src/sim/specs_generated.rs` (включается в `mod.rs`; тот же файл компилирует `airdef-core`) и `airdef-mod/src/sim/unit_specs_generated.rs` (включается в `units.rs`). Эти два файла не редактировать вручную. `tools/airdef_assets/catalog.py::load_specs` проверяет схему, звуковые ссылки, наборы kinds против `content.json`, ссылки на именованные части и заполненность всех строк; `gen_catalog.py --check` в CI ловит расхождение. Значения — игровой масштаб, не реальные характеристики техники. + +`catalog/sounds.json` хранит звуковые ID, дальности и наличие дальнего варианта; `tools/gen_catalog.py` экспортирует ID для Rust, а `tools/gen_sounds.py` создаёт `airdef-mod/assets/airdef/sounds/*.ogg` и `sounds.json`. Этот генератор не использует manifest и не входит в проверку `gen_models.py`. ## Архив: `legacy/` Первая версия мода: датапак на командах (`legacy/airdef-datapack/`) и контент-мод (`legacy/airdef-content/`) с генератором `legacy/gen_content.py`. Ничего из текущей сборки на него не ссылается: ни скрипты в `tools/`, ни `install.sh`, ни `Cargo.toml`, ни исходники мода. Не собирается и не устанавливается. Оставлен как история. Не редактировать и не удалять без отдельного решения. `legacy/airdef-content/data/` сейчас не отслеживается git и не входит в эту карту. + +## Авторские и сгенерированные ресурсы + +- `tools/gen_models.py` генерирует геометрию моделей и управляет сборкой набора. +- `tools/airdef_assets/registry.py` создаёт blockstate/item registry, локализацию и data JSON. +- `tools/airdef_assets/bounds.py` экспортирует габариты итоговых моделей для headless-навигации; authored overrides входят в расчёт. +- `tools/gen_tex.py` отвечает за вывод процедурных текстур и иконок; переиспользуемые примитивы палитры также находятся здесь. +- `tools/airdef_assets/texture_catalog.py` связывает стабильную палитру и процедурные функции с текстурами моделей, не смешивая каталог с геометрией. +- `catalog/content.json` задаёт идентичности техники и предметов и оформление предметов; `tools/airdef_assets/icons.py` читает его и содержит чистую функцию рендера иконки с явными параметрами `(name, color, seed)`. +- `tools/airdef_assets/pipeline.py` проверяет manifest, рассчитывает безопасный план обновления и откатывает записи при исключении. +- `airdef-mod/source-assets/assets/airdef/` — источник авторских переопределений. Генератор объединяет его с процедурными ресурсами во временном дереве, проверяет ссылки/JSON/PNG, затем устанавливает runtime-файлы. +- `airdef-mod/assets/` и `airdef-mod/data/` — упакованные generated output. Не редактируйте их вручную; добавляйте авторские исходники в `source-assets`. +- `airdef-mod/generated-manifest.txt` хранит SHA-256 только для управляемого результата. Неизменённые старые outputs можно заменить или удалить; посторонние и вручную правленные файлы блокируют конфликтующий запуск. +- `python3 tools/test_gen_assets.py` проверяет manifest, защиту ручных файлов, откат при ошибке записи, детерминизм и полный цикл authored icon override/validation. + +`airdef-mod/src/sim/generated_bounds.rs` также управляется manifest: это данные геометрии, а не файл для ручного редактирования. Пилотный арт и сравнения — в `docs/MODEL-PILOTS.md`. diff --git a/docs/REVIEW-FOLLOWUP-2026-11-02.md b/docs/REVIEW-FOLLOWUP-2026-11-02.md new file mode 100644 index 0000000..279a714 --- /dev/null +++ b/docs/REVIEW-FOLLOWUP-2026-11-02.md @@ -0,0 +1,47 @@ +# Сверка дополнительного ревью реалистичности + +Полученное автором внешнее ревью проверено по `kimi-work` на `ac866d6`, а не принято как готовая +спецификация. Просмотрены текущие исходники и все 118 прежних issues. Проверку независимо помогли +провести GPT-6.1 Terra и GPT-6 Luna. Ниже — результат статического анализа, не новый балансировочный полигон. + +## Новые самостоятельные задачи + +- [#119 — укрытие от непрямого взрыва](https://gitea.maoniao.team/chexit/airdef/issues/119). + sim/damage.rs::blast_damage уменьшает здоровье корпуса, узлов и объектов по расстоянию, + не получает геометрию. Ограниченная оценка защищённости лучами даст полезное взаимодействие с постройками. +- [#120 — энергия и геометрия перехвата](https://gitea.maoniao.team/chexit/airdef/issues/120). + Ускорение и swept closest approach уже есть, поэтому утверждение «геометрии нет» неверно. + Самостоятельный этап — конечная работа двигателя, coast и зависимость от фактического состояния ракеты. +- [#121 — радиомолчание](https://gitea.maoniao.team/chexit/airdef/issues/121). + Off/On собственного поиска с отдельной работой сетевого канала и осмысленным раскрытием позиции. + Это новый игровой выбор, а не требование воспроизводить реальные радиотехнические процедуры. + +## Дополнения к существующим задачам + +- В [#93](https://gitea.maoniao.team/chexit/airdef/issues/93) добавлен комментарий: last-known точка + есть, но часть планировщика после фильтра по известному ID читает live Sam.pos. Это пробел прежней + приёмки, а не новый дубль. Некоторые пути adapt уже используют старые точки. +- В [#46](https://gitea.maoniao.team/chexit/airdef/issues/46) добавлен комментарий: первое радарное + измерение копирует точные kind/vel, хотя ident ещё Unknown. NEAR<=24 намеренно описан как исключение + видимости — его стоит рассматривать как выбор политики честного режима, не выдавать за случайный баг. +- Ответные удары и боекомплект уже охвачены #118; наблюдаемая разведка ПУ — #107; каталог/варианты + вооружения — #91; художественные модели — #115; диагностика — #18. + +## Что в отчёте не подтвердилось или требует оговорок + +- «Обратная дальность» в шуме: сейчас sigma = 0.8 + dist * 0.004 (или 0.3 при lock), она растёт с дальностью. +- «160 очередей ×10 снарядов =1600 у Гепарда»: GunSpec.shell=10 — **скорость снаряда в блоках за тик**, + не число снарядов в очереди. Это прямо определено в units.rs и используется в упреждении/скорости трассера. + Число физических выстрелов на условную очередь пока не задаётся этим полем; пересчёт запаса до 68 + по такому умножению был бы необоснованным изменением баланса. +- Разгон перехватчика, относительное движение в наведении и проверка сближения за тик уже реализованы. + Новый #120 уточняет остаток и запрещает добавлять второй независимый бросок pk. +- Наземные способности комплексов нельзя свести к «все могут» или именной blacklist. #118 уже различает + пушечный наземный огонь, ракетный огонь по наблюдаемой цели и удар по удалённым координатам. +- Название PAC-3 само по себе не задаёт один магазин: производитель двигателей + [L3Harris различает 16 PAC-3 и 12 PAC-3 MSE на M903](https://www.l3harris.com/en-gb/all-capabilities/pac-3-mse). + Поэтому ни автоматическое «PAC-3 всегда 16», ни переименование текущей игровой модели в PAC-2 + без выбора полного согласованного профиля не выполняется. Tor/9М338 и другие варианты также требуют + проверки конкретной модификации; уменьшенный игровой запас может быть явной условностью. + +Состояния прежних issues не менялись. Добавлены только три самостоятельных enhancement и комментарии. diff --git a/docs/VEHICLE-MODELS.md b/docs/VEHICLE-MODELS.md new file mode 100644 index 0000000..2715d13 --- /dev/null +++ b/docs/VEHICLE-MODELS.md @@ -0,0 +1,33 @@ +# Наземная техника: художественный проход (#115) + +Это промежуточная стилизация, а не точные копии машин и не завершённая приёмка крупного плана. Существующие идентификаторы, составные части и игровые крепления сохранены. Исходники — `airdef-mod/source-assets/assets/airdef/models/block/`, авторские экспортёры — `tools/vehicle_models.py` и `tools/support_models.py`. Использованы авторские матовые оливковые материалы и существующие игровые текстуры стекла, ходовой, антенн и металла. + +- **Панцирь-С1:** четырёхосное шасси с цельной застеклённой кабиной, отдельной палубой, башней и пусковым блоком. Камуфляж крупных плоскостей заменён матовым материалом. +- **Patriot:** четырёхосный тягач в духе HEMTT M983 и двухосный лафет; ракеты и их игровая компоновка не менялись. [Ориентир: семейство HEMTT, Army ODIN](https://odin.t2com.army.mil/WEG/Asset/M977_HEMTT_American_8x8_Heavy_Expanded_Mobility_Tactical_Truck_%28HEMTT%29_Cargo_Truck). +- **IRIS-T SLM / NASAMS:** четырёхосный носитель IRIS-T с рамой и буксируемый двухосный лафет NASAMS. Сохранены сетки из восьми и шести контейнеров. Ориентиры: [Diehl Defence](https://new.diehl.com/defence/de/produkte/bodengebundene-luftverteidigung/iris-t-slm2) и [Kongsberg](https://www.kongsberg.com/kda/what-we-do/defence-and-security/integrated-air-and-missile-defence/nasams-air-defence-system/nasams-multi-missile-launcher/). Лафет NASAMS используется и для P1-SUN. +- **Бук-М2 / Тор-М2:** гусеничные шасси с семью катками на сторону и разными модулями; сохранены четыре направляющие «Бука» и восемь игровых ячеек «Тора». Последнее — существующая игровая компоновка, а не утверждение об аутентичном боекомплекте конкретного варианта. Ориентиры: [описание «Бук-М2Э»](https://roe.ru/pdfs/pdf_2699.pdf) и [гусеничный «Тор-М2Э»](https://roe.ru/pdfs/pdf_6301.pdf). +- **Служебная техника:** условные четырёхосные машины РЛС, ТЗМ с неподвижным краном и РЭБ с фургоном и антенной; игровой склад с ящиками и контейнерами; прожектор на отдельном поворотном основании. Эти объекты не заявлены копиями конкретных образцов. + +## Примеры в игре + +Снимки сделаны в изолированном мире Horizon, offscreen, macOS/Metal, без звука, при дневном освещении. Видны действующие составные модели, размещённые через `/airdef deploy`; передний ряд на последнем кадре — отдельные статичные обгоревшие блоки, а не целые собранные уничтоженные комплексы. В ряде мест полигон не загрузился по краям: кажущееся зависание над пропастью в этих сценах не подтверждает корректную посадку техники на землю. + +| Панцирь | Patriot | +|---|---| +| ![Панцирь](media/vehicle-pass/pantsir-front.png) | ![Patriot](media/vehicle-pass/patriot-front.png) | + +| IRIS-T | Бук после исправления решётки | +|---|---| +| ![IRIS-T](media/vehicle-pass/irist-back.png) | ![Бук после исправления](media/vehicle-pass/buk-fixed.png) | + +| ТЗМ | Обгоревшие части | +|---|---| +| ![ТЗМ](media/vehicle-pass/tzm-front.png) | ![Обгоревшие части](media/vehicle-pass/burnt-line.png) | + +На [первом кадре «Бука»](media/vehicle-pass/buk-front.png) обнаружено пересечение внутренней ракеты с решёткой РЛС. Решётка сужена и повторно проверена в игре: новый кадр выше показывает зазор между ней и ракетой. Повторный кадр снят ближе и с другого ракурса, поэтому это проверка исправления, а не сравнение при идентичной камере. Повторный запуск использовал engine `86b1014a9` (API26); основной проход — `209559080`. + +## Бюджет и ограничения проверки + +Число элементов основных новых частей: Панцирь — 79 без антенны; Patriot — 93 у тягача и лафета без пускового блока и радара; IRIS-T — 76 без отдельного радара; NASAMS — 42 без отдельного радара; Бук — 72; Тор — 65. Машины РЛС, ТЗМ и РЭБ — 62, 65 и 63; склад — 9; основание и голова прожектора — 9. Это не замер FPS или количества draw calls. + +Проверены статичные дневные ракурсы развёрнутых систем и общий вид части обгоревших блоков. Не проверены весь диапазон поворотов и углов наведения, пуски, движение и сцепка в движении, полноценное уничтожение в симуляции, все обгоревшие составные части, ночное освещение и массовая производительность. Снимки также не заменяют проверки моделей вблизи: особенно стоит проверить посадку антенны РЛС и прожектор при повороте. Прежние общие модели ракет в полёте для «Бука» и «Тора» не изменены. diff --git a/docs/licenses/GPL-3.0.txt b/docs/licenses/GPL-3.0.txt new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/docs/licenses/GPL-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/docs/licenses/HORIZON-MIT.txt b/docs/licenses/HORIZON-MIT.txt new file mode 100644 index 0000000..e959a07 --- /dev/null +++ b/docs/licenses/HORIZON-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ludovic Marie and the Horizon contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/licenses/README.md b/docs/licenses/README.md new file mode 100644 index 0000000..e39e84e --- /dev/null +++ b/docs/licenses/README.md @@ -0,0 +1,15 @@ +# Notices for the bundled Horizon AirDef fork + +Upstream Horizon portions retain their MIT notices (HORIZON-MIT.txt). +The fork includes SPR-derived GPL-3.0-only source files. Its existing notice explains the GPL terms +for a combined build: `horizon/crates/horizon-client/src/acoustics/THIRD_PARTY_SPR.md`. +Game archives also include that notice and Horizon THIRD_PARTY_NOTICES.md. +GPL-3.0.txt is the unmodified license from Sound Physics Remastered commit +453654ece086f40c97712d9016baa4dee7c4f6d2: +https://github.com/henkelmax/sound-physics-remastered/blob/453654ece086f40c97712d9016baa4dee7c4f6d2/LICENSE + +Corresponding fork source: https://gitea.maoniao.team/chexit/horizon-airdef +AirDef source and build scripts: https://gitea.maoniao.team/chexit/airdef +The exact engine revision is the `horizon/` gitlink; packaged build-identity.json records the pin. +Build instructions: docs/ENGINE.md and docs/INSTALL.md in the AirDef source. +This notice does not change the licenses of the respective source files. diff --git a/docs/measurements/api27-wasm-budget.json b/docs/measurements/api27-wasm-budget.json new file mode 100644 index 0000000..a6a5bb0 --- /dev/null +++ b/docs/measurements/api27-wasm-budget.json @@ -0,0 +1,65 @@ +{ + "airdef": { + "diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "dirty": false, + "sha": "aab3e784b8a87d3af0741b8c47d3351cd68dc152" + }, + "api_level": 27, + "artifact_sha256": "faecb18f25210e694fb53a1f6f99afe4abc3060a68a0376555e8b1dc9114d612", + "build_identity": { + "checks": { + "airdef_diff_sha256": true, + "airdef_dirty": true, + "airdef_sha": true, + "artifact_sha256": true, + "engine_identity": true, + "engine_pin": true, + "wit_sha256": true + }, + "matches": true, + "path": "airdef-mod/target/horizon-mod/build-identity.json" + }, + "command": "test-budgets", + "coverage_correction": { + "confirmed_peak_airborne": null, + "reason": "Second raid was requested during planning and not accepted; original fixture did not assert feedback. The runtime pass does not confirm 160 active or launched units.", + "second_raid_accepted": false, + "valid_double_raid_proof": false + }, + "engine": { + "identity": "c89c78c47047fcfc0dfb0b8968600c650bf12d2b", + "pin": "c89c78c47047fcfc0dfb0b8968600c650bf12d2b" + }, + "exit_code": 0, + "output": " Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.34s\n Running `/horizon/target/debug/cargo-horizon-mod horizon-mod test --hmod /airdef-mod/target/horizon-mod/airdef.hmod budget`\nrunning 1 scripts against /airdef-mod/target/horizon-mod/airdef.hmod\ntest tests/wasm_budget.toml ... ok (69 steps)\ntest result: ok. 1 passed\n", + "runtime_limits": { + "fuel_per_call": { + "event": 1000000000, + "frame": 10000000, + "init": 2000000000, + "tick": 300000000 + }, + "memory_bytes": 67108864, + "metering": "source_configured; measured_per_scenario: unavailable", + "source": "horizon/crates/horizon-mods/src/runtime.rs", + "wasmtime_component_model": true, + "wasmtime_fuel_metering": true + }, + "scenario": "tests/budgets/wasm_budget.toml", + "scenario_counts": { + "air_defense_units": 64, + "frames": 300, + "layout_unit_limit": 2000, + "max_units_per_raid": 80, + "raids": 2, + "requested_raid_units": 160, + "scripts": 1, + "steps": 69, + "ticks": 602 + }, + "scenario_sha256": "58490798d6e70910e188ffe8fd282fbf975fec5548287561b227fc53e06bf7e6", + "schema": 1, + "status": "passed", + "wasmtime_version": "48.0.7", + "wit_sha256": "14c57b73f144888e04e27b77a6dfa29098c6b0c27c0383bb96aedaccb4c0a1d4" +} diff --git a/docs/measurements/api29-wasm-budget.json b/docs/measurements/api29-wasm-budget.json new file mode 100644 index 0000000..56554eb --- /dev/null +++ b/docs/measurements/api29-wasm-budget.json @@ -0,0 +1,85 @@ +{ + "airdef": { + "diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "dirty": false, + "sha": "56339b076e3dad9c6825ddbc73fdfe6b9a2727bc" + }, + "api_level": 29, + "artifact_sha256": "f3720d58e18210b2e962ae924306a18cbf12c28e033c2bf862ad6a1a0db462b6", + "build_identity": { + "checks": { + "airdef_diff_sha256": true, + "airdef_dirty": true, + "airdef_sha": true, + "artifact_sha256": true, + "engine_identity": true, + "engine_pin": true, + "wit_sha256": true + }, + "matches": true, + "path": "/Users/chexit/Documents/ChatGPT/reviewer-horizon/airdef-kimi-work/airdef-mod/target/horizon-mod/build-identity.json" + }, + "command": "test-budgets", + "engine": { + "identity": "57f8b2853c4443f7e3ff5701600cbb262b53118f", + "pin": "57f8b2853c4443f7e3ff5701600cbb262b53118f" + }, + "exit_code": 0, + "measured_fuel": { + "available": true, + "callbacks": [ + { + "budget": 1000000000, + "callback": "command", + "calls": 69, + "failures": 0, + "max_used": 1013387, + "mod": "airdef" + }, + { + "budget": 300000000, + "callback": "tick", + "calls": 2102, + "failures": 0, + "max_used": 106132646, + "mod": "airdef" + } + ], + "scope": "guest callback fuel; excludes host CPU, memory and GPU time" + }, + "output": " Compiling horizon-mods v0.1.0 (/Users/chexit/Documents/ChatGPT/reviewer-horizon/airdef-kimi-work/horizon/crates/horizon-mods)\n Compiling cargo-horizon-mod v0.1.0 (/Users/chexit/Documents/ChatGPT/reviewer-horizon/airdef-kimi-work/horizon/crates/cargo-horizon-mod)\n Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.70s\n Running `/Users/chexit/Documents/ChatGPT/reviewer-horizon/airdef-kimi-work/horizon/target/debug/cargo-horizon-mod horizon-mod test --hmod /Users/chexit/Documents/ChatGPT/reviewer-horizon/airdef-kimi-work/airdef-mod/target/horizon-mod/airdef.hmod --fuel-profile budget`\nrunning 1 scripts against /Users/chexit/Documents/ChatGPT/reviewer-horizon/airdef-kimi-work/airdef-mod/target/horizon-mod/airdef.hmod\nfuel airdef command: calls=69 max=1013387 budget=1000000000 failures=0\nfuel airdef tick: calls=2102 max=106132646 budget=300000000 failures=0\ntest tests/wasm_budget.toml ... ok (271 steps)\ntest result: ok. 1 passed\n", + "runtime_limits": { + "fuel_per_call": { + "event": 1000000000, + "frame": 10000000, + "init": 2000000000, + "tick": 300000000 + }, + "memory_bytes": 67108864, + "metering": "source_configured", + "source": "horizon/crates/horizon-mods/src/runtime.rs", + "wasmtime_component_model": true, + "wasmtime_fuel_metering": true + }, + "scenario": "tests/budgets/wasm_flat_budget.toml", + "scenario_counts": { + "accepted_raids_asserted": 2, + "air_defense_units": 64, + "frames": 2052, + "layout_unit_limit": 2000, + "peak_airborne_observed": null, + "raid_requests": 2, + "raid_units_requested": 80, + "scripts": 1, + "snapshot_airborne_asserted": 184, + "snapshot_tick": 2102, + "steps": 271, + "ticks": 2102 + }, + "scenario_render_world": "flat at y=64", + "scenario_sha256": "bcc0359289ab20ab3de608bcc130f72d2cf88f9f6fbc470d7b6846e9db5d11df", + "schema": 1, + "status": "passed", + "wasmtime_version": "48.0.7", + "wit_sha256": "dded023a150a5f916bc5d935b7f891806728e0118eb7107ad0551d2aad8c70a5" +} diff --git a/docs/measurements/flat-wasm-raid-budget.json b/docs/measurements/flat-wasm-raid-budget.json new file mode 100644 index 0000000..6ba70b6 --- /dev/null +++ b/docs/measurements/flat-wasm-raid-budget.json @@ -0,0 +1,93 @@ +{ + "airdef": { + "diff_sha256": "143fbab5f1fd60981df9ef8ef3081b08a4102507f2588c6942924a7325b5139b", + "dirty": true, + "sha": "e6a70004406b34ea17884875ce236a5fb4b849e4" + }, + "api_level": 29, + "artifact_sha256": "c4ef7d759524eb053ccd55f0eccdcb49c91954201eb4e0ad923ee9b2828efc4e", + "build_identity": { + "checks": { + "airdef_diff_sha256": true, + "airdef_dirty": true, + "airdef_sha": true, + "artifact_sha256": true, + "engine_identity": true, + "engine_pin": true, + "wit_sha256": true + }, + "matches": true, + "path": "/private/tmp/issue-122-raid/airdef-mod/target/horizon-mod/build-identity.json" + }, + "command": "test-budgets", + "engine": { + "identity": "73b9d307048fc44d7f21fa3c829fc5a855d144a6", + "pin": "73b9d307048fc44d7f21fa3c829fc5a855d144a6" + }, + "exit_code": 0, + "measured_fuel": { + "available": true, + "callbacks": [ + { + "budget": 1000000000, + "callback": "command", + "calls": 79, + "failures": 0, + "max_used": 6939810, + "mod": "airdef" + }, + { + "budget": 10000000, + "callback": "frame", + "calls": 2202, + "failures": 0, + "max_used": 77, + "mod": "airdef" + }, + { + "budget": 300000000, + "callback": "tick", + "calls": 3302, + "failures": 0, + "max_used": 182413686, + "mod": "airdef" + } + ], + "scope": "guest callback fuel; excludes host CPU, memory and GPU time" + }, + "output": " Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s\n Running `/private/tmp/issue-122-raid/horizon/target/debug/cargo-horizon-mod horizon-mod test --hmod /private/tmp/issue-122-raid/airdef-mod/target/horizon-mod/airdef.hmod --fuel-profile budget`\nrunning 1 scripts against /private/tmp/issue-122-raid/airdef-mod/target/horizon-mod/airdef.hmod\nfuel airdef command: calls=79 max=6939810 budget=1000000000 failures=0\nfuel airdef frame: calls=2202 max=77 budget=10000000 failures=0\nfuel airdef tick: calls=3302 max=182413686 budget=300000000 failures=0\ntest tests/wasm_budget.toml ... ok (283 steps)\ntest result: ok. 1 passed\n", + "runtime_limits": { + "fuel_per_call": { + "event": 1000000000, + "frame": 10000000, + "init": 2000000000, + "tick": 300000000 + }, + "memory_bytes": 67108864, + "metering": "source_configured", + "source": "horizon/crates/horizon-mods/src/runtime.rs", + "wasmtime_component_model": true, + "wasmtime_fuel_metering": true + }, + "scenario": "tests/budgets/wasm_flat_budget.toml", + "scenario_counts": { + "accepted_raids_asserted": 4, + "air_defense_units": 68, + "frames": 2202, + "layout_unit_limit": 2000, + "peak_airborne_observed": null, + "raid_requests": 2, + "raid_units_requested": 80, + "scripts": 1, + "snapshot_airborne_asserted": 184, + "snapshot_tick": 2102, + "steps": 283, + "ticks": 3302 + }, + "scenario_render_world": "flat at y=64", + "scenario_sha256": "134cb4667691cf9faa639ed37120941d829cfc13ce45e9638582c409d9e0dd29", + "schema": 1, + "status": "passed", + "wasmtime_version": "48.0.7", + "wit_sha256": "dded023a150a5f916bc5d935b7f891806728e0118eb7107ad0551d2aad8c70a5" +} diff --git a/docs/measurements/issue-120-balance.csv b/docs/measurements/issue-120-balance.csv new file mode 100644 index 0000000..31ad250 --- /dev/null +++ b/docs/measurements/issue-120-balance.csv @@ -0,0 +1,25 @@ +variant,commit,seed,sent,kills,leaked,missiles,ticks +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,17,17,0,26,301 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,17,16,1,29,482 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,17,17,0,33,400 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,17,17,0,29,452 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,17,17,0,30,533 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,17,16,1,35,495 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,17,17,0,35,434 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,17,17,0,25,548 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,9,17,17,0,31,728 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,10,17,17,0,29,675 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,11,17,17,0,32,629 +baseline,2689d933e790b7d751dfcf2a4c6a325e041562ce,12,17,17,0,33,546 +finite_motor,508f9a0,1,17,17,0,27,434 +finite_motor,508f9a0,2,17,16,1,28,462 +finite_motor,508f9a0,3,17,17,0,30,384 +finite_motor,508f9a0,4,17,17,0,31,551 +finite_motor,508f9a0,5,17,17,0,29,700 +finite_motor,508f9a0,6,17,17,0,33,443 +finite_motor,508f9a0,7,17,17,0,33,463 +finite_motor,508f9a0,8,17,17,0,25,548 +finite_motor,508f9a0,9,17,17,0,30,728 +finite_motor,508f9a0,10,17,17,0,26,875 +finite_motor,508f9a0,11,17,17,0,30,640 +finite_motor,508f9a0,12,17,17,0,33,546 diff --git a/docs/measurements/issue-120-grid.csv b/docs/measurements/issue-120-grid.csv new file mode 100644 index 0000000..82cc85f --- /dev/null +++ b/docs/measurements/issue-120-grid.csv @@ -0,0 +1,97 @@ +variant,commit,seed,sams,dist,sent,kills,leaked,missiles,heavy,ticks,blocked,scrubbed,crashes,fuse_miss,expired,orphaned,energy_lost,leaks_by_kind +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,10,500,46,35,11,61,9,781,0,0,0,9,0,21,0,Geran3:3;Kalibr:1;Kh22:1;Zircon:1;Iskander:3;Kinzhal:2 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,1,10,500,46,37,9,62,9,812,0,0,0,4,1,23,0,Geran3:2;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,10,500,46,40,6,62,10,875,0,0,0,7,0,16,0,Shahed:1;Kh101:2;Kalibr:2;Iskander:1 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,2,10,500,46,41,5,65,11,883,0,0,0,6,0,20,0,Shahed:1;Kh101:2;Kalibr:1;Iskander:1 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,10,500,46,39,7,56,6,740,0,0,0,4,0,18,0,Kalibr:2;Zircon:1;Iskander:3;Kinzhal:1 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,3,10,500,46,39,7,56,6,740,0,0,0,4,0,18,0,Kalibr:2;Zircon:1;Iskander:3;Kinzhal:1 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,10,500,46,38,8,75,9,919,0,0,0,13,0,24,0,Geran3:1;Kh101:1;Kh22:1;Iskander:3;Kinzhal:2 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,4,10,500,46,34,11,57,8,854,0,0,0,4,0,20,0,Geran3:2;Kh101:3;Kalibr:2;Kh22:1;Iskander:2;Kinzhal:1 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,10,500,46,34,11,66,8,871,0,0,0,12,0,26,0,Geran3:1;Kh101:2;Kh22:2;Zircon:1;Iskander:4;Kinzhal:2 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,5,10,500,46,39,7,67,6,914,0,0,0,7,0,26,0,Kh101:2;Kh22:1;Iskander:3;Kinzhal:1 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,10,500,46,36,10,72,11,986,0,0,0,6,0,30,0,Geran3:1;Kh101:3;Kalibr:3;Zircon:1;Iskander:2;Kinzhal:2 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,6,10,500,46,36,10,73,11,977,0,0,0,7,0,30,0,Geran3:1;Kh101:3;Kalibr:3;Zircon:1;Iskander:2;Kinzhal:2 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,10,500,46,35,11,62,6,802,0,0,0,6,0,23,0,Geran3:1;Kh101:2;Kalibr:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,7,10,500,46,35,11,63,6,802,0,0,0,7,0,23,0,Geran3:1;Kh101:2;Kalibr:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt10,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,10,500,46,40,6,62,8,979,0,0,0,6,0,20,0,Kh101:1;Kh22:1;Iskander:2;Kinzhal:2 +batt10,e6a70004406b34ea17884875ce236a5fb4b849e4,8,10,500,46,40,6,61,8,979,0,0,0,3,0,21,0,Kh101:1;Kh22:1;Iskander:2;Kinzhal:2 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,20,500,46,41,5,72,13,580,0,0,0,11,0,22,0,Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,1,20,500,46,42,4,75,13,578,0,0,0,10,1,22,0,Kalibr:1;Kh22:1;Iskander:1;Kinzhal:1 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,20,500,46,40,6,81,14,629,0,0,0,10,0,36,0,Kh101:2;Kalibr:1;Zircon:1;Kinzhal:2 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,2,20,500,46,41,5,71,15,612,0,0,0,4,0,26,0,Kh101:2;Kalibr:1;Kinzhal:2 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,20,500,46,42,4,64,13,513,0,0,0,9,0,17,0,Zircon:1;Iskander:3 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,3,20,500,46,44,2,68,13,541,0,0,0,6,0,23,0,Zircon:1;Iskander:1 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,20,500,46,42,4,81,15,581,0,0,0,14,0,28,0,Kh22:1;Iskander:2;Kinzhal:1 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,4,20,500,46,42,4,76,15,614,0,0,0,8,0,27,0,Kh22:1;Iskander:2;Kinzhal:1 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,20,500,46,42,4,71,15,730,0,0,0,7,0,23,0,Kh101:1;Iskander:2;Kinzhal:1 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,5,20,500,46,42,4,73,15,748,0,0,0,6,0,25,0,Kh101:1;Iskander:2;Kinzhal:1 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,20,500,46,41,5,77,14,696,0,0,0,7,0,29,0,Kalibr:1;Zircon:1;Iskander:1;Kinzhal:2 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,6,20,500,46,41,5,80,13,681,0,0,0,5,0,34,0,Kalibr:1;Zircon:1;Iskander:1;Kinzhal:2 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,20,500,46,40,6,68,13,619,0,0,0,6,0,23,0,Kh101:2;Kh22:1;Zircon:1;Kinzhal:2 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,7,20,500,46,40,6,68,13,619,0,0,0,6,0,23,0,Kh101:2;Kh22:1;Zircon:1;Kinzhal:2 +batt20,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,20,500,46,43,3,70,15,632,0,0,0,10,0,23,0,Zircon:1;Iskander:1;Kinzhal:1 +batt20,e6a70004406b34ea17884875ce236a5fb4b849e4,8,20,500,46,42,4,67,14,669,0,0,0,8,0,23,0,Kh101:1;Iskander:2;Kinzhal:1 +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,50,500,46,45,1,84,30,394,0,0,0,9,0,31,0,Kh22:1 +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,1,50,500,46,45,1,83,28,418,0,0,0,7,0,32,0,Kh22:1 +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,50,500,46,46,0,83,26,575,0,0,0,10,0,31,0, +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,2,50,500,46,46,0,82,28,514,0,0,0,6,0,35,0, +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,50,500,46,46,0,81,27,499,0,0,0,10,0,35,0, +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,3,50,500,46,46,0,77,27,523,0,0,0,3,0,35,0, +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,50,500,46,45,1,84,31,581,0,0,0,14,0,26,0,Iskander:1;Kinzhal:1 +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,4,50,500,46,46,0,82,31,607,0,0,0,7,0,32,0, +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,50,500,46,44,2,82,32,586,0,0,0,8,0,36,0,Zircon:1;Kinzhal:1 +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,5,50,500,46,46,0,87,29,707,0,0,0,10,0,40,0, +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,50,500,46,45,1,82,26,629,0,0,0,9,0,29,0,Kinzhal:1 +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,6,50,500,46,46,0,94,29,584,0,0,0,11,0,39,0, +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,50,500,46,41,5,91,29,573,0,0,0,17,0,34,0,Kh101:1;Kh22:1;Zircon:1;Iskander:1;Kinzhal:1 +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,7,50,500,46,42,4,82,31,523,0,0,0,4,0,37,0,Kh101:1;Kh22:1;Zircon:1;Kinzhal:1 +batt50,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,50,500,46,45,1,74,26,677,0,0,0,6,0,30,0,Kinzhal:1 +batt50,e6a70004406b34ea17884875ce236a5fb4b849e4,8,50,500,46,46,0,75,26,695,0,0,0,4,0,33,0, +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,20,200,46,41,5,75,16,350,0,0,0,2,0,32,0,Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,1,20,200,46,41,5,75,16,350,0,0,0,2,0,32,0,Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,20,200,46,40,5,69,14,383,0,0,1,5,0,25,0,Shahed:1;Kh101:2;Iskander:1;Kinzhal:2 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,2,20,200,46,40,5,71,14,401,0,0,1,4,0,29,0,Kh101:2;Iskander:2;Kinzhal:1 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,20,200,46,39,6,61,14,375,0,0,1,3,0,26,0,Shahed:1;Kh101:2;Iskander:2;Kinzhal:1 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,3,20,200,46,39,6,60,12,344,0,0,1,2,0,22,0,Shahed:1;Kh101:2;Iskander:3 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,20,200,46,40,6,83,15,459,0,0,0,15,0,30,0,Geran3:1;Kh22:2;Zircon:1;Iskander:1;Kinzhal:1 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,4,20,200,46,40,6,73,13,495,0,0,0,7,0,26,0,Geran3:1;Kh22:2;Iskander:2;Kinzhal:1 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,20,200,46,37,9,76,16,406,0,0,0,14,0,33,0,Kh101:1;Kh22:2;Zircon:1;Iskander:3;Kinzhal:2 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,5,20,200,46,41,5,71,17,455,0,0,0,2,0,34,0,Kh101:1;Kh22:1;Iskander:2;Kinzhal:1 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,20,200,46,39,7,71,13,450,0,0,0,4,0,29,0,Kh101:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,6,20,200,46,39,7,71,13,450,0,0,0,4,0,29,0,Kh101:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,20,200,46,39,7,77,14,448,0,0,0,9,0,31,0,Kh101:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,7,20,200,46,40,6,84,14,465,0,0,0,11,0,33,0,Kh101:1;Kh22:1;Zircon:1;Iskander:1;Kinzhal:2 +near200,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,20,200,46,38,8,70,14,524,0,0,0,10,0,28,0,Kh101:1;Kh22:1;Zircon:1;Iskander:3;Kinzhal:2 +near200,e6a70004406b34ea17884875ce236a5fb4b849e4,8,20,200,46,38,8,67,15,563,0,0,0,4,0,31,0,Kh101:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,20,1500,46,42,4,75,15,2181,0,0,0,9,0,27,0,Kh101:1;Iskander:1;Kinzhal:2 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,1,20,1500,46,44,2,68,14,2218,0,0,0,4,0,24,0,Iskander:1;Kinzhal:1 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,20,1500,46,41,5,72,17,2342,0,0,0,8,0,23,0,Kh101:1;Kalibr:1;Kh22:1;Iskander:1;Kinzhal:1 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,2,20,1500,46,43,3,70,20,2342,0,0,0,7,0,20,0,Kh101:1;Kalibr:1;Iskander:1 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,20,1500,46,43,3,68,19,2385,0,0,0,8,0,19,0,Iskander:2;Kinzhal:1 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,3,20,1500,46,44,2,70,20,2363,0,0,0,5,0,21,0,Iskander:2 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,20,1500,46,41,5,73,16,2388,0,0,0,14,0,22,0,Kh101:1;Kh22:1;Iskander:2;Kinzhal:1 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,4,20,1500,46,42,4,68,16,2387,0,0,0,5,0,25,0,Kh101:1;Kh22:1;Iskander:1;Kinzhal:1 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,20,1500,46,42,4,75,18,2550,0,0,0,12,0,24,0,Kh101:2;Iskander:1;Kinzhal:1 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,5,20,1500,46,42,4,69,19,2635,0,0,0,5,0,23,0,Kh101:2;Iskander:1;Kinzhal:1 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,20,1500,46,36,10,75,18,2575,0,0,0,3,0,37,0,Kh101:2;Kalibr:1;Kh22:1;Zircon:1;Iskander:3;Kinzhal:2 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,6,20,1500,46,38,8,76,18,2575,0,0,0,2,0,36,0,Kh101:2;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,20,1500,46,42,4,78,20,2361,0,0,0,10,0,27,0,Kh101:1;Iskander:1;Kinzhal:2 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,7,20,1500,46,43,3,73,18,2361,0,0,0,6,0,25,0,Kh101:1;Kalibr:1;Kinzhal:1 +far1500,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,20,1500,46,41,5,66,19,2544,0,0,0,6,0,23,0,Kh101:1;Iskander:2;Kinzhal:2 +far1500,e6a70004406b34ea17884875ce236a5fb4b849e4,8,20,1500,46,42,4,68,20,2544,0,0,0,4,0,26,0,Kh101:1;Iskander:2;Kinzhal:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,1,20,500,46,41,5,79,14,606,0,0,0,12,0,27,0,Kalibr:1;Kh22:1;Zircon:1;Iskander:2 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,1,20,500,46,40,6,73,12,551,0,0,0,8,0,25,0,Geran3:1;Kalibr:1;Kh22:1;Zircon:1;Iskander:3;Kinzhal:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,2,20,500,46,38,8,77,15,651,8,0,0,2,0,30,0,Kh101:2;Kalibr:1;Kh22:1;Iskander:3;Kinzhal:1 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,2,20,500,46,38,8,81,14,681,7,0,0,3,0,33,0,Kh101:2;Kalibr:1;Kh22:1;Iskander:3;Kinzhal:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,3,20,500,46,44,1,79,12,640,0,1,1,10,0,28,0,Kh101:1;Zircon:1;Iskander:1 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,3,20,500,46,44,1,78,13,675,0,0,1,7,0,29,0,Kh101:1;Zircon:1;Iskander:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,4,20,500,46,40,6,82,15,492,3,3,0,13,0,25,0,Kh22:1;Iskander:4;Kinzhal:1 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,4,20,500,46,42,4,77,15,515,3,2,0,8,0,24,0,Kh22:1;Iskander:3 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,5,20,500,46,39,7,69,18,514,0,1,0,8,0,23,0,Kh22:1;Iskander:4;Kinzhal:2 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,5,20,500,46,42,4,65,15,469,1,0,0,4,0,22,0,Kh22:1;Iskander:2;Kinzhal:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,6,20,500,46,43,3,68,13,612,0,0,0,3,0,22,0,Zircon:1;Iskander:1;Kinzhal:1 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,6,20,500,46,43,3,68,13,612,0,0,0,3,0,22,0,Zircon:1;Iskander:1;Kinzhal:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,7,20,500,46,41,5,66,13,554,0,0,0,6,0,19,0,Kh22:1;Zircon:1;Iskander:1;Kinzhal:2 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,7,20,500,46,43,3,64,15,546,0,0,0,5,0,16,0,Kh22:1;Zircon:1;Iskander:1;Kinzhal:1 +hills20,2689d933e790b7d751dfcf2a4c6a325e041562ce,8,20,500,46,42,4,77,14,712,3,0,0,14,0,20,0,Iskander:2;Kinzhal:2 +hills20,e6a70004406b34ea17884875ce236a5fb4b849e4,8,20,500,46,42,4,77,14,712,3,0,0,13,0,20,0,Iskander:2;Kinzhal:2 diff --git a/docs/measurements/issue-122-ui-fuel/after.txt b/docs/measurements/issue-122-ui-fuel/after.txt new file mode 100644 index 0000000..57cb266 --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/after.txt @@ -0,0 +1,6 @@ +running 1 scripts against /tmp/issue-122-raid/airdef-mod/target/horizon-mod/airdef.hmod +fuel airdef command: calls=179 max=32946177 budget=1000000000 failures=0 +fuel airdef frame: calls=377 max=77 budget=10000000 failures=0 +fuel airdef tick: calls=3802 max=236581288 budget=300000000 failures=0 +test tests/wasm_budget.toml ... ok (255 steps) +test result: ok. 1 passed diff --git a/docs/measurements/issue-122-ui-fuel/before-scaling.txt b/docs/measurements/issue-122-ui-fuel/before-scaling.txt new file mode 100644 index 0000000..9419c0c --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/before-scaling.txt @@ -0,0 +1,10 @@ +=== BEFORE: scaling witness — an 11-drone second order at the same state === +running 1 scripts against /tmp/issue-122-run/airdef-diag-before.hmod +fuel airdef command: calls=177 max=247805865 budget=1000000000 failures=0 +fuel airdef frame: calls=257 max=77 budget=10000000 failures=0 +fuel airdef tick: calls=2602 max=241372671 budget=300000000 failures=0 +test tests/wasm_budget.toml ... FAILED + step 229 (/airdef wave mix 1 300): feedback + expected: Some("PROBE") + got: Some("DIAG revealed=13172 known=144 sams=172 alive=144 planning=true queue=0") +error: 1 failed, 0 passed diff --git a/docs/measurements/issue-122-ui-fuel/before-trap.txt b/docs/measurements/issue-122-ui-fuel/before-trap.txt new file mode 100644 index 0000000..91a189c --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/before-trap.txt @@ -0,0 +1,7 @@ +=== BEFORE: second order (UI path) traps at the 1e9 event budget === +running 1 scripts against /tmp/issue-122-run/airdef-diag-before.hmod +fuel airdef command: calls=177 max=1000000000 budget=1000000000 failures=1 +fuel airdef frame: calls=257 max=77 budget=10000000 failures=0 +fuel airdef tick: calls=2602 max=241372671 budget=300000000 failures=0 +test tests/wasm_budget.toml ... ok (229 steps) +test result: ok. 1 passed diff --git a/docs/measurements/issue-122-ui-fuel/scenario-after.toml b/docs/measurements/issue-122-ui-fuel/scenario-after.toml new file mode 100644 index 0000000..9fa21c9 --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/scenario-after.toml @@ -0,0 +1,762 @@ +[[step]] +render-ground-y = 64 +ticks = 2 +frames = 2 +expect.commands = ["airdef"] +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +command = "airdef ew on" +[[step]] +command = "airdef wave mix 10 300" +[[step]] +ticks = 100 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef ew on" +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef ew on" +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 0, ракет: 0, ПВО: 172, сбито: 308, поражений: 87, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 8156 секций (382 КБ), запросов 16075, неизвестно 0, лучей 87524 (ударов 1830, неизв. 1567), клеток 1318224; взрывы в блоках: 239 (подтверждено 0, без перемен 37, нет чанка 202), чужих ответов 0" +[[step]] +command = "airdef wave mix 10 300" +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 0, ракет: 0, ПВО: 172, сбито: 309, поражений: 87, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 8557 секций (401 КБ), запросов 16511, неизвестно 0, лучей 89230 (ударов 2855, неизв. 2039), клеток 1326528; взрывы в блоках: 239 (подтверждено 0, без перемен 37, нет чанка 202), чужих ответов 0" diff --git a/docs/measurements/issue-122-ui-fuel/scenario-before.toml b/docs/measurements/issue-122-ui-fuel/scenario-before.toml new file mode 100644 index 0000000..e13f160 --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/scenario-before.toml @@ -0,0 +1,688 @@ +[[step]] +render-ground-y = 64 +ticks = 2 +frames = 2 +expect.commands = ["airdef"] +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 0.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 40.0 } +command = "airdef deploy ew" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 40.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 80.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 120.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 0.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 40.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 80.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 120.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 160.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 200.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 240.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy s400" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy patriot" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy nasams" +[[step]] +player = { x = 280.0, z = 160.0 } +command = "airdef deploy pantsir" +[[step]] +command = "airdef ew on" +[[step]] +command = "airdef wave mix 10 300" +[[step]] +ticks = 100 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef ew on" +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef ew on" +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +ticks = 50 +frames = 5 +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 0, ракет: 0, ПВО: 172, сбито: 308, поражений: 87, разбились о препятствия: 0; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 8156 секций (382 КБ), запросов 16075, неизвестно 0, лучей 87524 (ударов 1830, неизв. 1567), клеток 1318224; взрывы в блоках: 239 (подтверждено 0, без перемен 37, нет чанка 202), чужих ответов 0" +[[step]] +command = "airdef wave mix 10 300" +expect.stopped = "exceeded its execution limit" diff --git a/docs/measurements/issue-122-ui-fuel/summary.json b/docs/measurements/issue-122-ui-fuel/summary.json new file mode 100644 index 0000000..7aed2b9 --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/summary.json @@ -0,0 +1,67 @@ +{ + "schema": 1, + "issue": 122, + "kind": "diagnostic_guest_ui_path_comparison_not_exact_user_session", + "user_report": { + "log": "Horizon: mod airdef stopped in UI event (fuel budget 1000000000, remaining Some(0))", + "backtrace": "airdef_core::sim::ew::Sim::known_units <- sim::raid::Sim::order_raid <- airdef::AirDef::launch_wave <- horizon_mod_sdk::Mod::ui_select <- ui-events select", + "windows_session_confirmed_same_cause": false + }, + "root_cause": { + "what": "order_raid recomputed the planner snapshot known_units() for every drone (choose_target), and known_units() itself rescanned the whole force and the dedup list per intel entry", + "growth": "drones x intel_log x force_size per UI event; the intel log holds every launch, EW disclosure, and radar disclosure of the last 6000 ticks", + "state_at_trap": { + "revealed_entries": 13172, + "known_alive_units": 144, + "sams_total": 172 + } + }, + "fix": { + "description": "one known_units snapshot per order_raid passed to radar_strikes and choose_target; known_units is a single O(R+S) set pass; price_grid_on reuses its snapshot", + "semantics": "identical planner results; native equivalence test against the former predicate; WASM checkpoint /airdef stats byte-identical before/after at tick 2602", + "limits_unchanged": true, + "wave_size_unrestricted": true, + "hd_record_untouched": true + }, + "setup": { + "defenses": "40 spread clusters x 4 heavy SAMs + 12 EW = 172, player moved per deploy step", + "first_raid": "airdef wave mix 10 (111 drones, Smart, AllSides) via the diagnostic UI-path trigger", + "second_raid": "same, at tick ~2600 after the first raid is fought off", + "note": "stacked single-point layouts die to one warhead (known=0, cheap order); the trap needs a surviving force, like the user's session" + }, + "before": { + "source": "e6a7000 + trigger.patch (diagnostic guest only)", + "second_order_111_drones": { + "callback": "command (the diagnostic trigger invokes the exact ui_select(BTN_LAUNCH) handler; the UI event and the command share the same 1e9 event budget)", + "max_used": 1000000000, + "budget": 1000000000, + "failures": 1, + "mod_stopped": true, + "log": "before-trap.txt" + }, + "scaling_witness_11_drones_same_state": { + "max_used": 247805865, + "budget": 1000000000, + "failures": 0, + "log": "before-scaling.txt" + } + }, + "after": { + "same_scenario_passed": true, + "steps": 255, + "max_command_fuel": 32946177, + "max_tick_fuel": 236581288, + "failures": 0, + "checkpoint_stats_equal_before_after": true, + "log": "after.txt" + }, + "artifacts": { + "trigger_patch": "trigger.patch", + "scenario_before": "scenario-before.toml", + "scenario_after": "scenario-after.toml", + "before_guest_sha256": "6d4525cd7aaa61dbf344593f9819be1b61e06654126e9e9088e57967dc657365", + "after_guest_sha256": "c4ef7d759524eb053ccd55f0eccdcb49c91954201eb4e0ad923ee9b2828efc4e", + "scenario_before_sha256": "398d099275e396deab269601e5adbcf325ab5e0ea6b540c1d03a477d6b0963d1", + "scenario_after_sha256": "3bc27338d2e6fb433f027a0cf1afc39ff9a94e1af5840454082af6d8d15aad7c" + } +} diff --git a/docs/measurements/issue-122-ui-fuel/trigger.patch b/docs/measurements/issue-122-ui-fuel/trigger.patch new file mode 100644 index 0000000..91ee1e6 --- /dev/null +++ b/docs/measurements/issue-122-ui-fuel/trigger.patch @@ -0,0 +1,23 @@ +diff --git a/airdef-mod/src/lib.rs b/airdef-mod/src/lib.rs +index dc6585eb3..73864e41e 100644 +--- a/airdef-mod/src/lib.rs ++++ b/airdef-mod/src/lib.rs +@@ -2351,8 +2351,16 @@ impl Mod for AirDef { + .nearest_target(DVec3::new(p.x, p.y, p.z)) + .unwrap_or(DVec3::new(p.x, p.y, p.z)); + if *kind == "mix" { +- let objects = self.objects(DVec3::new(p.x, p.y, p.z)); +- self.sim.order_raid(&wave::WaveConfig::mix(count, dist), &objects, &mut out); ++ // DIAGNOSTIC ONLY (issue #122): drive the exact UI launch path ++ // (Page::WaveGo, BTN_LAUNCH) with the maximum menu composition. ++ let mut wave = wave::WaveConfig::default(); ++ wave.counts = [40; wave::KINDS.len()]; ++ wave.dist = dist; ++ wave.approach = sim::Approach::AllSides; ++ wave.aim = sim::Aim::Smart; ++ self.wave = wave; ++ self.menu = Some(Page::WaveGo); ++ self.ui_select(BTN_LAUNCH, 0); + } else { + let kind = DroneKind::from_key(kind).ok_or( + "Тип: shahed, geran3, lancet, gerbera, kh101, kalibr, kh22, zircon, iskander, kinzhal, oreshnik или mix", diff --git a/docs/measurements/issue-127-enemy-picture-baseline.json b/docs/measurements/issue-127-enemy-picture-baseline.json new file mode 100644 index 0000000..d6c96f2 --- /dev/null +++ b/docs/measurements/issue-127-enemy-picture-baseline.json @@ -0,0 +1,155 @@ +{ + "schema": 1, + "world": "EnemyPicture127", + "branch": "kimi-work (baseline, before #127 stage 1)", + "code_commit": "0b645d77e", + "bench": "cargo run --release --example intel_bench (airdef-core, SDK-free)", + "scenarios": [ + "big-smart", + "logistics", + "ew-adaptive" + ], + "seeds": [ + 1, + 7, + 42 + ], + "metrics": "completion: raids_ended/raids_ordered, all_ended; losses: sams_lost; exchange: launched/kills/hits/crashes/fired; failure reasons: sim.tally text (Russian, as the tally words them)", + "runs": [ + { + "scenario": "big-smart", + "seed": 1, + "ticks_run": 1642, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 50, + "failures": "5 промахов по взрывателю, 30 ракет остались без цели, 4 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 7, + "ticks_run": 1514, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 49, + "failures": "2 промахов по взрывателю, 22 ракет остались без цели, 6 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 42, + "ticks_run": 1490, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 56, + "failures": "5 промахов по взрывателю, 35 ракет остались без цели, 9 потерь трека" + }, + { + "scenario": "logistics", + "seed": 1, + "ticks_run": 1009, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 11, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "1 промахов по взрывателю, 6 ракет остались без цели, 1 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 7, + "ticks_run": 970, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 7, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "6 ракет остались без цели, 2 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 42, + "ticks_run": 1003, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "6 ракет остались без цели, 3 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "ew-adaptive", + "seed": 1, + "ticks_run": 8661, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 62, + "hits": 3, + "crashes": 4, + "sams_lost": 1, + "fired": 82, + "failures": "8 промахов по взрывателю, 1 ракет выработали ресурс, 24 ракет остались без цели, 14 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 7, + "ticks_run": 8809, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 61, + "hits": 5, + "crashes": 3, + "sams_lost": 4, + "fired": 78, + "failures": "7 промахов по взрывателю, 20 ракет остались без цели, 10 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 42, + "ticks_run": 6976, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 61, + "hits": 7, + "crashes": 1, + "sams_lost": 4, + "fired": 75, + "failures": "8 промахов по взрывателю, 1 ракет выработали ресурс, 25 ракет остались без цели, 10 потерь трека" + } + ] +} \ No newline at end of file diff --git a/docs/measurements/issue-127-enemy-picture-result.json b/docs/measurements/issue-127-enemy-picture-result.json new file mode 100644 index 0000000..10b2177 --- /dev/null +++ b/docs/measurements/issue-127-enemy-picture-result.json @@ -0,0 +1,155 @@ +{ + "schema": 1, + "world": "EnemyPicture127", + "branch": "agent/issue-127-enemy-picture", + "code_commit": "agent/issue-127-enemy-picture (see git log for the exact SHA)", + "bench": "cargo run --release --example intel_bench (airdef-core, SDK-free)", + "scenarios": [ + "big-smart", + "logistics", + "ew-adaptive" + ], + "seeds": [ + 1, + 7, + 42 + ], + "metrics": "completion: raids_ended/raids_ordered, all_ended; losses: sams_lost; exchange: launched/kills/hits/crashes/fired; failure reasons: sim.tally text (Russian, as the tally words them)", + "runs": [ + { + "scenario": "big-smart", + "seed": 1, + "ticks_run": 1716, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 49, + "failures": "3 промахов по взрывателю, 27 ракет остались без цели, 3 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 7, + "ticks_run": 1542, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 48, + "failures": "2 промахов по взрывателю, 22 ракет остались без цели, 5 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 42, + "ticks_run": 1433, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 49, + "failures": "2 промахов по взрывателю, 27 ракет остались без цели, 7 потерь трека" + }, + { + "scenario": "logistics", + "seed": 1, + "ticks_run": 1009, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 11, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "1 промахов по взрывателю, 6 ракет остались без цели, 1 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 7, + "ticks_run": 970, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 7, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "6 ракет остались без цели, 2 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 42, + "ticks_run": 1003, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "6 ракет остались без цели, 3 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "ew-adaptive", + "seed": 1, + "ticks_run": 8084, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 59, + "hits": 7, + "crashes": 3, + "sams_lost": 3, + "fired": 69, + "failures": "7 промахов по взрывателю, 20 ракет остались без цели, 11 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 7, + "ticks_run": 8193, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 59, + "hits": 7, + "crashes": 3, + "sams_lost": 3, + "fired": 81, + "failures": "4 промахов по взрывателю, 3 ракет выработали ресурс, 26 ракет остались без цели, 8 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 42, + "ticks_run": 7916, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 56, + "hits": 8, + "crashes": 5, + "sams_lost": 4, + "fired": 69, + "failures": "7 промахов по взрывателю, 20 ракет остались без цели, 12 потерь трека" + } + ] +} \ No newline at end of file diff --git a/docs/measurements/issue-127-intent-queue-baseline.json b/docs/measurements/issue-127-intent-queue-baseline.json new file mode 100644 index 0000000..5a1c345 --- /dev/null +++ b/docs/measurements/issue-127-intent-queue-baseline.json @@ -0,0 +1,154 @@ +{ + "schema": 1, + "world": "IntentQueue127", + "branch": "8fc10c07a", + "code_commit": "8fc10c07a (base of the branch; the run was made from a clean worktree at that commit)", + "bench": "cargo run --release --example intel_bench (airdef-mod, SDK-free harness)", + "scenarios": [ + "big-smart", + "ew-adaptive", + "logistics" + ], + "seeds": [ + 1, + 7, + 42 + ], + "runs": [ + { + "scenario": "big-smart", + "seed": 1, + "ticks_run": 1716, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 49, + "failures": "3 промахов по взрывателю, 27 ракет остались без цели, 3 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 7, + "ticks_run": 1542, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 48, + "failures": "2 промахов по взрывателю, 22 ракет остались без цели, 5 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 42, + "ticks_run": 1433, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 49, + "failures": "2 промахов по взрывателю, 27 ракет остались без цели, 7 потерь трека" + }, + { + "scenario": "logistics", + "seed": 1, + "ticks_run": 1009, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 11, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "1 промахов по взрывателю, 6 ракет остались без цели, 1 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 7, + "ticks_run": 970, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 7, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "6 ракет остались без цели, 2 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 42, + "ticks_run": 1003, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "failures": "6 ракет остались без цели, 3 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "ew-adaptive", + "seed": 1, + "ticks_run": 8084, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 59, + "hits": 7, + "crashes": 3, + "sams_lost": 3, + "fired": 69, + "failures": "7 промахов по взрывателю, 20 ракет остались без цели, 11 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 7, + "ticks_run": 8193, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 59, + "hits": 7, + "crashes": 3, + "sams_lost": 3, + "fired": 81, + "failures": "4 промахов по взрывателю, 3 ракет выработали ресурс, 26 ракет остались без цели, 8 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 42, + "ticks_run": 7916, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 56, + "hits": 8, + "crashes": 5, + "sams_lost": 4, + "fired": 69, + "failures": "7 промахов по взрывателю, 20 ракет остались без цели, 12 потерь трека" + } + ] +} diff --git a/docs/measurements/issue-127-intent-queue-result.json b/docs/measurements/issue-127-intent-queue-result.json new file mode 100644 index 0000000..7c755f8 --- /dev/null +++ b/docs/measurements/issue-127-intent-queue-result.json @@ -0,0 +1,190 @@ +{ + "schema": 1, + "world": "IntentQueue127", + "branch": "agent/issue-127-intent-durable", + "code_commit": "agent/issue-127-intent-durable (this branch; see git log for the exact SHA)", + "bench": "cargo run --release --example intel_bench (airdef-mod, SDK-free harness)", + "scenarios": [ + "big-smart", + "ew-adaptive", + "logistics" + ], + "seeds": [ + 1, + 7, + 42 + ], + "runs": [ + { + "scenario": "big-smart", + "seed": 1, + "ticks_run": 1676, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 60, + "retargets_max": 1, + "air_bytes": 61895, + "switches_max": 1, + "intents": "plan:25,sead:0,react:13,replace:0", + "failures": "3 промахов по взрывателю, 41 ракет остались без цели, 9 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 7, + "ticks_run": 1542, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 68, + "retargets_max": 2, + "air_bytes": 62311, + "switches_max": 1, + "intents": "plan:24,sead:0,react:14,replace:0", + "failures": "6 промахов по взрывателю, 37 ракет остались без цели, 9 потерь трека" + }, + { + "scenario": "big-smart", + "seed": 42, + "ticks_run": 1433, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 38, + "kills": 38, + "hits": 0, + "crashes": 0, + "sams_lost": 0, + "fired": 71, + "retargets_max": 2, + "air_bytes": 61205, + "switches_max": 1, + "intents": "plan:22,sead:0,react:16,replace:0", + "failures": "5 промахов по взрывателю, 49 ракет остались без цели, 21 потерь трека" + }, + { + "scenario": "logistics", + "seed": 1, + "ticks_run": 1009, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 11, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "retargets_max": 0, + "air_bytes": 4041, + "switches_max": 0, + "intents": "plan:0,sead:0,react:0,replace:0", + "failures": "1 промахов по взрывателю, 6 ракет остались без цели, 1 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 7, + "ticks_run": 970, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 7, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "retargets_max": 0, + "air_bytes": 4031, + "switches_max": 0, + "intents": "plan:0,sead:0,react:0,replace:0", + "failures": "6 ракет остались без цели, 2 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "logistics", + "seed": 42, + "ticks_run": 1003, + "raids_ordered": 1, + "raids_ended": 1, + "all_ended": true, + "launched": 27, + "kills": 12, + "hits": 8, + "crashes": 0, + "sams_lost": 0, + "fired": 18, + "retargets_max": 0, + "air_bytes": 4018, + "switches_max": 0, + "intents": "plan:0,sead:0,react:0,replace:0", + "failures": "6 ракет остались без цели, 3 потерь трека, 3 ожиданий БК" + }, + { + "scenario": "ew-adaptive", + "seed": 1, + "ticks_run": 7146, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 58, + "hits": 7, + "crashes": 4, + "sams_lost": 3, + "fired": 76, + "retargets_max": 0, + "air_bytes": 7125, + "switches_max": 1, + "intents": "plan:47,sead:8,react:14,replace:0", + "failures": "10 промахов по взрывателю, 17 ракет остались без цели, 11 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 7, + "ticks_run": 7650, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 58, + "hits": 9, + "crashes": 2, + "sams_lost": 4, + "fired": 98, + "retargets_max": 0, + "air_bytes": 8808, + "switches_max": 1, + "intents": "plan:19,sead:2,react:13,replace:35", + "failures": "9 промахов по взрывателю, 45 ракет остались без цели, 13 потерь трека" + }, + { + "scenario": "ew-adaptive", + "seed": 42, + "ticks_run": 7243, + "raids_ordered": 3, + "raids_ended": 3, + "all_ended": true, + "launched": 69, + "kills": 63, + "hits": 3, + "crashes": 3, + "sams_lost": 2, + "fired": 82, + "retargets_max": 0, + "air_bytes": 7997, + "switches_max": 1, + "intents": "plan:49,sead:8,react:12,replace:0", + "failures": "12 промахов по взрывателю, 21 ракет остались без цели, 18 потерь трека" + } + ] +} diff --git a/docs/measurements/issue-129-balance.csv b/docs/measurements/issue-129-balance.csv new file mode 100644 index 0000000..26c9bf6 --- /dev/null +++ b/docs/measurements/issue-129-balance.csv @@ -0,0 +1,25 @@ +variant,commit,seed,sent,kills,leaked,missiles,ticks +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,17,17,0,27,434 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,17,16,1,28,462 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,17,17,0,30,384 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,17,17,0,31,551 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,17,17,0,29,700 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,17,17,0,33,443 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,17,17,0,33,463 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,17,17,0,25,548 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,9,17,17,0,30,728 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,10,17,17,0,26,875 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,11,17,17,0,30,640 +baseline,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,12,17,17,0,33,546 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,17,17,0,28,301 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,17,16,1,30,544 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,17,16,1,30,384 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,17,17,0,30,465 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,17,17,0,30,731 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,17,17,0,32,521 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,17,17,0,35,592 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,17,17,0,26,626 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,9,17,17,0,30,729 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,10,17,16,1,26,765 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,11,17,17,0,30,632 +cold-launch,4b013fb7f30dbaa87a266b27a31911937fc523b4,12,17,17,0,33,546 diff --git a/docs/measurements/issue-129-grid.csv b/docs/measurements/issue-129-grid.csv new file mode 100644 index 0000000..35de017 --- /dev/null +++ b/docs/measurements/issue-129-grid.csv @@ -0,0 +1,97 @@ +variant,commit,seed,sams,dist,sent,kills,leaked,missiles,heavy,ticks,blocked,scrubbed,crashes,fuse_miss,expired,orphaned,energy_lost,leaks_by_kind +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,10,500,46,37,9,62,9,812,0,0,0,4,1,23,0,Geran3:2;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,10,500,46,41,5,65,11,883,0,0,0,6,0,20,0,Shahed:1;Kh101:2;Kalibr:1;Iskander:1 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,10,500,46,39,7,56,6,740,0,0,0,4,0,18,0,Kalibr:2;Zircon:1;Iskander:3;Kinzhal:1 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,10,500,46,34,11,57,8,854,0,0,0,4,0,20,0,Geran3:2;Kh101:3;Kalibr:2;Kh22:1;Iskander:2;Kinzhal:1 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,10,500,46,39,7,67,6,914,0,0,0,7,0,26,0,Kh101:2;Kh22:1;Iskander:3;Kinzhal:1 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,10,500,46,36,10,73,11,977,0,0,0,7,0,30,0,Geran3:1;Kh101:3;Kalibr:3;Zircon:1;Iskander:2;Kinzhal:2 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,10,500,46,35,11,63,6,802,0,0,0,7,0,23,0,Geran3:1;Kh101:2;Kalibr:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt10,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,10,500,46,40,6,61,8,979,0,0,0,3,0,21,0,Kh101:1;Kh22:1;Iskander:2;Kinzhal:2 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,10,500,46,36,10,58,9,825,0,0,0,4,0,20,0,Geran3:3;Kh101:1;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,10,500,46,38,8,64,10,892,0,0,0,4,0,23,0,Shahed:1;Kh101:2;Kalibr:2;Zircon:1;Iskander:2 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,10,500,46,38,8,62,8,714,0,0,0,7,0,23,0,Kalibr:2;Kh22:1;Zircon:1;Iskander:3;Kinzhal:1 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,10,500,46,35,10,61,8,902,0,0,0,4,0,24,0,Geran3:1;Kh101:2;Kalibr:1;Kh22:1;Iskander:3;Kinzhal:2 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,10,500,46,36,9,62,6,896,0,0,0,6,0,27,0,Geran3:1;Kh101:2;Kh22:1;Iskander:4;Kinzhal:1 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,10,500,46,31,13,64,8,988,0,0,0,4,0,29,0,Geran3:1;Kh101:3;Kalibr:3;Zircon:1;Iskander:3;Kinzhal:2 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,10,500,46,36,9,60,9,921,0,0,0,5,0,20,0,Geran3:1;Kh101:3;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +batt10,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,10,500,46,40,6,63,7,979,0,0,0,5,0,22,0,Kh101:1;Zircon:1;Iskander:2;Kinzhal:2 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,20,500,46,42,4,75,13,578,0,0,0,10,1,22,0,Kalibr:1;Kh22:1;Iskander:1;Kinzhal:1 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,20,500,46,41,5,71,15,612,0,0,0,4,0,26,0,Kh101:2;Kalibr:1;Kinzhal:2 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,20,500,46,44,2,68,13,541,0,0,0,6,0,23,0,Zircon:1;Iskander:1 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,20,500,46,42,4,76,15,614,0,0,0,8,0,27,0,Kh22:1;Iskander:2;Kinzhal:1 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,20,500,46,42,4,73,15,748,0,0,0,6,0,25,0,Kh101:1;Iskander:2;Kinzhal:1 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,20,500,46,41,5,80,13,681,0,0,0,5,0,34,0,Kalibr:1;Zircon:1;Iskander:1;Kinzhal:2 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,20,500,46,40,6,68,13,619,0,0,0,6,0,23,0,Kh101:2;Kh22:1;Zircon:1;Kinzhal:2 +batt20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,20,500,46,42,4,67,14,669,0,0,0,8,0,23,0,Kh101:1;Iskander:2;Kinzhal:1 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,20,500,46,40,6,79,12,555,0,0,0,10,0,31,0,Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,20,500,46,40,6,71,14,626,0,0,0,4,0,29,0,Kh101:2;Kalibr:1;Zircon:1;Iskander:1;Kinzhal:1 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,20,500,46,42,4,65,14,528,0,0,0,5,0,22,0,Zircon:1;Iskander:1;Kinzhal:2 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,20,500,46,42,4,79,15,632,0,0,0,10,0,30,0,Kh22:1;Iskander:1;Kinzhal:2 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,20,500,46,41,5,74,14,687,0,0,0,6,0,27,0,Kh101:1;Iskander:2;Kinzhal:2 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,20,500,46,39,7,83,15,689,0,0,0,9,0,34,0,Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,20,500,46,40,6,70,12,606,0,0,0,6,0,26,0,Kh101:2;Kh22:1;Zircon:1;Iskander:1;Kinzhal:2 +batt20,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,20,500,46,43,3,72,16,636,0,0,0,5,0,28,0,Iskander:2;Kinzhal:1 +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,50,500,46,45,1,83,28,418,0,0,0,7,0,32,0,Kh22:1 +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,50,500,46,46,0,82,28,514,0,0,0,6,0,35,0, +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,50,500,46,46,0,77,27,523,0,0,0,3,0,35,0, +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,50,500,46,46,0,82,31,607,0,0,0,7,0,32,0, +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,50,500,46,46,0,87,29,707,0,0,0,10,0,40,0, +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,50,500,46,46,0,94,29,584,0,0,0,11,0,39,0, +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,50,500,46,42,4,82,31,523,0,0,0,4,0,37,0,Kh101:1;Kh22:1;Zircon:1;Kinzhal:1 +batt50,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,50,500,46,46,0,75,26,695,0,0,0,4,0,33,0, +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,50,500,46,45,1,81,29,481,0,0,0,3,0,32,0,Zircon:1 +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,50,500,46,45,1,85,29,501,0,0,0,2,0,42,0,Kinzhal:1 +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,50,500,46,45,1,76,26,474,0,0,0,6,0,35,0,Kinzhal:1 +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,50,500,46,46,0,77,29,617,0,0,0,4,0,32,0, +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,50,500,46,46,0,86,31,597,0,0,0,7,0,40,0, +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,50,500,46,44,2,91,31,821,0,0,0,10,0,42,0,Kh22:1;Kinzhal:1 +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,50,500,46,43,3,79,28,523,0,0,0,4,0,34,0,Kh101:1;Zircon:1;Kinzhal:1 +batt50,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,50,500,46,44,2,77,27,678,0,0,0,6,0,35,0,Zircon:1;Kinzhal:1 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,20,1500,46,44,2,68,14,2218,0,0,0,4,0,24,0,Iskander:1;Kinzhal:1 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,20,1500,46,43,3,70,20,2342,0,0,0,7,0,20,0,Kh101:1;Kalibr:1;Iskander:1 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,20,1500,46,44,2,70,20,2363,0,0,0,5,0,21,0,Iskander:2 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,20,1500,46,42,4,68,16,2387,0,0,0,5,0,25,0,Kh101:1;Kh22:1;Iskander:1;Kinzhal:1 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,20,1500,46,42,4,69,19,2635,0,0,0,5,0,23,0,Kh101:2;Iskander:1;Kinzhal:1 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,20,1500,46,38,8,76,18,2575,0,0,0,2,0,36,0,Kh101:2;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,20,1500,46,43,3,73,18,2361,0,0,0,6,0,25,0,Kh101:1;Kalibr:1;Kinzhal:1 +far1500,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,20,1500,46,42,4,68,20,2544,0,0,0,4,0,26,0,Kh101:1;Iskander:2;Kinzhal:1 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,20,1500,46,44,2,67,16,2145,0,0,0,4,0,26,0,Iskander:1;Kinzhal:1 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,20,1500,46,42,4,65,17,2346,0,0,0,6,0,18,0,Kh101:1;Kalibr:1;Iskander:1;Kinzhal:1 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,20,1500,46,43,3,68,18,2358,0,0,0,5,0,21,0,Iskander:3 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,20,1500,46,41,5,70,16,2386,0,0,0,5,0,28,0,Kh101:1;Kh22:1;Iskander:1;Kinzhal:2 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,20,1500,46,42,4,71,19,2551,0,0,0,5,0,24,0,Kh101:2;Iskander:1;Kinzhal:1 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,20,1500,46,37,9,78,19,2579,0,0,0,3,0,38,0,Kh101:2;Kalibr:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,20,1500,46,43,3,75,20,2365,0,0,0,7,0,26,0,Kh101:1;Kinzhal:2 +far1500,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,20,1500,46,42,4,67,18,2548,0,0,0,4,0,25,0,Kh101:1;Iskander:2;Kinzhal:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,20,500,46,40,6,73,12,551,0,0,0,8,0,25,0,Geran3:1;Kalibr:1;Kh22:1;Zircon:1;Iskander:3;Kinzhal:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,20,500,46,38,8,81,14,681,7,0,0,3,0,33,0,Kh101:2;Kalibr:1;Kh22:1;Iskander:3;Kinzhal:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,20,500,46,44,1,78,13,675,0,0,1,7,0,29,0,Kh101:1;Zircon:1;Iskander:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,20,500,46,42,4,77,15,515,3,2,0,8,0,24,0,Kh22:1;Iskander:3 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,20,500,46,42,4,65,15,469,1,0,0,4,0,22,0,Kh22:1;Iskander:2;Kinzhal:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,20,500,46,43,3,68,13,612,0,0,0,3,0,22,0,Zircon:1;Iskander:1;Kinzhal:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,20,500,46,43,3,64,15,546,0,0,0,5,0,16,0,Kh22:1;Zircon:1;Iskander:1;Kinzhal:1 +hills20,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,20,500,46,42,4,77,14,712,3,0,0,13,0,20,0,Iskander:2;Kinzhal:2 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,20,500,46,38,8,75,14,541,0,0,0,6,0,31,0,Kalibr:1;Kh22:1;Zircon:1;Iskander:3;Kinzhal:2 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,20,500,46,39,6,77,14,633,5,0,1,3,0,31,0,Kh101:2;Iskander:3;Kinzhal:1 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,20,500,46,41,3,71,12,643,0,1,2,1,0,27,0,Kh101:1;Zircon:1;Iskander:2 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,20,500,46,39,7,75,16,520,3,2,0,3,0,28,0,Kh22:1;Iskander:4;Kinzhal:2 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,20,500,46,39,7,70,18,512,0,1,0,3,0,28,0,Kh101:1;Kh22:1;Iskander:3;Kinzhal:2 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,20,500,46,41,5,66,14,615,1,0,0,4,0,20,0,Kh101:1;Iskander:2;Kinzhal:2 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,20,500,46,41,5,76,14,579,0,0,0,8,0,28,0,Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +hills20,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,20,500,46,41,5,70,14,653,0,0,0,6,0,26,0,Kh101:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,1,20,200,46,41,5,75,16,350,0,0,0,2,0,32,0,Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,2,20,200,46,40,5,71,14,401,0,0,1,4,0,29,0,Kh101:2;Iskander:2;Kinzhal:1 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,3,20,200,46,39,6,60,12,344,0,0,1,2,0,22,0,Shahed:1;Kh101:2;Iskander:3 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,4,20,200,46,40,6,73,13,495,0,0,0,7,0,26,0,Geran3:1;Kh22:2;Iskander:2;Kinzhal:1 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,5,20,200,46,41,5,71,17,455,0,0,0,2,0,34,0,Kh101:1;Kh22:1;Iskander:2;Kinzhal:1 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,6,20,200,46,39,7,71,13,450,0,0,0,4,0,29,0,Kh101:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,7,20,200,46,40,6,84,14,465,0,0,0,11,0,33,0,Kh101:1;Kh22:1;Zircon:1;Iskander:1;Kinzhal:2 +near200,dc36d67eb740e6a9fb56224b32b007e3bf2512b9,8,20,200,46,38,8,67,15,563,0,0,0,4,0,31,0,Kh101:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,1,20,200,46,41,5,84,16,404,0,0,0,8,0,35,0,Geran3:1;Kh22:1;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,2,20,200,46,38,7,70,15,393,0,0,1,4,0,30,0,Shahed:1;Kh101:2;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,3,20,200,46,39,6,61,13,338,0,0,1,4,0,27,0,Shahed:1;Kh101:2;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,4,20,200,46,38,7,74,14,467,0,0,0,3,0,36,0,Geran3:1;Kh22:2;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,5,20,200,46,40,6,73,13,481,0,0,0,8,0,32,0,Kh101:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:1 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,6,20,200,46,38,8,66,14,540,0,0,0,2,0,27,0,Kh101:3;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,7,20,200,46,39,7,86,16,496,0,0,0,9,0,39,0,Kh101:1;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 +near200,4b013fb7f30dbaa87a266b27a31911937fc523b4,8,20,200,46,38,8,73,15,585,0,0,0,4,0,34,0,Kh101:2;Kh22:1;Zircon:1;Iskander:2;Kinzhal:2 diff --git a/docs/measurements/issue-129-pair.json b/docs/measurements/issue-129-pair.json new file mode 100644 index 0000000..7d645af --- /dev/null +++ b/docs/measurements/issue-129-pair.json @@ -0,0 +1,150 @@ +{ + "schema": 1, + "world": "ColdLaunch129", + "scenario": "pair129", + "scenario_seed": 965460218386186365, + "scenario_file": "scenario 1 / sam s400 1.5 72 8.5 / roles s400 31 / wave 0,0,0,0,0,0,1,0,0,0,0 0 900 0 1", + "director": "/airdef replay export best fps=30 size=1280x720 crf=16 sec=24", + "launch_shot_start_frame": 94, + "launch_shot_start_seconds": 3.1, + "center_region": "x 590..689, y 300..419 in 1280x720", + "center_threshold": 100.0, + "before": { + "code_commit": "dc36d67eb740e6a9fb56224b32b007e3bf2512b9", + "artifact_sha256": "2d828aa8b865c8d5e6ba52be91c4d6b380811151fb85335cac80f3cbffbd9175", + "source_mp4": "/private/tmp/issue-129-pair-base/airdef-films/pair129-before.mp4", + "source_sha256": "fdc828199666fa986effd4feafb8e90b9d2646f64f7f4d4ac1afd90de5002301", + "source_size": 17268968, + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "r_frame_rate": "30/1", + "nb_frames": "403" + }, + { + "codec_name": "aac", + "sample_rate": "48000", + "r_frame_rate": "0/0", + "nb_frames": "631" + } + ], + "format": { + "duration": "13.433333", + "size": "17268968" + } + }, + "center_illumination_frame": 101, + "center_illumination_seconds_after_launch": 0.23333333333333334, + "launch_shot_end_frame": 151, + "cold_ticks": 5, + "cold_spec_seconds": 0.25, + "audio_rms_window_s": 0.05, + "audio_onset_threshold_rms": 0.03, + "audio_onset_s": 3.45, + "audio_onset_after_launch_s": 0.35 + }, + "after": { + "code_commit": "00f0209942868a10624117abb799b56407e96d5f", + "artifact_sha256": "ca3bdd305f0149f627a8d733c148525421c987c5486fc246664ad6c980c1f680", + "source_mp4": "/private/tmp/issue-129-pair-after/airdef-films/pair129-after.mp4", + "source_sha256": "fb381cce5d83cb98eb43ba35d8434bc2f3e514970cf418fd301366a14f8c80ca", + "source_size": 17832718, + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "r_frame_rate": "30/1", + "nb_frames": "412" + }, + { + "codec_name": "aac", + "sample_rate": "48000", + "r_frame_rate": "0/0", + "nb_frames": "645" + } + ], + "format": { + "duration": "13.733333", + "size": "17832718" + } + }, + "center_illumination_frame": 121, + "center_illumination_seconds_after_launch": 0.9, + "launch_shot_end_frame": 160, + "cold_ticks": 20, + "cold_spec_seconds": 1.0, + "audio_rms_window_s": 0.05, + "audio_onset_threshold_rms": 0.03, + "audio_onset_s": 4.15, + "audio_onset_after_launch_s": 1.05, + "code_commit_note": "hmod identity 00f020994; the code is the same as 4b013fb7 (only docs changed after it)" + }, + "docs_copies": { + "before_mp4": { + "path": "docs/media/cold-launch/cold-launch-129-before.mp4", + "size": 5831407, + "sha256": "ef5fb96738d5092f0282ac1c5d0ab241eb2ba8dc2114a8f466b1151b2a9c639e", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "r_frame_rate": "30/1", + "nb_frames": "403" + }, + { + "codec_name": "aac", + "sample_rate": "48000", + "r_frame_rate": "0/0", + "nb_frames": "631" + } + ], + "format": { + "duration": "13.433333", + "size": "5831407" + } + } + }, + "after_mp4": { + "path": "docs/media/cold-launch/cold-launch-129-after.mp4", + "size": 6137154, + "sha256": "d165b2d2ac3c79dd8c57fb1cc477e1ae5ec43ab92b8bab6e9a6a5ffe3d315d98", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "r_frame_rate": "30/1", + "nb_frames": "412" + }, + { + "codec_name": "aac", + "sample_rate": "48000", + "r_frame_rate": "0/0", + "nb_frames": "645" + } + ], + "format": { + "duration": "13.733333", + "size": "6137154" + } + } + }, + "note": "re-encoded h264 crf 26 for storage; frame count, frame rate, duration and AAC track preserved" + } +} diff --git a/docs/measurements/mass-acoustics-2026-11-04.json b/docs/measurements/mass-acoustics-2026-11-04.json new file mode 100644 index 0000000..6faacdf --- /dev/null +++ b/docs/measurements/mass-acoustics-2026-11-04.json @@ -0,0 +1,27 @@ +{ + "audio_ms_per_tick": 50, + "audio_sha256": "8b5eeabb2585e2d8b2524a9fe992b028b2eba8c413f5c822d2a68ef352689b25", + "capture_reset": "next capture produced exact zero samples", + "channels": 2, + "command": "HORIZON_ACOUSTICS_OUT=/scratch cargo test --release --manifest-path horizon/Cargo.toml -p horizon-client --no-default-features --lib -j 2 audio::tests::export_moving_mass_raid -- --ignored --nocapture", + "fixture_commit": "e44ad82a9", + "gpu_or_windows_fps_measured": false, + "machine": "Apple M5 / macOS arm64", + "max_direct_cells_per_tick": 4056, + "max_echo_cells_per_tick": 36835, + "max_sources": 116, + "output_device": false, + "profile": "release", + "rate": 48000, + "real_mp4_checked": false, + "rust": "1.99.1", + "sample_determinism": "two full runs compared sample-for-sample in the test", + "schema": 1, + "scope": "synthetic geometry and samples through production Audio::tick_acoustics and film_mix, excludes game snapshot construction and GPU/encoder", + "seconds": 6, + "step_plus_mix_ms_max": 5.573375, + "step_plus_mix_ms_p50": 3.551625, + "step_plus_mix_ms_p95": 4.297167, + "synthetic": true, + "ticks": 120 +} diff --git a/docs/measurements/max-raid-fuel/after.txt b/docs/measurements/max-raid-fuel/after.txt new file mode 100644 index 0000000..be49943 --- /dev/null +++ b/docs/measurements/max-raid-fuel/after.txt @@ -0,0 +1,5 @@ +running 1 scripts against /tmp/airdef-raiddiag/airdef-mod/target/horizon-mod/airdef.hmod +fuel airdef command: calls=69 max=92658271 budget=1000000000 failures=0 +fuel airdef tick: calls=3602 max=135273690 budget=300000000 failures=0 +test tests/mix.toml ... ok (421 steps) +test result: ok. 1 passed diff --git a/docs/measurements/max-raid-fuel/before.txt b/docs/measurements/max-raid-fuel/before.txt new file mode 100644 index 0000000..93d78a8 --- /dev/null +++ b/docs/measurements/max-raid-fuel/before.txt @@ -0,0 +1,6 @@ +running 1 scripts against /tmp/airdef-raiddiag/airdef-mod/target/horizon-mod/airdef.hmod +fuel airdef command: calls=68 max=92658271 budget=1000000000 failures=0 +fuel airdef tick: calls=1835 max=300000000 budget=300000000 failures=1 +test tests/mix.toml ... FAILED + step 244 (10 ticks, 5 frames): the mod stopped: it exceeded its execution limit +error: 1 failed, 0 passed diff --git a/docs/measurements/max-raid-fuel/checkpoint-after.txt b/docs/measurements/max-raid-fuel/checkpoint-after.txt new file mode 100644 index 0000000..9581490 --- /dev/null +++ b/docs/measurements/max-raid-fuel/checkpoint-after.txt @@ -0,0 +1,8 @@ +running 1 scripts against /tmp/raiddiag-fixed-instrumented.hmod +fuel airdef command: calls=68 max=92323272 budget=1000000000 failures=0 +fuel airdef tick: calls=1834 max=136123919 budget=300000000 failures=0 +test tests/mix.toml ... FAILED + step 244 (/airdef stats): feedback + expected: Some("probe") + got: Some("В воздухе: 489, ракет: 0, ПВО: 64, сбито: 209, поражений: 2447, разбились о препятствия: 4; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 11990 секций (562 КБ), запросов 44224, неизвестно 0, лучей 574840 (ударов 2867, неизв. 4700), клеток 10800173; взрывы в блоках: 2669 (подтверждено 0, без перемен 651, нет чанка 1094), чужих ответов 0") +error: 1 failed, 0 passed diff --git a/docs/measurements/max-raid-fuel/checkpoint-before.txt b/docs/measurements/max-raid-fuel/checkpoint-before.txt new file mode 100644 index 0000000..4ca505f --- /dev/null +++ b/docs/measurements/max-raid-fuel/checkpoint-before.txt @@ -0,0 +1,8 @@ +running 1 scripts against /tmp/raiddiag-original.hmod +fuel airdef command: calls=68 max=92232498 budget=1000000000 failures=0 +fuel airdef tick: calls=1834 max=295473677 budget=300000000 failures=0 +test tests/mix.toml ... FAILED + step 244 (/airdef stats): feedback + expected: Some("probe") + got: Some("В воздухе: 489, ракет: 0, ПВО: 64, сбито: 209, поражений: 2447, разбились о препятствия: 4; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 11990 секций (562 КБ), запросов 44224, неизвестно 0, лучей 574840 (ударов 2867, неизв. 4700), клеток 10800173; взрывы в блоках: 2669 (подтверждено 0, без перемен 651, нет чанка 1094), чужих ответов 0") +error: 1 failed, 0 passed diff --git a/docs/measurements/max-raid-fuel/scenario.toml b/docs/measurements/max-raid-fuel/scenario.toml new file mode 100644 index 0000000..af52b0d --- /dev/null +++ b/docs/measurements/max-raid-fuel/scenario.toml @@ -0,0 +1,1619 @@ +[[step]] +render-ground-y = 64 +ticks = 2 +frames = 2 +expect.commands = ["airdef"] + +[[step]] +command = "airdef shader hd" +expect.feedback = "Эффекты: HD" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef deploy patriot" + +[[step]] +command = "airdef deploy pantsir" + +[[step]] +command = "airdef deploy gepard" + +[[step]] +command = "airdef deploy nasams" + +[[step]] +command = "airdef deploy irist" + +[[step]] +command = "airdef deploy tor" + +[[step]] +command = "airdef deploy s400" + +[[step]] +command = "airdef grid" + +[[step]] +command = "airdef wave mix 80 300" + +[[step]] +ticks = 100 +frames = 10 + +[[step]] +command = "airdef wave mix 80 300" +expect.chat = [] + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +ticks = 10 +frames = 5 + +[[step]] +command = "airdef stats" +expect.feedback = "В воздухе: 0, ракет: 0, ПВО: 64, сбито: 209, поражений: 3435, разбились о препятствия: 15; удары по ПУ: пущено 0, уничтожено 0, подавлено 0, промахов 0, сбито ПВО 0; мир: 6525 секций (305 КБ), запросов 54263, неизвестно 0, лучей 687603 (ударов 2975, неизв. 5884), клеток 12713149; взрывы в блоках: 3668 (подтверждено 0, без перемен 965, нет чанка 1793), чужих ответов 0" diff --git a/docs/measurements/max-raid-fuel/summary.json b/docs/measurements/max-raid-fuel/summary.json new file mode 100644 index 0000000..ad5e1e9 --- /dev/null +++ b/docs/measurements/max-raid-fuel/summary.json @@ -0,0 +1,57 @@ +{ + "baseline": { + "at_tick": 1835, + "callback": "tick", + "failures": 1, + "fuel_budget": 300000000, + "max_used": 300000000, + "stopped": true + }, + "baseline_guest_sha256": "572f4c4ed320e183a9e59455a23e1c871b447309bd0805cdfbcc4a12aeadaa2e", + "baseline_source": "10f24b5", + "checkpoint": { + "after_max_tick_fuel": 136123919, + "after_profile_has_phase_markers": true, + "before_max_tick_fuel": 295473677, + "expected_probe_assertions_fail_deliberately": true, + "reported_stats_equal": true, + "scope": "equality of reported combat/world counters, not every hidden simulation field", + "stats_sha256": "8a7b2f7ec19a1ce462807bde24923f7d97ae46841cc600e43bb722b8933243f7", + "tick": 1834 + }, + "fixed": { + "airborne_at_end": 0, + "collisions_at_end": 15, + "command_budget": 1000000000, + "failures": 0, + "hits_at_end": 3435, + "kills_at_end": 209, + "max_command_fuel": 92658271, + "max_tick_fuel": 135273690, + "passed": true, + "steps": 421, + "tick_budget": 300000000, + "ticks": 3602 + }, + "fixed_guest_sha256": "0d59654fba9842d481a7f04e84d795f5fb2f7be8389e917e35c5598876f3a415", + "gpu_or_fps_measured": false, + "guest_change": "trigger.patch replaces only mix command composition with the exact maximum UI WaveConfig; no gameplay or rendering feature is disabled", + "kind": "diagnostic_guest_comparison_not_exact_user_session", + "limits_unchanged": true, + "production_fix": "1f28f83", + "scenario_sha256": "af4e0486256239d87c940bfba81caedb47914ad7aaf6b14806c9745dc0979e1c", + "schema": 1, + "setup": { + "defenses": 64, + "distance_blocks": 300, + "each_of_11_kinds": 40, + "grid": true, + "hd": true, + "per_order": 440, + "raid_orders": 2, + "recording": true, + "smoke_update": true, + "synthetic_ground_y": 64 + }, + "user_windows_session_confirmed_same_cause": false +} diff --git a/docs/measurements/max-raid-fuel/trigger.patch b/docs/measurements/max-raid-fuel/trigger.patch new file mode 100644 index 0000000..fe34fdc --- /dev/null +++ b/docs/measurements/max-raid-fuel/trigger.patch @@ -0,0 +1,18 @@ +diff --git a/airdef-mod/src/lib.rs b/airdef-mod/src/lib.rs +index 2255f57..f1da2c6 100644 +--- a/airdef-mod/src/lib.rs ++++ b/airdef-mod/src/lib.rs +@@ -2338,7 +2338,12 @@ impl Mod for AirDef { + .unwrap_or(DVec3::new(p.x, p.y, p.z)); + if *kind == "mix" { + let objects = self.objects(DVec3::new(p.x, p.y, p.z)); +- self.sim.order_raid(&wave::WaveConfig::mix(count, dist), &objects, &mut out); ++ let mut wave = wave::WaveConfig::default(); ++ wave.counts = [40; wave::KINDS.len()]; ++ wave.dist = dist; ++ wave.approach = sim::Approach::AllSides; ++ wave.aim = sim::Aim::Smart; ++ self.sim.order_raid(&wave.order(), &objects, &mut out); + } else { + let kind = DroneKind::from_key(kind).ok_or( + "Тип: shahed, geran3, lancet, gerbera, kh101, kalibr, kh22, zircon, iskander, kinzhal, oreshnik или mix", diff --git a/docs/media/aircraft-pass/kh101-after-nose.png b/docs/media/aircraft-pass/kh101-after-nose.png new file mode 100644 index 0000000..064f861 Binary files /dev/null and b/docs/media/aircraft-pass/kh101-after-nose.png differ diff --git a/docs/media/aircraft-pass/kh101-after-top.png b/docs/media/aircraft-pass/kh101-after-top.png new file mode 100644 index 0000000..cd1a6c0 Binary files /dev/null and b/docs/media/aircraft-pass/kh101-after-top.png differ diff --git a/docs/media/aircraft-pass/kh101-before-nose.png b/docs/media/aircraft-pass/kh101-before-nose.png new file mode 100644 index 0000000..670f696 Binary files /dev/null and b/docs/media/aircraft-pass/kh101-before-nose.png differ diff --git a/docs/media/aircraft-pass/kh101-before-top.png b/docs/media/aircraft-pass/kh101-before-top.png new file mode 100644 index 0000000..65c6286 Binary files /dev/null and b/docs/media/aircraft-pass/kh101-before-top.png differ diff --git a/docs/media/aircraft-pass/lancet-after-nose.png b/docs/media/aircraft-pass/lancet-after-nose.png new file mode 100644 index 0000000..2100e69 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-after-nose.png differ diff --git a/docs/media/aircraft-pass/lancet-after-top.png b/docs/media/aircraft-pass/lancet-after-top.png new file mode 100644 index 0000000..af5a430 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-after-top.png differ diff --git a/docs/media/aircraft-pass/lancet-before-nose.png b/docs/media/aircraft-pass/lancet-before-nose.png new file mode 100644 index 0000000..48efa3b Binary files /dev/null and b/docs/media/aircraft-pass/lancet-before-nose.png differ diff --git a/docs/media/aircraft-pass/lancet-before-top.png b/docs/media/aircraft-pass/lancet-before-top.png new file mode 100644 index 0000000..1a522bd Binary files /dev/null and b/docs/media/aircraft-pass/lancet-before-top.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-after-nose.png b/docs/media/aircraft-pass/lancet-instance-after-nose.png new file mode 100644 index 0000000..3478441 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-after-nose.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-after-top.png b/docs/media/aircraft-pass/lancet-instance-after-top.png new file mode 100644 index 0000000..04000dc Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-after-top.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-before-nose.png b/docs/media/aircraft-pass/lancet-instance-before-nose.png new file mode 100644 index 0000000..40e8410 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-before-nose.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-before-top.png b/docs/media/aircraft-pass/lancet-instance-before-top.png new file mode 100644 index 0000000..0045fdf Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-before-top.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-normal-before.png b/docs/media/aircraft-pass/lancet-instance-normal-before.png new file mode 100644 index 0000000..ef5f67c Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-normal-before.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-normal-fix-night.png b/docs/media/aircraft-pass/lancet-instance-normal-fix-night.png new file mode 100644 index 0000000..f2f61c8 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-normal-fix-night.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-normal-fix-top.png b/docs/media/aircraft-pass/lancet-instance-normal-fix-top.png new file mode 100644 index 0000000..9dcbb79 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-normal-fix-top.png differ diff --git a/docs/media/aircraft-pass/lancet-instance-normal-fix.png b/docs/media/aircraft-pass/lancet-instance-normal-fix.png new file mode 100644 index 0000000..97dc1f7 Binary files /dev/null and b/docs/media/aircraft-pass/lancet-instance-normal-fix.png differ diff --git a/docs/media/cold-launch/cold-launch-129-after.mp4 b/docs/media/cold-launch/cold-launch-129-after.mp4 new file mode 100644 index 0000000..bfca93e Binary files /dev/null and b/docs/media/cold-launch/cold-launch-129-after.mp4 differ diff --git a/docs/media/cold-launch/cold-launch-129-before.mp4 b/docs/media/cold-launch/cold-launch-129-before.mp4 new file mode 100644 index 0000000..f51846f Binary files /dev/null and b/docs/media/cold-launch/cold-launch-129-before.mp4 differ diff --git a/docs/media/cold-launch/cold-launch-129-pair-0.5s.png b/docs/media/cold-launch/cold-launch-129-pair-0.5s.png new file mode 100644 index 0000000..6050be3 Binary files /dev/null and b/docs/media/cold-launch/cold-launch-129-pair-0.5s.png differ diff --git a/docs/media/cold-launch/cold-launch-129-pair-1.0s.png b/docs/media/cold-launch/cold-launch-129-pair-1.0s.png new file mode 100644 index 0000000..b17bdc6 Binary files /dev/null and b/docs/media/cold-launch/cold-launch-129-pair-1.0s.png differ diff --git a/docs/media/cold-launch/cold-launch-129-pair-1.5s.png b/docs/media/cold-launch/cold-launch-129-pair-1.5s.png new file mode 100644 index 0000000..c39fb9b Binary files /dev/null and b/docs/media/cold-launch/cold-launch-129-pair-1.5s.png differ diff --git a/docs/media/cold-launch/cold-launch-129-pair-general.png b/docs/media/cold-launch/cold-launch-129-pair-general.png new file mode 100644 index 0000000..ea93bc9 Binary files /dev/null and b/docs/media/cold-launch/cold-launch-129-pair-general.png differ diff --git a/docs/media/cold-launch/s400-closed.png b/docs/media/cold-launch/s400-closed.png new file mode 100644 index 0000000..0fa9899 Binary files /dev/null and b/docs/media/cold-launch/s400-closed.png differ diff --git a/docs/media/cold-launch/s400-one-cell-used.png b/docs/media/cold-launch/s400-one-cell-used.png new file mode 100644 index 0000000..cd5aaba Binary files /dev/null and b/docs/media/cold-launch/s400-one-cell-used.png differ diff --git a/docs/media/model-pilots/after-gepard-day.png b/docs/media/model-pilots/after-gepard-day.png new file mode 100644 index 0000000..92ac53c Binary files /dev/null and b/docs/media/model-pilots/after-gepard-day.png differ diff --git a/docs/media/model-pilots/after-overview-hd.png b/docs/media/model-pilots/after-overview-hd.png new file mode 100644 index 0000000..b160a37 Binary files /dev/null and b/docs/media/model-pilots/after-overview-hd.png differ diff --git a/docs/media/model-pilots/after-overview-night-hd.png b/docs/media/model-pilots/after-overview-night-hd.png new file mode 100644 index 0000000..03edbb6 Binary files /dev/null and b/docs/media/model-pilots/after-overview-night-hd.png differ diff --git a/docs/media/model-pilots/after-s400-day.png b/docs/media/model-pilots/after-s400-day.png new file mode 100644 index 0000000..09be954 Binary files /dev/null and b/docs/media/model-pilots/after-s400-day.png differ diff --git a/docs/media/model-pilots/after-shahed-day.png b/docs/media/model-pilots/after-shahed-day.png new file mode 100644 index 0000000..443dc1a Binary files /dev/null and b/docs/media/model-pilots/after-shahed-day.png differ diff --git a/docs/media/model-pilots/before-gepard-day.png b/docs/media/model-pilots/before-gepard-day.png new file mode 100644 index 0000000..1b68645 Binary files /dev/null and b/docs/media/model-pilots/before-gepard-day.png differ diff --git a/docs/media/model-pilots/before-overview-hd.png b/docs/media/model-pilots/before-overview-hd.png new file mode 100644 index 0000000..7ccad67 Binary files /dev/null and b/docs/media/model-pilots/before-overview-hd.png differ diff --git a/docs/media/model-pilots/before-s400-day.png b/docs/media/model-pilots/before-s400-day.png new file mode 100644 index 0000000..12bb31f Binary files /dev/null and b/docs/media/model-pilots/before-s400-day.png differ diff --git a/docs/media/model-pilots/before-shahed-day.png b/docs/media/model-pilots/before-shahed-day.png new file mode 100644 index 0000000..1aa8ec0 Binary files /dev/null and b/docs/media/model-pilots/before-shahed-day.png differ diff --git a/docs/media/vehicle-pass/buk-fixed.png b/docs/media/vehicle-pass/buk-fixed.png new file mode 100644 index 0000000..d569205 Binary files /dev/null and b/docs/media/vehicle-pass/buk-fixed.png differ diff --git a/docs/media/vehicle-pass/buk-front.png b/docs/media/vehicle-pass/buk-front.png new file mode 100644 index 0000000..c4ce414 Binary files /dev/null and b/docs/media/vehicle-pass/buk-front.png differ diff --git a/docs/media/vehicle-pass/burnt-line.png b/docs/media/vehicle-pass/burnt-line.png new file mode 100644 index 0000000..5b39b25 Binary files /dev/null and b/docs/media/vehicle-pass/burnt-line.png differ diff --git a/docs/media/vehicle-pass/irist-back.png b/docs/media/vehicle-pass/irist-back.png new file mode 100644 index 0000000..b1e787c Binary files /dev/null and b/docs/media/vehicle-pass/irist-back.png differ diff --git a/docs/media/vehicle-pass/pantsir-front.png b/docs/media/vehicle-pass/pantsir-front.png new file mode 100644 index 0000000..d227f9d Binary files /dev/null and b/docs/media/vehicle-pass/pantsir-front.png differ diff --git a/docs/media/vehicle-pass/patriot-front.png b/docs/media/vehicle-pass/patriot-front.png new file mode 100644 index 0000000..1a10d52 Binary files /dev/null and b/docs/media/vehicle-pass/patriot-front.png differ diff --git a/docs/media/vehicle-pass/tzm-front.png b/docs/media/vehicle-pass/tzm-front.png new file mode 100644 index 0000000..44a8dac Binary files /dev/null and b/docs/media/vehicle-pass/tzm-front.png differ diff --git a/horizon b/horizon new file mode 160000 index 0000000..278f356 --- /dev/null +++ b/horizon @@ -0,0 +1 @@ +Subproject commit 278f356aa604fa21c2d15d0081823bbe0f891df2 diff --git a/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch b/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch deleted file mode 100644 index bb2073d..0000000 --- a/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch +++ /dev/null @@ -1,577 +0,0 @@ -From 3de7a6875140a107e28563cc44489b55f5794517 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 12:37:45 +0300 -Subject: [PATCH 01/42] Mod API (AirDef fork): world models, client particles, - sounds, ground heights - -render.set-models draws block models with an interpolated pose per id; -render.spawn-particles and render.play-sound act on this client only; -render.query-ground / render.ground read the motion-blocking heightmap. - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game.rs | 2 + - crates/horizon-client/src/game/mod_models.rs | 197 +++++++++++++++++++ - crates/horizon-client/src/game/mods.rs | 24 +++ - crates/horizon-mod-sdk/wit/horizon.wit | 58 ++++++ - crates/horizon-mods/src/api_level.rs | 10 +- - crates/horizon-mods/src/runtime.rs | 116 ++++++++++- - 6 files changed, 403 insertions(+), 4 deletions(-) - create mode 100644 crates/horizon-client/src/game/mod_models.rs - -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index 1d7bbbbd6..7c342cd86 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -115,6 +115,7 @@ mod mod_input; - #[cfg(feature = "mods")] - mod mod_recipes; - mod mod_render; -+mod mod_models; - mod mods; - mod mount_menu; - mod mounts; -@@ -8232,6 +8233,7 @@ impl Game<'_> { - } - } - self.mark_entity(out.len(), ""); -+ self.mod_model_geometry(camera, alpha, &mut out); - self.moving_piston_geometry(camera, alpha, &mut out); - self.mark_entity(out.len(), "minecraft:tnt"); - self.tnt_geometry(camera, alpha, &mut out); -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -new file mode 100644 -index 000000000..3c7f9345c ---- /dev/null -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -0,0 +1,197 @@ -+//! AirDef fork: capability `render`'s world models, client particles and -+//! sounds. A mod sets its models once a tick (`render.set-models`); each id -+//! keeps its last two poses and is drawn between them every frame, so models -+//! move smoothly at any frame rate. -+use super::Game; -+#[cfg(feature = "mods")] -+use { -+ glam::{DVec3, Mat4, Quat, Vec3}, -+ horizon_client::{ -+ mesher::Vertex, -+ particles::{ParticleOptions, ParticleType}, -+ }, -+ horizon_core::random::RandomSource, -+ horizon_mods::ParticleSpawn, -+ horizon_world::registry::BlockStateId, -+ std::collections::HashMap, -+}; -+ -+/// One model of a mod: where it was a tick ago and where it is now. -+#[cfg(feature = "mods")] -+struct Pose { -+ state: BlockStateId, -+ previous: DVec3, -+ position: DVec3, -+ old_rotation: Quat, -+ rotation: Quat, -+ old_scale: f32, -+ scale: f32, -+ glow: bool, -+} -+ -+/// The models the running mods keep, by (mod, id). -+#[cfg(feature = "mods")] -+#[derive(Default)] -+pub(super) struct ModModels { -+ poses: HashMap<(usize, u32), Pose>, -+ /// Block states by the text mods name them, parsed once. -+ states: HashMap>, -+} -+ -+/// Full brightness: sky and block light 15, as the entity shader packs them. -+#[cfg(feature = "mods")] -+const GLOW: u32 = 240 | (240 << 8); -+ -+/// A build without the `mods` feature draws no mod models. -+#[cfg(not(feature = "mods"))] -+impl Game<'_> { -+ pub(super) fn mod_model_geometry( -+ &self, -+ _camera: glam::DVec3, -+ _alpha: f64, -+ _out: &mut Vec, -+ ) { -+ } -+} -+ -+#[cfg(feature = "mods")] -+impl Game<'_> { -+ /// After the mods' tick: each model's new pose, its last one kept to -+ /// draw from. Ids no longer set are dropped; new ones start where they -+ /// are. -+ pub(super) fn update_mod_models(&mut self) { -+ let mut seen: Vec<(usize, u32)> = Vec::new(); -+ let registry = &self.pack.assets.registry; -+ let models = &mut self.mods.models; -+ for (owner, m) in self.mods.mods.models() { -+ let state = *models -+ .states -+ .entry(m.model.clone()) -+ .or_insert_with(|| super::render_control::parse_state(registry, &m.model).ok()); -+ let Some(state) = state else { -+ continue; -+ }; -+ let key = (owner, m.id); -+ seen.push(key); -+ let position = DVec3::new(m.x, m.y, m.z); -+ let (x, y, z, w) = m.rotation; -+ let rotation = Quat::from_xyzw(x, y, z, w); -+ match models.poses.get_mut(&key) { -+ Some(p) => { -+ p.previous = p.position; -+ p.old_rotation = p.rotation; -+ p.old_scale = p.scale; -+ p.position = position; -+ p.rotation = rotation; -+ p.scale = m.scale; -+ p.state = state; -+ p.glow = m.glow; -+ } -+ None => { -+ models.poses.insert( -+ key, -+ Pose { -+ state, -+ previous: position, -+ position, -+ old_rotation: rotation, -+ rotation, -+ old_scale: m.scale, -+ scale: m.scale, -+ glow: m.glow, -+ }, -+ ); -+ } -+ } -+ } -+ if seen.len() != models.poses.len() { -+ let seen: std::collections::HashSet<_> = seen.into_iter().collect(); -+ models.poses.retain(|k, _| seen.contains(k)); -+ } -+ } -+ -+ /// Draws every mod model between its last two poses, lit by the world -+ /// (or at full brightness), its faces shaded by its own rotation. -+ pub(super) fn mod_model_geometry(&self, camera: DVec3, alpha: f64, out: &mut Vec) { -+ if self.mods.models.poses.is_empty() { -+ return; -+ } -+ let t = alpha.clamp(0.0, 1.0); -+ for p in self.mods.models.poses.values() { -+ let at = p.previous.lerp(p.position, t); -+ // Far models are culled by distance; the view frustum does the rest. -+ if at.distance_squared(camera) > 512.0 * 512.0 { -+ continue; -+ } -+ let rotation = p.old_rotation.slerp(p.rotation, t as f32); -+ let scale = p.old_scale + (p.scale - p.old_scale) * t as f32; -+ if scale <= 0.0 { -+ continue; -+ } -+ let light = if p.glow { -+ GLOW -+ } else { -+ let l = self.world.light( -+ at.x.floor() as i32, -+ at.y.floor() as i32, -+ at.z.floor() as i32, -+ ); -+ (u32::from(l >> 4) * 16) | ((u32::from(l & 15) * 16) << 8) -+ }; -+ // The model's unit cube turned and scaled about its centre. -+ let m = Mat4::from_scale_rotation_translation( -+ Vec3::splat(scale), -+ rotation, -+ (at - camera).as_vec3(), -+ ) * Mat4::from_translation(Vec3::splat(-0.5)); -+ self.block_model_vertices(p.state, 0, m, light, true, out); -+ } -+ } -+ -+ /// `render.spawn-particles`: made on this client at once. -+ pub(super) fn mod_particles(&mut self, particles: Vec) { -+ for s in particles { -+ let Some(ty) = ParticleType::from_name(&s.particle) else { -+ continue; -+ }; -+ let rgb = [ -+ ((s.color >> 16) & 255) as f32 / 255.0, -+ ((s.color >> 8) & 255) as f32 / 255.0, -+ (s.color & 255) as f32 / 255.0, -+ ]; -+ let rgb = if s.color == 0 { [1.0; 3] } else { rgb }; -+ let options = match ty { -+ ParticleType::Dust => ParticleOptions::dust(rgb, s.scale), -+ ParticleType::EntityEffect | ParticleType::TintedLeaves | ParticleType::Flash => { -+ ParticleOptions::colored(ty, [rgb[0], rgb[1], rgb[2], 1.0]) -+ } -+ ParticleType::Block -+ | ParticleType::FallingDust -+ | ParticleType::DustPillar -+ | ParticleType::BlockMarker -+ | ParticleType::Item -+ | ParticleType::ItemSlime -+ | ParticleType::ItemSnowball -+ | ParticleType::ItemCobweb => continue, -+ _ => ParticleOptions::simple(ty), -+ }; -+ let (sx, sy, sz) = s.spread; -+ let (vx, vy, vz) = s.velocity; -+ let speed = f64::from(s.speed); -+ for _ in 0..s.count { -+ let r = self.particles.random(); -+ let p = [ -+ s.x + r.next_gaussian() * f64::from(sx), -+ s.y + r.next_gaussian() * f64::from(sy), -+ s.z + r.next_gaussian() * f64::from(sz), -+ ]; -+ let v = [ -+ f64::from(vx) + r.next_gaussian() * speed, -+ f64::from(vy) + r.next_gaussian() * speed, -+ f64::from(vz) + r.next_gaussian() * speed, -+ ]; -+ self.particles.create(&options, p, v); -+ } -+ } -+ } -+} -diff --git a/crates/horizon-client/src/game/mods.rs b/crates/horizon-client/src/game/mods.rs -index 52dc46fe0..3eef96aa3 100644 ---- a/crates/horizon-client/src/game/mods.rs -+++ b/crates/horizon-client/src/game/mods.rs -@@ -46,6 +46,9 @@ pub(super) struct ModsState { - /// Capability `render`'s camera shakes. - #[cfg(feature = "mods")] - pub(super) shake: super::mod_render::Shake, -+ /// Capability `render`'s world models (AirDef fork), with their last poses. -+ #[cfg(feature = "mods")] -+ pub(super) models: super::mod_models::ModModels, - /// `--dev-mod PATH`: always on, reloaded when rebuilt. - pub(super) dev: Vec, - /// When dev mods were last checked for a rebuild. -@@ -596,9 +599,17 @@ impl Game<'_> { - return; - } - self.tick_mod_use(); -+ for (i, columns) in self.mods.mods.take_ground_queries() { -+ let ground = columns -+ .into_iter() -+ .map(|(x, z)| (x, z, self.rain_height(x, z))) -+ .collect(); -+ self.mods.mods.answer_ground(i, ground); -+ } - let snapshot = self.mod_snapshot(); - self.mods.mods.set_snapshot(snapshot); - self.mods.mods.tick(self.ticks); -+ self.update_mod_models(); - self.show_mod_output(); - } - /// Restarts the dev mods rebuilt since the last check, once a second. -@@ -716,6 +727,19 @@ impl Game<'_> { - self.mods.shake.add(strength, seconds); - continue; - } -+ Output::Particles { particles } => { -+ self.mod_particles(particles); -+ continue; -+ } -+ Output::Sound { -+ event, -+ at, -+ volume, -+ pitch, -+ } => { -+ self.audio.play(&event, Some(at), volume, pitch); -+ continue; -+ } - Output::PlaceRecipe { book_id, all } => { - self.mod_place_recipe(book_id, all); - continue; -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index 0680cce92..89d1906a9 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -429,6 +429,64 @@ interface render { - /// Distance setting, when on, wins. One far terrain shows at a time: the - /// first loaded mod's. - set-far-terrain: func(far-terrain: option); -+ /// A block model drawn in the world (AirDef fork). -+ record model-instance { -+ /// Stays the same while the object lives: the game moves an id -+ /// smoothly from where the last `set-models` put it. -+ id: u32, -+ /// A block state as commands write it (`airdef:shahed`, -+ /// `minecraft:stone`, `minecraft:furnace[facing=east]`): its block -+ /// model is drawn. An unknown one draws nothing. -+ model: string, -+ /// Where the model's centre is, in blocks. -+ x: f64, -+ y: f64, -+ z: f64, -+ /// Rotation about the model's centre: a quaternion (x, y, z, w). -+ rotation: tuple, -+ /// Uniform scale, 1.0 a block (0 to 64). -+ scale: f32, -+ /// Drawn at full brightness, whatever the light where it is. -+ glow: bool, -+ } -+ /// Replaces the mod's models: up to 4096, drawn with the world's -+ /// entities until replaced. Call it once a tick: the game interpolates -+ /// each id between its last two positions and rotations every frame. -+ /// An empty list removes them. -+ set-models: func(models: list); -+ /// Particles to spawn on this client (AirDef fork). -+ record particle-spawn { -+ /// As `/particle` names the type: `minecraft:flame`, `minecraft:dust`. -+ particle: string, -+ x: f64, -+ y: f64, -+ z: f64, -+ /// Gaussian spread round the point, per axis, in blocks. -+ spread: tuple, -+ /// Every particle's starting velocity, blocks a tick. -+ velocity: tuple, -+ /// Gaussian speed added to it per axis. -+ speed: f32, -+ /// 1 to 256. -+ count: u32, -+ /// 0xRRGGBB for dust and coloured types (0: white). -+ color: u32, -+ /// Dust size (0.01 to 4). -+ scale: f32, -+ } -+ /// Spawns particles on this client only: no command, no server. -+ /// At most 512 spawns a call. -+ spawn-particles: func(particles: list); -+ /// Plays a sound event (`minecraft:entity.generic.explode`, -+ /// `:`) at a point on this client. -+ play-sound: func(event: string, x: f64, y: f64, z: f64, volume: f32, pitch: f32); -+ /// Asks for the ground height of these columns (x, z): at most 512 a -+ /// tick, answered by `ground` from the next tick on. -+ query-ground: func(columns: list>); -+ /// The answers to the last tick's `query-ground`: (x, z, y), y one above -+ /// the highest block that blocks motion or holds a fluid, as the motion -+ /// blocking heightmap; -64 where the chunk isn't loaded. -+ ground: func() -> list>; - } - - /// What the game calls. Only the hooks `init` asks for are ever called. -diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs -index 045a9346b..b64792eaa 100644 ---- a/crates/horizon-mods/src/api_level.rs -+++ b/crates/horizon-mods/src/api_level.rs -@@ -7,13 +7,13 @@ - //! version's level so the launcher can tell before it installs. - - /// The newest level this Horizon serves. --pub const LEVEL: u32 = 7; -+pub const LEVEL: u32 = 8; - - /// Every function the game offers mods, as `interface.function`, with its - /// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new - /// function takes the next level and raises [`LEVEL`] (the `api_reference` - /// test fails until it is listed here). --pub const FUNCTIONS: [(&str, u32); 46] = [ -+pub const FUNCTIONS: [(&str, u32); 51] = [ - ("log.write", 0), - ("game.get-player", 0), - ("game.get-clock", 0), -@@ -60,6 +60,12 @@ pub const FUNCTIONS: [(&str, u32); 46] = [ - ("recipes.place", 6), - ("recipes.give", 6), - ("render.set-far-terrain", 7), -+ // AirDef fork: world models, client particles and sounds. -+ ("render.set-models", 8), -+ ("render.spawn-particles", 8), -+ ("render.play-sound", 8), -+ ("render.query-ground", 8), -+ ("render.ground", 8), - ]; - - /// What the game's program carries so the launcher can read its level -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index cff44722b..317cd4f06 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -71,6 +71,7 @@ mod render_bindings { - wasmtime::component::bindgen!({ - path: "../horizon-mod-sdk/wit", - world: "render-host", -+ additional_derives: [PartialEq], - }); - } - use render_bindings::horizon::mods::input; -@@ -79,8 +80,8 @@ pub use render_bindings::horizon::mods::input::{Event as InputEvent, HeldItem, I - /// What capability `render` asks the game to draw (WIT `interface render`). - pub use render_bindings::horizon::mods::render::{ - Corner as RenderCorner, FarTerrain as ModFarTerrain, FillShape, FrameRate, ItemShape, -- Minimap as ModMinimap, Point as RenderPoint, QuadShape, Rect as RenderRect, Shape, SpriteShape, -- TextShape, Zoom as ModZoom, -+ Minimap as ModMinimap, ModelInstance, ParticleSpawn, Point as RenderPoint, QuadShape, -+ Rect as RenderRect, Shape, SpriteShape, TextShape, Zoom as ModZoom, - }; - use render_bindings::{RenderHost, horizon::mods::render}; - /// Capability `recipes`'s imports (docs/mods.md): the game's recipes and items. -@@ -238,6 +239,15 @@ pub enum Output { - CloseScreen { owner: usize }, - /// `render.shake`: strength 0 to 1, fading out over `seconds` (≤ 10). - Shake { strength: f32, seconds: f32 }, -+ /// `render.spawn-particles` (AirDef fork): particles to make on this client. -+ Particles { particles: Vec }, -+ /// `render.play-sound` (AirDef fork): a sound event at a point on this client. -+ Sound { -+ event: String, -+ at: [f64; 3], -+ volume: f32, -+ pitch: f32, -+ }, - /// `recipes.place`: the recipe book's click on this display id, all of - /// it when `all`. - PlaceRecipe { book_id: i32, all: bool }, -@@ -416,6 +426,12 @@ struct State { - minimap: Option, - /// Capability `render`'s far terrain, when the mod draws one. - far_terrain: Option, -+ /// Capability `render`'s world models (AirDef fork), drawn until replaced. -+ models: Vec, -+ /// `render.query-ground`'s columns, answered before the next tick. -+ ground_queries: Vec<(i32, i32)>, -+ /// The game's answers to them. -+ ground: Vec<(i32, i32, i32)>, - input: InputState, - browser: BrowserState, - /// Capability `storage`'s folder; made on first `set`. -@@ -619,6 +635,9 @@ impl Mods { - zoom: None, - minimap: None, - far_terrain: None, -+ models: Vec::new(), -+ ground_queries: Vec::new(), -+ ground: Vec::new(), - input: InputState::default(), - browser: BrowserState::default(), - data_dir, -@@ -1173,6 +1192,32 @@ impl Mods { - .iter() - .find_map(|e| e.run.as_ref()?.store.data().minimap) - } -+ /// Every running mod's world models (AirDef fork), with the mod's index -+ /// so ids of different mods don't meet. -+ pub fn models(&self) -> impl Iterator { -+ self.entries.iter().enumerate().flat_map(|(i, e)| { -+ e.run -+ .iter() -+ .flat_map(move |r| r.store.data().models.iter().map(move |m| (i, m))) -+ }) -+ } -+ /// The columns each running mod asked the ground height of, taken. -+ pub fn take_ground_queries(&mut self) -> Vec<(usize, Vec<(i32, i32)>)> { -+ self.entries -+ .iter_mut() -+ .enumerate() -+ .filter_map(|(i, e)| { -+ let q = std::mem::take(&mut e.run.as_mut()?.store.data_mut().ground_queries); -+ (!q.is_empty()).then_some((i, q)) -+ }) -+ .collect() -+ } -+ /// Mod `i`'s answers to its ground queries, for its next calls. -+ pub fn answer_ground(&mut self, i: usize, ground: Vec<(i32, i32, i32)>) { -+ if let Some(run) = self.entries.get_mut(i).and_then(|e| e.run.as_mut()) { -+ run.store.data_mut().ground = ground; -+ } -+ } - /// The far terrain the first running mod draws (capability `render`). - pub fn far_terrain(&self) -> Option { - self.entries -@@ -1734,6 +1779,12 @@ const MINIMAP_SIZE: (f32, f32) = (16.0, 256.0); - const MINIMAP_SCALE: (f32, f32) = (0.5, 4.0); - /// `set-far-terrain`'s distance in chunks, as the LOD Distance setting's. - const FAR_TERRAIN_DISTANCE: (u32, u32) = (64, 1024); -+/// `set-models`' most models a mod keeps, and their largest scale. -+const MODELS: usize = 4096; -+const MODEL_SCALE: f32 = 64.0; -+/// `spawn-particles`' most spawns a call and particles a spawn. -+const PARTICLE_SPAWNS: usize = 512; -+const PARTICLES_PER_SPAWN: u32 = 256; - - /// A mod's corner panel: shapes in a `width` × `height` box the game - /// places in `corner`, stacked with the other overlays. -@@ -1851,6 +1902,67 @@ impl render::Host for State { - self.out.push(Output::Shake { strength, seconds }); - } - } -+ fn set_models(&mut self, models: Vec) { -+ let finite = |v: f64| v.is_finite() && v.abs() < 30_000_000.0; -+ self.models = models -+ .into_iter() -+ .filter(|m| finite(m.x) && finite(m.y) && finite(m.z)) -+ .take(MODELS) -+ .map(|mut m| { -+ let (x, y, z, w) = m.rotation; -+ let len = (x * x + y * y + z * z + w * w).sqrt(); -+ m.rotation = if len.is_finite() && len > 1e-6 { -+ (x / len, y / len, z / len, w / len) -+ } else { -+ (0.0, 0.0, 0.0, 1.0) -+ }; -+ m.scale = if m.scale.is_finite() { -+ m.scale.clamp(0.0, MODEL_SCALE) -+ } else { -+ 1.0 -+ }; -+ m.model = clip(m.model); -+ m -+ }) -+ .collect(); -+ } -+ fn spawn_particles(&mut self, particles: Vec) { -+ let particles: Vec = particles -+ .into_iter() -+ .filter(|p| p.x.is_finite() && p.y.is_finite() && p.z.is_finite()) -+ .take(PARTICLE_SPAWNS) -+ .map(|mut p| { -+ p.count = p.count.clamp(1, PARTICLES_PER_SPAWN); -+ p.particle = clip(p.particle); -+ p -+ }) -+ .collect(); -+ if !particles.is_empty() { -+ self.out.push(Output::Particles { particles }); -+ } -+ } -+ fn query_ground(&mut self, columns: Vec<(i32, i32)>) { -+ let room = 512usize.saturating_sub(self.ground_queries.len()); -+ self.ground_queries.extend(columns.into_iter().take(room)); -+ } -+ fn ground(&mut self) -> Vec<(i32, i32, i32)> { -+ self.ground.clone() -+ } -+ fn play_sound(&mut self, event: String, x: f64, y: f64, z: f64, volume: f32, pitch: f32) { -+ if !(x.is_finite() && y.is_finite() && z.is_finite()) { -+ return; -+ } -+ let ok = |v: f32, max: f32| if v.is_finite() { v.clamp(0.0, max) } else { 1.0 }; -+ let sounds = self.out.iter().filter(|o| matches!(o, Output::Sound { .. })).count(); -+ if sounds < 64 { -+ self.out.push(Output::Sound { -+ event: clip(event), -+ at: [x, y, z], -+ volume: ok(volume, 16.0), -+ pitch: ok(pitch, 2.0), -+ }); -+ } -+ } - } - - impl command::Host for State { --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch b/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch deleted file mode 100644 index c6ab2ca..0000000 --- a/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch +++ /dev/null @@ -1,28 +0,0 @@ -From a6d64e6415bf618539af464e521091e72b72d72f Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 12:39:45 +0300 -Subject: [PATCH 02/42] cargo-horizon-mod: report particles and sounds - -Co-Authored-By: Claude Opus 5.5 ---- - crates/cargo-horizon-mod/src/check.rs | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/crates/cargo-horizon-mod/src/check.rs b/crates/cargo-horizon-mod/src/check.rs -index 988ba826d..6a91fda28 100644 ---- a/crates/cargo-horizon-mod/src/check.rs -+++ b/crates/cargo-horizon-mod/src/check.rs -@@ -280,6 +280,10 @@ fn play(path: &Path, report: &mut Report) -> Result<(), String> { - Output::Shake { strength, seconds } => { - format!("shook the camera: {strength:.2} for {seconds:.1} s") - } -+ Output::Particles { particles } => { -+ format!("spawned {} particle group(s)", particles.len()) -+ } -+ Output::Sound { event, .. } => format!("played {event}"), - }); - } - let shapes = mods.hud_shapes().count(); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch b/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch deleted file mode 100644 index 92f1e6d..0000000 --- a/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch +++ /dev/null @@ -1,603 +0,0 @@ -From ee79b3562feebe51959a01f1585a9e85bd53ccb5 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 13:33:10 +0300 -Subject: [PATCH 03/42] Mod API (AirDef fork): full-screen post pass for mods - -render.set-post compiles a mod's WGSL after a prelude (camera matrices, -time, 256 vec4 of params, the world's colour and depth) and runs it after -the world, before the hand and the HUD; render.set-params feeds it. - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game.rs | 1 + - crates/horizon-client/src/game/mod_models.rs | 28 ++ - crates/horizon-client/src/gfx.rs | 35 +- - crates/horizon-client/src/gfx/mod_post.rs | 320 +++++++++++++++++++ - crates/horizon-mod-sdk/wit/horizon.wit | 13 + - crates/horizon-mods/src/api_level.rs | 5 +- - crates/horizon-mods/src/runtime.rs | 28 ++ - 7 files changed, 428 insertions(+), 2 deletions(-) - create mode 100644 crates/horizon-client/src/gfx/mod_post.rs - -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index 7c342cd86..ca6a8c266 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -11527,6 +11527,7 @@ impl Game<'_> { - return Ok(false); - }; - let alpha = self.clock.fraction(); -+ self.apply_mod_post(alpha as f32); - // Camera.getCameraEntityPartialTicks: the view, the hand and the - // player's own model stay smooth while the level is frozen. - let camera_alpha = self.clock.camera_fraction(); -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index 3c7f9345c..be00ea4c7 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -36,6 +36,8 @@ pub(super) struct ModModels { - poses: HashMap<(usize, u32), Pose>, - /// Block states by the text mods name them, parsed once. - states: HashMap>, -+ /// The version of the mod post pass the renderer has (0: none). -+ post: u64, - } - - /// Full brightness: sky and block light 15, as the entity shader packs them. -@@ -52,6 +54,7 @@ impl Game<'_> { - _out: &mut Vec, - ) { - } -+ pub(super) fn apply_mod_post(&mut self, _partial: f32) {} - } - - #[cfg(feature = "mods")] -@@ -148,6 +151,31 @@ impl Game<'_> { - } - } - -+ /// Before each frame: the renderer gets the mod's post pass when it changed, and its -+ /// parameters. A pass that doesn't compile is reported once; the last good one runs on. -+ pub(super) fn apply_mod_post(&mut self, partial: f32) { -+ let (version, source, params) = match self.mods.mods.post() { -+ Some((v, s, p)) => (v, Some(s.to_owned()), p.to_vec()), -+ None => (0, None, Vec::new()), -+ }; -+ let mut error = None; -+ let Some(renderer) = self.renderer.as_mut() else { return }; -+ if version != self.mods.models.post { -+ self.mods.models.post = version; -+ error = renderer.set_mod_post(source.as_deref()).err(); -+ } -+ if version != 0 { -+ renderer.set_mod_params(¶ms, partial); -+ } -+ if let Some(e) = error { -+ log::warn!("Horizon: mod post pass refused: {e}"); -+ self.system_message( -+ horizon_client::text::Text::literal(format!("Mod shader error: {e}")) -+ .with_color(horizon_client::text::RED), -+ ); -+ } -+ } -+ - /// `render.spawn-particles`: made on this client at once. - pub(super) fn mod_particles(&mut self, particles: Vec) { - for s in particles { -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index beb8b9060..4c5005615 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -77,6 +77,7 @@ mod lod; - mod native_material; - mod native_pack; - mod native_post; -+mod mod_post; - mod native_sky; - mod occlusion; - mod pack_atlas; -@@ -543,6 +544,9 @@ pub struct Renderer { - pack_post: Option<(i32, Option)>, - /// The post passes of one of Horizon's own shader packs; never beside `pack_post`'s. - native: Option, -+ /// AirDef fork: a mod's post pass, its parameters and the partial tick they are for. -+ mod_post: Option, -+ mod_params: (Vec, f32), - /// Time the shader pack's passes on the GPU (F3, or `HORIZON_SHADER_PACK_TIMING`). - pass_timing: bool, - /// The entity vertices Horizon draws itself while a shader pack draws the others. -@@ -2646,6 +2650,8 @@ impl Renderer { - hdr_output, - pack_post: None, - native: None, -+ mod_post: None, -+ mod_params: (Vec::new(), 0.0), - pass_timing: pack_timing::PassTiming::logging(), - pack_entity_rest: Vec::new(), - cloud_bindings, -@@ -3287,6 +3293,24 @@ impl Renderer { - report - } - /// Drops the running shader pack, of either kind. -+ /// AirDef fork: runs a mod's post pass from now on (`None`: none). On an error the pass -+ /// that ran keeps running. -+ pub fn set_mod_post(&mut self, source: Option<&str>) -> std::result::Result<(), String> { -+ match source { -+ None => self.mod_post = None, -+ Some(source) => { -+ let format = self.config.format.remove_srgb_suffix(); -+ self.mod_post = Some(mod_post::ModPost::new(&self.device, source, format)?); -+ } -+ } -+ Ok(()) -+ } -+ /// AirDef fork: the mod post pass's parameters for the next frames. -+ pub fn set_mod_params(&mut self, params: &[f32], partial: f32) { -+ self.mod_params.0.clear(); -+ self.mod_params.0.extend_from_slice(¶ms[..params.len().min(mod_post::PARAMS)]); -+ self.mod_params.1 = partial; -+ } - pub fn drop_shader_packs(&mut self) { - self.pack_post = None; - self.native = None; -@@ -4456,7 +4480,9 @@ impl Renderer { - .then(|| pack_timing::PassTiming::new(&self.device, &self.queue)) - .flatten(); - } -- let pack_reads_world = pack_post || native_post; -+ // AirDef fork: a mod's post pass reads the world target too. -+ let mod_post = !frame.title && !pack_post && !native_post && self.mod_post.is_some(); -+ let pack_reads_world = pack_post || native_post || mod_post; - // The pack's program reads the vertices this frame's draws bind. - let pack_draws_terrain = pack_terrain && flat_frame == self.pack_terrain_flat; - // The shadow camera's lists, drawn by the pack's shadow programs. -@@ -5026,6 +5052,13 @@ impl Renderer { - size, - native.timing.as_mut(), - ); -+ } else if let Some((color, depth)) = &scaled -+ && mod_post -+ && let Some(post) = &mut self.mod_post -+ { -+ let size = self.world_post.size().unwrap_or([1, 1]); -+ post.write(&self.queue, frame, size, &self.mod_params.0, self.mod_params.1); -+ post.run(&self.device, &mut encoder, (color, depth), &view_target); - } else if scaled.is_some() { - self.world_post.finish(&mut encoder, &view_target); - } -diff --git a/crates/horizon-client/src/gfx/mod_post.rs b/crates/horizon-client/src/gfx/mod_post.rs -new file mode 100644 -index 000000000..497cff854 ---- /dev/null -+++ b/crates/horizon-client/src/gfx/mod_post.rs -@@ -0,0 +1,320 @@ -+//! AirDef fork: a mod's full-screen post pass (`render.set-post`). The mod's WGSL is put after -+//! a prelude that declares the frame (camera matrices, time, the mod's parameters) and the -+//! world's colour and depth; it defines `fn post(uv: vec2, pixel: vec2) -> vec4`. -+//! The pass runs after the world and before the hand and the HUD, reading the scaled world -+//! target and drawing onto the screen. -+ -+use glam::Mat4; -+ -+use super::Frame; -+ -+/// Floats of mod parameters (256 vec4). -+pub const PARAMS: usize = 1024; -+ -+/// The uniform the prelude's `frame` reads. -+#[repr(C)] -+#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -+struct ModFrame { -+ view_proj: [[f32; 4]; 4], -+ view_inverse: [[f32; 4]; 4], -+ projection_inverse: [[f32; 4]; 4], -+ /// xyz: the camera; w: unused. -+ camera: [f32; 4], -+ /// width, height, 1/width, 1/height. -+ resolution: [f32; 4], -+ /// seconds (wraps hourly), the partial tick, the sun's height, unused. -+ time: [f32; 4], -+ /// The sun's direction. -+ sun: [f32; 4], -+ params: [[f32; 4]; PARAMS / 4], -+} -+ -+pub const PRELUDE: &str = r#" -+struct ModFrame { -+ view_proj: mat4x4, -+ view_inverse: mat4x4, -+ projection_inverse: mat4x4, -+ camera: vec4, -+ resolution: vec4, -+ time: vec4, -+ sun: vec4, -+ params: array, 256>, -+} -+@group(0) @binding(0) var frame: ModFrame; -+@group(0) @binding(1) var scene_color: texture_2d; -+@group(0) @binding(2) var scene_depth: texture_depth_2d; -+@group(0) @binding(3) var linear_sampler: sampler; -+ -+struct HorizonModPostOut { -+ @builtin(position) position: vec4, -+ @location(0) uv: vec2, -+} -+ -+@vertex -+fn horizon_mod_post_vs(@builtin(vertex_index) i: u32) -> HorizonModPostOut { -+ let uv = vec2(f32((i << 1u) & 2u), f32(i & 2u)); -+ var out: HorizonModPostOut; -+ out.position = vec4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0); -+ out.uv = uv; -+ return out; -+} -+ -+/// The world's depth at `uv` (reversed: 1 near, 0 the sky). -+fn depth_at(uv: vec2) -> f32 { -+ let size = vec2(textureDimensions(scene_depth)); -+ let p = vec2(clamp(uv * size, vec2(0.0), size - 1.0)); -+ return textureLoad(scene_depth, p, 0); -+} -+ -+/// The point the pixel at `uv` sees at `depth`, relative to the camera (world axes). -+fn view_ray_point(uv: vec2, depth: f32) -> vec3 { -+ let ndc = vec4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); -+ let v = frame.projection_inverse * ndc; -+ return (frame.view_inverse * vec4(v.xyz / v.w, 0.0)).xyz; -+} -+ -+/// The unit direction of the view ray through `uv`. -+fn view_dir(uv: vec2) -> vec3 { -+ return normalize(view_ray_point(uv, 0.5)); -+} -+ -+/// How far along the view ray the world is at `uv` (a large number for the sky). -+fn scene_distance(uv: vec2) -> f32 { -+ let d = depth_at(uv); -+ if d <= 0.0 { return 1.0e6; } -+ return length(view_ray_point(uv, d)); -+} -+ -+/// A world point on the screen: xy in uv, z its distance (negative behind the camera). -+fn project(world: vec3) -> vec3 { -+ let c = frame.view_proj * vec4(world - frame.camera.xyz, 1.0); -+ let ndc = c.xyz / max(abs(c.w), 1e-5); -+ return vec3(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5, c.w); -+} -+ -+fn scene(uv: vec2) -> vec3 { -+ return textureSampleLevel(scene_color, linear_sampler, uv, 0.0).rgb; -+} -+"#; -+ -+const ENTRY: &str = r#" -+@fragment -+fn horizon_mod_post_fs(in: HorizonModPostOut) -> @location(0) vec4 { -+ return post(in.uv, in.position.xy); -+} -+"#; -+ -+pub struct ModPost { -+ pipeline: wgpu::RenderPipeline, -+ layout: wgpu::BindGroupLayout, -+ buffer: wgpu::Buffer, -+ sampler: wgpu::Sampler, -+ group: Option<((wgpu::TextureView, wgpu::TextureView), wgpu::BindGroup)>, -+ start: std::time::Instant, -+} -+ -+/// The mod's source with the prelude, checked by naga: errors name the mod's own lines. -+fn compile(source: &str) -> Result { -+ let full = format!("{PRELUDE}\n{source}\n{ENTRY}"); -+ let module = naga::front::wgsl::parse_str(&full).map_err(|e| { -+ let prelude_lines = PRELUDE.lines().count() + 1; -+ let text = e.emit_to_string(&full); -+ match e.location(&full) { -+ Some(l) if l.line_number as usize > prelude_lines => { -+ format!("line {}: {}", l.line_number as usize - prelude_lines, e.message()) -+ } -+ _ => text, -+ } -+ })?; -+ naga::valid::Validator::new( -+ naga::valid::ValidationFlags::all(), -+ naga::valid::Capabilities::default(), -+ ) -+ .validate(&module) -+ .map_err(|e| format!("{}", e.into_inner()))?; -+ Ok(module) -+} -+ -+impl ModPost { -+ pub fn new(device: &wgpu::Device, source: &str, format: wgpu::TextureFormat) -> Result { -+ let module = compile(source)?; -+ let texture = |binding, sample_type| wgpu::BindGroupLayoutEntry { -+ binding, -+ visibility: wgpu::ShaderStages::FRAGMENT, -+ ty: wgpu::BindingType::Texture { -+ sample_type, -+ view_dimension: wgpu::TextureViewDimension::D2, -+ multisampled: false, -+ }, -+ count: None, -+ }; -+ let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("Mod post"), -+ entries: &[ -+ wgpu::BindGroupLayoutEntry { -+ binding: 0, -+ visibility: wgpu::ShaderStages::FRAGMENT, -+ ty: wgpu::BindingType::Buffer { -+ ty: wgpu::BufferBindingType::Uniform, -+ has_dynamic_offset: false, -+ min_binding_size: wgpu::BufferSize::new(size_of::() as u64), -+ }, -+ count: None, -+ }, -+ texture(1, wgpu::TextureSampleType::Float { filterable: true }), -+ texture(2, wgpu::TextureSampleType::Depth), -+ wgpu::BindGroupLayoutEntry { -+ binding: 3, -+ visibility: wgpu::ShaderStages::FRAGMENT, -+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), -+ count: None, -+ }, -+ ], -+ }); -+ let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { -+ label: Some("Mod post"), -+ bind_group_layouts: &[Some(&layout)], -+ ..Default::default() -+ }); -+ let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); -+ let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { -+ label: Some("Mod post"), -+ source: wgpu::ShaderSource::Naga(std::borrow::Cow::Owned(module)), -+ }); -+ let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("Mod post"), -+ layout: Some(&pipeline_layout), -+ vertex: wgpu::VertexState { -+ module: &shader, -+ entry_point: Some("horizon_mod_post_vs"), -+ compilation_options: Default::default(), -+ buffers: &[], -+ }, -+ primitive: Default::default(), -+ depth_stencil: None, -+ multisample: Default::default(), -+ fragment: Some(wgpu::FragmentState { -+ module: &shader, -+ entry_point: Some("horizon_mod_post_fs"), -+ compilation_options: Default::default(), -+ targets: &[Some(wgpu::ColorTargetState { -+ format, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ })], -+ }), -+ multiview_mask: None, -+ cache: None, -+ }); -+ if let Some(e) = pollster::block_on(scope.pop()) { -+ return Err(e.to_string()); -+ } -+ let buffer = device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some("Mod post frame"), -+ size: size_of::() as u64, -+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }); -+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor { -+ label: Some("Mod post linear"), -+ mag_filter: wgpu::FilterMode::Linear, -+ min_filter: wgpu::FilterMode::Linear, -+ ..Default::default() -+ }); -+ Ok(Self { -+ pipeline, -+ layout, -+ buffer, -+ sampler, -+ group: None, -+ start: std::time::Instant::now(), -+ }) -+ } -+ -+ /// This frame's camera and the mod's `params`, for a world of `size`. -+ pub fn write(&self, queue: &wgpu::Queue, frame: &Frame, size: [u32; 2], params: &[f32], partial: f32) { -+ let aspect = size[0] as f32 / size[1].max(1) as f32; -+ let view = crate::camera::view_matrix(frame.yaw, frame.pitch); -+ let projection = -+ super::world_projection(frame.fov_degrees, aspect, frame.depth_far) * frame.bob * frame.spin; -+ let a = frame.sky.sun_angle.to_radians(); -+ let sun = [-a.sin(), a.cos(), 0.0, 0.0]; -+ let mut p = [[0.0f32; 4]; PARAMS / 4]; -+ for (i, v) in params.iter().take(PARAMS).enumerate() { -+ p[i / 4][i % 4] = *v; -+ } -+ let m = |m: Mat4| m.to_cols_array_2d(); -+ let inputs = ModFrame { -+ view_proj: m(projection * view), -+ view_inverse: m(view.inverse()), -+ projection_inverse: m(projection.inverse()), -+ camera: [frame.camera.x as f32, frame.camera.y as f32, frame.camera.z as f32, 0.0], -+ resolution: [ -+ size[0] as f32, -+ size[1] as f32, -+ 1.0 / size[0].max(1) as f32, -+ 1.0 / size[1].max(1) as f32, -+ ], -+ time: [self.start.elapsed().as_secs_f32() % 3600.0, partial, sun[1], 0.0], -+ sun, -+ params: p, -+ }; -+ queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(&inputs)); -+ } -+ -+ /// Draws the pass over the world target `world` (colour, depth) onto `screen`. -+ pub fn run( -+ &mut self, -+ device: &wgpu::Device, -+ encoder: &mut wgpu::CommandEncoder, -+ world: (&wgpu::TextureView, &wgpu::TextureView), -+ screen: &wgpu::TextureView, -+ ) { -+ if self -+ .group -+ .as_ref() -+ .is_none_or(|((c, d), _)| c != world.0 || d != world.1) -+ { -+ let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("Mod post"), -+ layout: &self.layout, -+ entries: &[ -+ wgpu::BindGroupEntry { binding: 0, resource: self.buffer.as_entire_binding() }, -+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(world.0) }, -+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(world.1) }, -+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self.sampler) }, -+ ], -+ }); -+ self.group = Some(((world.0.clone(), world.1.clone()), group)); -+ } -+ let Some((_, group)) = &self.group else { return }; -+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("Mod post"), -+ color_attachments: &[Some(wgpu::RenderPassColorAttachment { -+ view: screen, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { -+ load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), -+ store: wgpu::StoreOp::Store, -+ }, -+ })], -+ ..Default::default() -+ }); -+ pass.set_pipeline(&self.pipeline); -+ pass.set_bind_group(0, group, &[]); -+ pass.draw(0..3, 0..1); -+ } -+} -+ -+#[cfg(test)] -+mod tests { -+ #[test] -+ fn a_shader_compiles_and_errors_name_its_line() { -+ let ok = "fn post(uv: vec2, pixel: vec2) -> vec4 { return vec4(scene(uv), 1.0); }"; -+ assert!(super::compile(ok).is_ok()); -+ let bad = "fn post(uv: vec2, pixel: vec2) -> vec4 {\n return nope;\n}"; -+ let e = super::compile(bad).unwrap_err(); -+ assert!(e.starts_with("line 2"), "{e}"); -+ } -+} -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index 89d1906a9..1b167c7f7 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -487,6 +487,19 @@ interface render { - /// the highest block that blocks motion or holds a fluid, as the motion - /// blocking heightmap; -64 where the chunk isn't loaded. - ground: func() -> list>; -+ /// A full-screen post pass (AirDef fork): WGSL defining -+ /// `fn post(uv: vec2, pixel: vec2) -> vec4`, after a prelude -+ /// with `frame` (camera matrices, time, `params`), the world's colour and -+ /// depth and helpers (`scene`, `depth-at`, `view-dir`, `scene-distance`, -+ /// `project`). It runs after the world, before the hand and the HUD. -+ /// Compile errors come back in the chat and the log; the last good pass -+ /// keeps running. -+ set-post: func(wgsl: string); -+ clear-post: func(); -+ /// The pass's `frame.params` (up to 1024 floats, as 256 vec4), kept until -+ /// replaced; call it once a tick (the pass gets the partial tick in -+ /// `frame.time.y`). -+ set-params: func(params: list); - } - - /// What the game calls. Only the hooks `init` asks for are ever called. -diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs -index b64792eaa..68487e600 100644 ---- a/crates/horizon-mods/src/api_level.rs -+++ b/crates/horizon-mods/src/api_level.rs -@@ -13,7 +13,7 @@ pub const LEVEL: u32 = 8; - /// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new - /// function takes the next level and raises [`LEVEL`] (the `api_reference` - /// test fails until it is listed here). --pub const FUNCTIONS: [(&str, u32); 51] = [ -+pub const FUNCTIONS: [(&str, u32); 54] = [ - ("log.write", 0), - ("game.get-player", 0), - ("game.get-clock", 0), -@@ -66,6 +66,9 @@ pub const FUNCTIONS: [(&str, u32); 51] = [ - ("render.play-sound", 8), - ("render.query-ground", 8), - ("render.ground", 8), -+ ("render.set-post", 8), -+ ("render.clear-post", 8), -+ ("render.set-params", 8), - ]; - - /// What the game's program carries so the launcher can read its level -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index 317cd4f06..55c1fd13f 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -432,6 +432,9 @@ struct State { - ground_queries: Vec<(i32, i32)>, - /// The game's answers to them. - ground: Vec<(i32, i32, i32)>, -+ /// `render.set-post`'s source, with a number that changes with it. -+ post: Option<(u64, String)>, -+ post_params: Vec, - input: InputState, - browser: BrowserState, - /// Capability `storage`'s folder; made on first `set`. -@@ -638,6 +641,8 @@ impl Mods { - models: Vec::new(), - ground_queries: Vec::new(), - ground: Vec::new(), -+ post: None, -+ post_params: Vec::new(), - input: InputState::default(), - browser: BrowserState::default(), - data_dir, -@@ -1212,6 +1217,14 @@ impl Mods { - }) - .collect() - } -+ /// The first running mod's post pass (version, WGSL) and its parameters. -+ pub fn post(&self) -> Option<(u64, &str, &[f32])> { -+ self.entries.iter().find_map(|e| { -+ let d = e.run.as_ref()?.store.data(); -+ let (v, src) = d.post.as_ref()?; -+ Some((*v, src.as_str(), d.post_params.as_slice())) -+ }) -+ } - /// Mod `i`'s answers to its ground queries, for its next calls. - pub fn answer_ground(&mut self, i: usize, ground: Vec<(i32, i32, i32)>) { - if let Some(run) = self.entries.get_mut(i).and_then(|e| e.run.as_mut()) { -@@ -1941,6 +1954,21 @@ impl render::Host for State { - self.out.push(Output::Particles { particles }); - } - } -+ fn set_post(&mut self, wgsl: String) { -+ static VERSION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); -+ let v = VERSION.fetch_add(1, std::sync::atomic::Ordering::Relaxed); -+ self.post = Some((v, clip_to(wgsl, 256 * 1024))); -+ } -+ fn clear_post(&mut self) { -+ self.post = None; -+ } -+ fn set_params(&mut self, params: Vec) { -+ self.post_params = params -+ .into_iter() -+ .take(1024) -+ .map(|v| if v.is_finite() { v } else { 0.0 }) -+ .collect(); -+ } - fn query_ground(&mut self, columns: Vec<(i32, i32)>) { - let room = 512usize.saturating_sub(self.ground_queries.len()); - self.ground_queries.extend(columns.into_iter().take(room)); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch b/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch deleted file mode 100644 index 8977476..0000000 --- a/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 2b137dbbedd540daeebc7f5e387847aeb56a1355 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 22:09:30 +0300 -Subject: [PATCH 04/42] Mod models: draw far models as scaled impostors past - the far plane, cull at 4096 - ---- - crates/horizon-client/src/game/mod_models.rs | 20 ++++++++++++++++---- - 1 file changed, 16 insertions(+), 4 deletions(-) - -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index be00ea4c7..6cd55afc6 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -120,14 +120,26 @@ impl Game<'_> { - return; - } - let t = alpha.clamp(0.0, 1.0); -+ // Missiles are seen from far off: past the far plane a model is drawn nearer and -+ // smaller, the same size on screen. -+ let depth_far = f64::from(horizon_client::camera::depth_far( -+ self.terrain_distance(), -+ self.settings.cloud_range, -+ )); -+ let reach = depth_far * 0.85; - for p in self.mods.models.poses.values() { -- let at = p.previous.lerp(p.position, t); -- // Far models are culled by distance; the view frustum does the rest. -- if at.distance_squared(camera) > 512.0 * 512.0 { -+ let mut at = p.previous.lerp(p.position, t); -+ let d = at.distance(camera); -+ if d > 4096.0 { - continue; - } -+ let mut shrink = 1.0f32; -+ if d > reach { -+ shrink = (reach / d) as f32; -+ at = camera + (at - camera) * (reach / d); -+ } - let rotation = p.old_rotation.slerp(p.rotation, t as f32); -- let scale = p.old_scale + (p.scale - p.old_scale) * t as f32; -+ let scale = (p.old_scale + (p.scale - p.old_scale) * t as f32) * shrink; - if scale <= 0.0 { - continue; - } --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch b/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch deleted file mode 100644 index 4d9d621..0000000 --- a/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch +++ /dev/null @@ -1,927 +0,0 @@ -From c8ebca6e0af68630e65211844b34d25c5b985c03 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 22:31:00 +0300 -Subject: [PATCH 05/42] Mod cameras: render.set-views draws the world from - other points into pictures that HUD view shapes show (API level 9) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game.rs | 3 + - crates/horizon-client/src/game/mod_models.rs | 39 ++- - crates/horizon-client/src/game/mod_render.rs | 9 + - crates/horizon-client/src/gfx.rs | 192 ++++++++++- - crates/horizon-client/src/gfx/mod_views.rs | 318 +++++++++++++++++++ - crates/horizon-client/src/hud.rs | 15 + - crates/horizon-mod-sdk/wit/horizon.wit | 30 ++ - crates/horizon-mods/src/api_level.rs | 5 +- - crates/horizon-mods/src/runtime.rs | 30 +- - 9 files changed, 624 insertions(+), 17 deletions(-) - create mode 100644 crates/horizon-client/src/gfx/mod_views.rs - -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index ca6a8c266..e5ffb30c4 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -11806,7 +11806,9 @@ impl Game<'_> { - hud.patches.append(&mut self.map_patches); - let entity_spans = std::mem::take(self.entity_spans.get_mut()); - let glowing = std::mem::take(self.glowing_vertices.get_mut()); -+ let mod_views = self.mod_views(alpha); - let frame = Frame { -+ mod_views: &mod_views, - weather: &weather, - weather_time: horizon_client::weather::Time::new(self.world.game_time, alpha as f32), - nether_lighting: self.dim == Dim::Nether, -@@ -14725,6 +14727,7 @@ fn loading_renderer( - /// A title-shaped frame: black clear and panorama, only the HUD is drawn. - fn loading_frame(hud: &HudBatch) -> Frame<'_> { - Frame { -+ mod_views: &[], - weather: &[], - weather_time: horizon_client::weather::Time::new(0, 0.0), - camera: glam::DVec3::new(0.5, 100.0, 0.5), -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index 6cd55afc6..7ae58f664 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -11,7 +11,7 @@ use { - particles::{ParticleOptions, ParticleType}, - }, - horizon_core::random::RandomSource, -- horizon_mods::ParticleSpawn, -+ horizon_mods::{CameraView, ParticleSpawn}, - horizon_world::registry::BlockStateId, - std::collections::HashMap, - }; -@@ -38,6 +38,8 @@ pub(super) struct ModModels { - states: HashMap>, - /// The version of the mod post pass the renderer has (0: none). - post: u64, -+ /// The mods' cameras by id: a tick ago and now. -+ views: Vec<(CameraView, CameraView)>, - } - - /// Full brightness: sky and block light 15, as the entity shader packs them. -@@ -55,6 +57,9 @@ impl Game<'_> { - ) { - } - pub(super) fn apply_mod_post(&mut self, _partial: f32) {} -+ pub(super) fn mod_views(&self, _alpha: f64) -> Vec { -+ Vec::new() -+ } - } - - #[cfg(feature = "mods")] -@@ -111,6 +116,38 @@ impl Game<'_> { - let seen: std::collections::HashSet<_> = seen.into_iter().collect(); - models.poses.retain(|k, _| seen.contains(k)); - } -+ let views: Vec = self.mods.mods.views().map(|(_, v)| *v).collect(); -+ let old = std::mem::take(&mut self.mods.models.views); -+ self.mods.models.views = views -+ .into_iter() -+ .map(|v| { -+ let before = old.iter().find(|(_, o)| o.id == v.id).map_or(v, |(_, o)| *o); -+ (before, v) -+ }) -+ .collect(); -+ } -+ -+ /// The mods' cameras between their last two settings, for this frame. -+ pub(super) fn mod_views(&self, alpha: f64) -> Vec { -+ let t = alpha.clamp(0.0, 1.0); -+ let lerp = |a: f32, b: f32| a + (b - a) * t as f32; -+ self.mods -+ .models -+ .views -+ .iter() -+ .map(|(a, b)| { -+ // The short way round. -+ let turn = (b.yaw - a.yaw + 540.0).rem_euclid(360.0) - 180.0; -+ horizon_client::gfx::ModView { -+ id: b.id, -+ camera: DVec3::new(a.x, a.y, a.z).lerp(DVec3::new(b.x, b.y, b.z), t), -+ yaw: a.yaw + turn * t as f32, -+ pitch: lerp(a.pitch, b.pitch), -+ fov: lerp(a.fov, b.fov), -+ size: [b.width, b.height], -+ } -+ }) -+ .collect() - } - - /// Draws every mod model between its last two poses, lit by the world -diff --git a/crates/horizon-client/src/game/mod_render.rs b/crates/horizon-client/src/game/mod_render.rs -index 931aec641..85aae67e7 100644 ---- a/crates/horizon-client/src/game/mod_render.rs -+++ b/crates/horizon-client/src/game/mod_render.rs -@@ -120,6 +120,15 @@ impl Game<'_> { - self.draw_item_model(batch, &stack, x, y); - batch.scale_since(start, x, y, i.scale, i.scale); - } -+ Shape::View(v) => { -+ let r = v.rect; -+ batch.views.push(horizon_client::hud::HudView { -+ at: batch.vertices.len() as u32, -+ id: v.view, -+ rect: [ox + r.x, oy + r.y, r.w, r.h], -+ mode: v.mode, -+ }); -+ } - Shape::Text(t) => { - let text = Text::literal(t.text.clone()); - let centre = if t.centered { -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index 4c5005615..3ad257728 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -78,6 +78,8 @@ mod native_material; - mod native_pack; - mod native_post; - mod mod_post; -+mod mod_views; -+pub use mod_views::ModView; - mod native_sky; - mod occlusion; - mod pack_atlas; -@@ -214,6 +216,8 @@ const SECTION_UPLOAD_CHUNK: u64 = 4 << 20; - const SECTION_COPY_LIMIT: u64 = 64 << 20; - pub struct Frame<'a> { - pub camera: DVec3, -+ /// Mods' cameras (AirDef fork), drawn into the pictures the HUD shows. -+ pub mod_views: &'a [ModView], - pub yaw: f32, - pub pitch: f32, - pub fov_degrees: f32, -@@ -546,6 +550,7 @@ pub struct Renderer { - native: Option, - /// AirDef fork: a mod's post pass, its parameters and the partial tick they are for. - mod_post: Option, -+ mod_views: Option, - mod_params: (Vec, f32), - /// Time the shader pack's passes on the GPU (F3, or `HORIZON_SHADER_PACK_TIMING`). - pass_timing: bool, -@@ -2651,6 +2656,7 @@ impl Renderer { - pack_post: None, - native: None, - mod_post: None, -+ mod_views: None, - mod_params: (Vec::new(), 0.0), - pass_timing: pack_timing::PassTiming::logging(), - pack_entity_rest: Vec::new(), -@@ -3019,6 +3025,128 @@ impl Renderer { - self.section_uploads.finish(&encoder); - Some(encoder.finish()) - } -+ /// The HUD's vertices in `range`, with the mod cameras' pictures between -+ /// them where their shapes were. -+ fn draw_hud( -+ &self, -+ encoder: &mut wgpu::CommandEncoder, -+ target: &wgpu::TextureView, -+ batch: &crate::hud::HudBatch, -+ range: std::ops::Range, -+ ) { -+ let size = [self.config.width, self.config.height]; -+ let last = batch.vertices.len() as u32; -+ let mut at = range.start; -+ if let Some(views) = &self.mod_views { -+ for v in batch.views.iter().filter(|v| { -+ v.at >= range.start && (v.at < range.end || (v.at == range.end && range.end == last)) -+ }) { -+ self.hud.draw_range(encoder, target, &self.depth, batch, size, at..v.at); -+ views.blit(encoder, target, size, v, batch.scale); -+ at = v.at; -+ } -+ } -+ self.hud.draw_range(encoder, target, &self.depth, batch, size, at..range.end); -+ } -+ /// The mod cameras due this frame, each into its picture: sky, solid -+ /// terrain and entities, through the world's own pipelines with the -+ /// camera's globals in the world's buffer (the player's put back after). -+ fn draw_mod_views( -+ &mut self, -+ encoder: &mut wgpu::CommandEncoder, -+ frame: &Frame, -+ globals: &Globals, -+ due: &[mod_views::Due], -+ entity_quads: u32, -+ ) { -+ let Some(views) = &mut self.mod_views else { -+ return; -+ }; -+ let size = std::mem::size_of::() as u64; -+ self.uploads -+ .buffer(encoder, &views.main, 0, bytemuck::bytes_of(globals)); -+ for d in due { -+ let Some(t) = views.targets.get(&d.id) else { -+ continue; -+ }; -+ let own = Globals { -+ view_proj: d.view_proj.to_cols_array_2d(), -+ inverse_view: d.view.inverse().to_cols_array_2d(), -+ misc: [ -+ globals.misc[0], -+ globals.misc[1], -+ d.aspect, -+ (d.fov.to_radians() / 2.0).tan(), -+ ], -+ ..*globals -+ }; -+ self.uploads -+ .buffer(encoder, &t.globals, 0, bytemuck::bytes_of(&own)); -+ } -+ let fog = globals.fog_color; -+ let clear = wgpu::Color { -+ r: f64::from(fog[0]), -+ g: f64::from(fog[1]), -+ b: f64::from(fog[2]), -+ a: 1.0, -+ }; -+ for d in due { -+ let Some(t) = views.targets.get_mut(&d.id) else { -+ continue; -+ }; -+ t.drawn = true; -+ encoder.copy_buffer_to_buffer(&t.globals, 0, &self.globals, 0, size); -+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("Mod view"), -+ color_attachments: &[Some(wgpu::RenderPassColorAttachment { -+ view: &t.color, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { -+ load: wgpu::LoadOp::Clear(clear), -+ store: wgpu::StoreOp::Store, -+ }, -+ })], -+ depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { -+ view: &t.depth, -+ depth_ops: Some(wgpu::Operations { -+ load: wgpu::LoadOp::Clear(0.0), -+ store: wgpu::StoreOp::Discard, -+ }), -+ stencil_ops: None, -+ }), -+ ..Default::default() -+ }); -+ pass.set_bind_group(0, &self.group, &[]); -+ if frame.skybox { -+ pass.set_pipeline(&self.sky); -+ pass.draw(0..3, 0..1); -+ } -+ pass.set_pipeline(&self.solid); -+ pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32); -+ pass.set_vertex_buffer(1, self.instances.slice(..)); -+ let mut page = None; -+ for (k, v) in d.visible.iter().enumerate() { -+ let s = self.sections.get(v.slot); -+ if s.solid == 0 { -+ continue; -+ } -+ if page != Some(s.page) { -+ pass.set_vertex_buffer(0, self.arena.buffer(s.page).slice(..)); -+ page = Some(s.page); -+ } -+ let instance = d.first + k as u32; -+ pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); -+ } -+ if entity_quads > 0 { -+ pass.set_pipeline(&self.entity_pipeline); -+ pass.set_bind_group(0, &self.entity_group, &[]); -+ pass.set_vertex_buffer(0, self.entity_buffer.slice(..)); -+ pass.draw_indexed(0..entity_quads * 6, 0, 0..1); -+ } -+ } -+ encoder.copy_buffer_to_buffer(&views.main, 0, &self.globals, 0, size); -+ } - /// Draws `count` section draws from `first` with the arena and section - /// origins bound: one indirect multi-draw, or a loop without it. - /// Consecutive draws in one arena page share a vertex buffer binding, -@@ -3827,7 +3955,46 @@ impl Renderer { - }); - } - self.drawn_sections = visible.len() + visible_lods.len(); -- let instances = self.drawn_sections + shadow_visible.len(); -+ // Mods' cameras: their sections round them, all of them (no occlusion), after the shadow's. -+ let globals_size = std::mem::size_of::() as u64; -+ let mut views_due = Vec::new(); -+ if !frame.title && !frame.mod_views.is_empty() { -+ let format = self.config.format.remove_srgb_suffix(); -+ let device = &self.device; -+ let views = self -+ .mod_views -+ .get_or_insert_with(|| mod_views::Views::new(device, format, globals_size)); -+ let mut first = (self.drawn_sections + shadow_visible.len()) as u32; -+ let radius = ((frame.detail_distance / 16.0) as i32).min(mod_views::RADIUS); -+ for v in views.choose(device, frame.mod_views, globals_size) { -+ let (view_proj, view, aspect) = mod_views::matrices(v, camera, frame.depth_far); -+ let own = world_projection(v.fov, aspect, frame.depth_far) * view; -+ let planes = Frustum { planes: frustum(own) }; -+ let mut seen = Vec::new(); -+ self.sections -+ .visible(v.camera, radius, &planes, |_| true, |_, _| false, &mut seen); -+ // Relative to the player's camera, as the rest of the frame. -+ let shift = (v.camera - camera).as_vec3(); -+ for s in &mut seen { -+ s.origin = [s.origin[0] + shift.x, s.origin[1] + shift.y, s.origin[2] + shift.z]; -+ } -+ let n = seen.len() as u32; -+ views_due.push(mod_views::Due { -+ id: v.id, -+ view_proj, -+ view, -+ aspect, -+ fov: v.fov, -+ visible: seen, -+ first, -+ }); -+ first += n; -+ } -+ } else if frame.mod_views.is_empty() { -+ self.mod_views = None; -+ } -+ let view_sections: usize = views_due.iter().map(|d| d.visible.len()).sum(); -+ let instances = self.drawn_sections + shadow_visible.len() + view_sections; - if instances as u64 > self.instance_capacity { - self.instance_capacity = (instances as u64).next_power_of_two(); - self.instances = self.device.create_buffer(&wgpu::BufferDescriptor { -@@ -3845,7 +4012,12 @@ impl Renderer { - .iter() - .map(|v| section_instance(v.origin)) - .chain(visible_lods.iter().map(|v| lod::instance(v.origin))) -- .chain(shadow_visible.iter().map(|v| section_instance(v.origin))), -+ .chain(shadow_visible.iter().map(|v| section_instance(v.origin))) -+ .chain( -+ views_due -+ .iter() -+ .flat_map(|d| d.visible.iter().map(|v| section_instance(v.origin))), -+ ), - ); - if !self.origins.is_empty() { - self.uploads.buffer( -@@ -4461,6 +4633,9 @@ impl Renderer { - bytemuck::cast_slice(&self.draws), - ); - } -+ if !views_due.is_empty() { -+ self.draw_mod_views(&mut encoder, frame, &globals, &views_due, entity_quads); -+ } - let view_target = target_texture.create_view(&wgpu::TextureViewDescriptor { - format: Some(self.config.format.remove_srgb_suffix()), - ..Default::default() -@@ -5167,14 +5342,7 @@ impl Renderer { - .menu_blur - .filter(|_| self.config.usage.contains(wgpu::TextureUsages::COPY_SRC)); - let blur_at = menu_blur.map_or(0, |_| frame.hud.blur_at); -- self.hud.draw_range( -- &mut encoder, -- &view_target, -- &self.depth, -- frame.hud, -- [self.config.width, self.config.height], -- 0..blur_at, -- ); -+ self.draw_hud(&mut encoder, &view_target, frame.hud, 0..blur_at); - if let Some(radius) = menu_blur { - let format = self.config.format; - let device = &self.device; -@@ -5190,12 +5358,10 @@ impl Renderer { - radius, - ); - } -- self.hud.draw_range( -+ self.draw_hud( - &mut encoder, - &view_target, -- &self.depth, - frame.hud, -- [self.config.width, self.config.height], - blur_at..frame.hud.vertices.len() as u32, - ); - if culling && let Some(cull) = &mut self.cull { -diff --git a/crates/horizon-client/src/gfx/mod_views.rs b/crates/horizon-client/src/gfx/mod_views.rs -new file mode 100644 -index 000000000..91ae103a8 ---- /dev/null -+++ b/crates/horizon-client/src/gfx/mod_views.rs -@@ -0,0 +1,318 @@ -+//! AirDef fork: mods' cameras (`render.set-views`). The world is drawn from -+//! another point into a small picture (sky, solid terrain, entities), which -+//! the HUD's `view` shapes show. The picture's passes reuse the world's -+//! pipelines and bind groups: its globals are copied into the world's -+//! buffer before them and the camera's copied back after. -+use glam::{DVec3, Mat4}; -+use std::collections::HashMap; -+ -+/// One camera of a mod, as the game interpolated it for this frame. -+#[derive(Clone, Copy, Debug)] -+pub struct ModView { -+ pub id: u32, -+ pub camera: DVec3, -+ /// Degrees, as the player's. -+ pub yaw: f32, -+ pub pitch: f32, -+ pub fov: f32, -+ pub size: [u32; 2], -+} -+ -+pub use crate::hud::HudView; -+ -+/// The most cameras drawn a frame; the others wait their turn. -+pub(super) const PER_FRAME: usize = 3; -+/// Sections round a camera it draws, in chunks. -+pub(super) const RADIUS: i32 = 12; -+ -+pub(super) struct Target { -+ size: [u32; 2], -+ pub color: wgpu::TextureView, -+ pub depth: wgpu::TextureView, -+ pub globals: wgpu::Buffer, -+ blit: wgpu::BindGroup, -+ /// Drawn at least once: the HUD shows nothing before. -+ pub drawn: bool, -+} -+ -+/// A camera drawn this frame. -+pub(super) struct Due { -+ pub id: u32, -+ /// Camera-relative to the player's camera, as the entities and the -+ /// world's other geometry are. -+ pub view_proj: Mat4, -+ pub view: Mat4, -+ pub aspect: f32, -+ pub fov: f32, -+ pub visible: Vec, -+ /// The instance of `visible[0]` in the section origins. -+ pub first: u32, -+} -+ -+pub(super) struct Views { -+ pub targets: HashMap, -+ /// The player's camera's globals, copied back after the pictures. -+ pub main: wgpu::Buffer, -+ cursor: usize, -+ format: wgpu::TextureFormat, -+ layout: wgpu::BindGroupLayout, -+ sampler: wgpu::Sampler, -+ pipeline: wgpu::RenderPipeline, -+} -+ -+const BLIT: &str = r#" -+@group(0) @binding(0) var picture: texture_2d; -+@group(0) @binding(1) var picture_sampler: sampler; -+struct Out { -+ @builtin(position) position: vec4, -+ @location(0) uv: vec2, -+ @location(1) @interpolate(flat) mode: u32, -+} -+@vertex -+fn vs(@builtin(vertex_index) i: u32, @builtin(instance_index) mode: u32) -> Out { -+ let uv = vec2(f32((i << 1u) & 2u), f32(i & 2u)); -+ var out: Out; -+ out.position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); -+ out.uv = uv; -+ out.mode = mode; -+ return out; -+} -+fn hash(p: vec2) -> f32 { -+ return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.547); -+} -+@fragment -+fn fs(in: Out) -> @location(0) vec4 { -+ let c = textureSample(picture, picture_sampler, in.uv).rgb; -+ let l = dot(c, vec3(0.299, 0.587, 0.114)); -+ let grain = (hash(in.position.xy + vec2(c.r * 91.0, c.g * 37.0)) - 0.5) * 0.06; -+ if in.mode == 1u { -+ // White hot: the cold sky dark, the land grey, flames and engines burning white. -+ let warm = max(c.r - c.b, 0.0) * 2.2; -+ let cold = max(c.b - c.r, 0.0) * 2.5; -+ let glow = smoothstep(0.8, 1.0, max(c.r, c.g)); -+ // High gain: a thermal camera sees as well by night. -+ let v = clamp(0.06 + pow(l, 0.4) * 0.55 + warm + glow * 0.9 - cold + grain, 0.0, 1.0); -+ return vec4(v, v * 0.98, v * 0.94, 1.0); -+ } -+ if in.mode == 2u { -+ let v = clamp(pow(l, 0.45) * 1.5 + grain, 0.0, 1.0); -+ return vec4(v * 0.3, v, v * 0.35, 1.0); -+ } -+ return vec4(c, 1.0); -+} -+"#; -+ -+impl Views { -+ pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat, globals_size: u64) -> Self { -+ let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("Mod view picture"), -+ entries: &[ -+ wgpu::BindGroupLayoutEntry { -+ binding: 0, -+ visibility: wgpu::ShaderStages::FRAGMENT, -+ ty: wgpu::BindingType::Texture { -+ sample_type: wgpu::TextureSampleType::Float { filterable: true }, -+ view_dimension: wgpu::TextureViewDimension::D2, -+ multisampled: false, -+ }, -+ count: None, -+ }, -+ wgpu::BindGroupLayoutEntry { -+ binding: 1, -+ visibility: wgpu::ShaderStages::FRAGMENT, -+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), -+ count: None, -+ }, -+ ], -+ }); -+ let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { -+ label: Some("Mod view blit"), -+ source: wgpu::ShaderSource::Wgsl(BLIT.into()), -+ }); -+ let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { -+ label: Some("Mod view blit"), -+ bind_group_layouts: &[Some(&layout)], -+ ..Default::default() -+ }); -+ let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("Mod view blit"), -+ layout: Some(&pipeline_layout), -+ vertex: wgpu::VertexState { -+ module: &module, -+ entry_point: Some("vs"), -+ compilation_options: Default::default(), -+ buffers: &[], -+ }, -+ fragment: Some(wgpu::FragmentState { -+ module: &module, -+ entry_point: Some("fs"), -+ compilation_options: Default::default(), -+ targets: &[Some(wgpu::ColorTargetState { -+ format, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ })], -+ }), -+ primitive: Default::default(), -+ depth_stencil: None, -+ multisample: Default::default(), -+ multiview_mask: None, -+ cache: None, -+ }); -+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor { -+ label: Some("Mod view picture"), -+ mag_filter: wgpu::FilterMode::Linear, -+ min_filter: wgpu::FilterMode::Linear, -+ ..Default::default() -+ }); -+ let main = device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some("Camera globals"), -+ size: globals_size, -+ usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }); -+ Self { -+ targets: HashMap::new(), -+ main, -+ cursor: 0, -+ format, -+ layout, -+ sampler, -+ pipeline, -+ } -+ } -+ -+ /// The cameras to draw this frame, in turn, at most `PER_FRAME`; their -+ /// targets made or remade to size, the gone ones' dropped. -+ pub fn choose<'a>( -+ &mut self, -+ device: &wgpu::Device, -+ views: &'a [ModView], -+ globals_size: u64, -+ ) -> Vec<&'a ModView> { -+ self.targets.retain(|id, _| views.iter().any(|v| v.id == *id)); -+ for v in views { -+ if self.targets.get(&v.id).is_none_or(|t| t.size != v.size) { -+ let t = self.target(device, v.size, globals_size); -+ self.targets.insert(v.id, t); -+ } -+ } -+ if views.is_empty() { -+ return Vec::new(); -+ } -+ let n = views.len().min(PER_FRAME); -+ let start = self.cursor % views.len(); -+ self.cursor = self.cursor.wrapping_add(n); -+ (0..n).map(|k| &views[(start + k) % views.len()]).collect() -+ } -+ -+ fn target(&self, device: &wgpu::Device, size: [u32; 2], globals_size: u64) -> Target { -+ let extent = wgpu::Extent3d { -+ width: size[0], -+ height: size[1], -+ depth_or_array_layers: 1, -+ }; -+ let texture = |label, format, usage| { -+ device -+ .create_texture(&wgpu::TextureDescriptor { -+ label: Some(label), -+ size: extent, -+ mip_level_count: 1, -+ sample_count: 1, -+ dimension: wgpu::TextureDimension::D2, -+ format, -+ usage, -+ view_formats: &[], -+ }) -+ .create_view(&Default::default()) -+ }; -+ let color = texture( -+ "Mod view colour", -+ self.format, -+ wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, -+ ); -+ let depth = texture( -+ "Mod view depth", -+ wgpu::TextureFormat::Depth32Float, -+ wgpu::TextureUsages::RENDER_ATTACHMENT, -+ ); -+ let blit = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("Mod view picture"), -+ layout: &self.layout, -+ entries: &[ -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: wgpu::BindingResource::TextureView(&color), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::Sampler(&self.sampler), -+ }, -+ ], -+ }); -+ let globals = device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some("Mod view globals"), -+ size: globals_size, -+ usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }); -+ Target { -+ size, -+ color, -+ depth, -+ globals, -+ blit, -+ drawn: false, -+ } -+ } -+ -+ /// Shows a camera's picture in `rect` (framebuffer pixels) of `screen`. -+ pub fn blit( -+ &self, -+ encoder: &mut wgpu::CommandEncoder, -+ screen: &wgpu::TextureView, -+ screen_size: [u32; 2], -+ view: &HudView, -+ scale: f32, -+ ) { -+ let Some(t) = self.targets.get(&view.id).filter(|t| t.drawn) else { -+ return; -+ }; -+ let [x, y, w, h] = view.rect.map(|v| v * scale); -+ let (x0, y0) = (x.max(0.0), y.max(0.0)); -+ let x1 = (x + w).min(screen_size[0] as f32); -+ let y1 = (y + h).min(screen_size[1] as f32); -+ if x1 - x0 < 1.0 || y1 - y0 < 1.0 { -+ return; -+ } -+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("Mod view picture"), -+ color_attachments: &[Some(wgpu::RenderPassColorAttachment { -+ view: screen, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { -+ load: wgpu::LoadOp::Load, -+ store: wgpu::StoreOp::Store, -+ }, -+ })], -+ ..Default::default() -+ }); -+ pass.set_viewport(x, y, w, h, 0.0, 1.0); -+ pass.set_scissor_rect(x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32); -+ pass.set_pipeline(&self.pipeline); -+ pass.set_bind_group(0, &t.blit, &[]); -+ let mode = view.mode.min(2); -+ pass.draw(0..3, mode..mode + 1); -+ } -+} -+ -+/// A camera's view and projection: the view from `camera`, moved so that -+/// geometry relative to `eye` (the player's camera) lands right. -+pub(super) fn matrices(v: &ModView, eye: DVec3, far: f32) -> (Mat4, Mat4, f32) { -+ let aspect = v.size[0] as f32 / v.size[1].max(1) as f32; -+ let view = crate::camera::view_matrix(v.yaw, v.pitch); -+ let projection = super::world_projection(v.fov, aspect, far); -+ let offset = (eye - v.camera).as_vec3(); -+ (projection * view * Mat4::from_translation(offset), view, aspect) -+} -diff --git a/crates/horizon-client/src/hud.rs b/crates/horizon-client/src/hud.rs -index fda0094d8..41af0650e 100644 ---- a/crates/horizon-client/src/hud.rs -+++ b/crates/horizon-client/src/hud.rs -@@ -625,6 +625,17 @@ pub struct HudVertex { - } - /// The GUI Scale option (0 = auto). - pub static GUI_SCALE: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); -+/// Where the HUD shows a mod camera's picture (AirDef fork): before its -+/// vertex `at`, over what was drawn before. -+#[derive(Clone, Copy, Debug)] -+pub struct HudView { -+ pub at: u32, -+ pub id: u32, -+ /// GUI pixels: x, y, width, height. -+ pub rect: [f32; 4], -+ /// 0 as seen, 1 thermal, 2 night vision. -+ pub mode: u32, -+} - /// One frame of overlay geometry in GUI pixels. - pub struct HudBatch { - pub vertices: Vec, -@@ -641,6 +652,8 @@ pub struct HudBatch { - pub glint_alpha: f32, - /// Screen's first vertex: the preceding HUD is part of its blurred background. - pub blur_at: u32, -+ /// Mod cameras' pictures, in vertex order. -+ pub views: Vec, - } - /// How a run of HUD vertices blends and tests depth. - #[derive(Clone, Copy, PartialEq, Eq, Debug)] -@@ -692,6 +705,7 @@ impl HudBatch { - end_portal: EndPortalFill::default(), - glint_alpha: 0.75, - blur_at: 0, -+ views: Vec::new(), - } - } - /// Draw the vertices added since `start` with the inverting blend. -@@ -2008,6 +2022,7 @@ mod tests { - end_portal: EndPortalFill::default(), - glint_alpha: 0.75, - blur_at: 0, -+ views: Vec::new(), - }; - for (image_width, image_height) in [(195, 136), (176, 166), (176, 222)] { - assert_eq!( -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index 1b167c7f7..436455a19 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -338,12 +338,21 @@ interface render { - centered: bool, - shadow: bool, - } -+ /// A picture of one of the mod's `set-views` cameras (AirDef fork). -+ record view-shape { -+ /// The camera's id; an unknown one draws nothing. -+ view: u32, -+ rect: rect, -+ /// 0 as seen, 1 thermal (white hot), 2 night vision (green). -+ mode: u32, -+ } - variant shape { - fill(fill-shape), - quad(quad-shape), - sprite(sprite-shape), - item(item-shape), - text(text-shape), -+ view(view-shape), - } - /// Replaces the mod's HUD shapes: up to 1024, drawn every frame in - /// order over the HUD (after the hotbar, under chat and screens), hidden -@@ -500,6 +509,27 @@ interface render { - /// replaced; call it once a tick (the pass gets the partial tick in - /// `frame.time.y`). - set-params: func(params: list); -+ /// A camera the game renders the world from besides the player's eye -+ /// (AirDef fork): the land, the sky and the entities near the player. -+ record camera-view { -+ /// Stays the same while the camera lives: the game turns it smoothly -+ /// between the last two `set-views`. -+ id: u32, -+ x: f64, -+ y: f64, -+ z: f64, -+ /// Degrees, as the player's: yaw 0 looks south (+z), pitch 90 down. -+ yaw: f32, -+ pitch: f32, -+ /// Vertical field of view, degrees (1 to 110). -+ fov: f32, -+ /// The picture's size in pixels (16 to 640 × 16 to 480). -+ width: u32, -+ height: u32, -+ } -+ /// Replaces the mod's cameras: up to 8. A `view` shape shows one; the -+ /// game renders up to three a frame, in turn. -+ set-views: func(views: list); - } - - /// What the game calls. Only the hooks `init` asks for are ever called. -diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs -index 68487e600..9d15851b4 100644 ---- a/crates/horizon-mods/src/api_level.rs -+++ b/crates/horizon-mods/src/api_level.rs -@@ -7,13 +7,13 @@ - //! version's level so the launcher can tell before it installs. - - /// The newest level this Horizon serves. --pub const LEVEL: u32 = 8; -+pub const LEVEL: u32 = 9; - - /// Every function the game offers mods, as `interface.function`, with its - /// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new - /// function takes the next level and raises [`LEVEL`] (the `api_reference` - /// test fails until it is listed here). --pub const FUNCTIONS: [(&str, u32); 54] = [ -+pub const FUNCTIONS: [(&str, u32); 55] = [ - ("log.write", 0), - ("game.get-player", 0), - ("game.get-clock", 0), -@@ -69,6 +69,7 @@ pub const FUNCTIONS: [(&str, u32); 54] = [ - ("render.set-post", 8), - ("render.clear-post", 8), - ("render.set-params", 8), -+ ("render.set-views", 9), - ]; - - /// What the game's program carries so the launcher can read its level -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index 55c1fd13f..01000dfeb 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -81,7 +81,7 @@ pub use render_bindings::horizon::mods::input::{Event as InputEvent, HeldItem, I - pub use render_bindings::horizon::mods::render::{ - Corner as RenderCorner, FarTerrain as ModFarTerrain, FillShape, FrameRate, ItemShape, - Minimap as ModMinimap, ModelInstance, ParticleSpawn, Point as RenderPoint, QuadShape, -- Rect as RenderRect, Shape, SpriteShape, TextShape, Zoom as ModZoom, -+ CameraView, Rect as RenderRect, Shape, SpriteShape, TextShape, ViewShape, Zoom as ModZoom, - }; - use render_bindings::{RenderHost, horizon::mods::render}; - /// Capability `recipes`'s imports (docs/mods.md): the game's recipes and items. -@@ -435,6 +435,8 @@ struct State { - /// `render.set-post`'s source, with a number that changes with it. - post: Option<(u64, String)>, - post_params: Vec, -+ /// `render.set-views`' cameras (AirDef fork). -+ views: Vec, - input: InputState, - browser: BrowserState, - /// Capability `storage`'s folder; made on first `set`. -@@ -643,6 +645,7 @@ impl Mods { - ground: Vec::new(), - post: None, - post_params: Vec::new(), -+ views: Vec::new(), - input: InputState::default(), - browser: BrowserState::default(), - data_dir, -@@ -1206,6 +1209,14 @@ impl Mods { - .flat_map(move |r| r.store.data().models.iter().map(move |m| (i, m))) - }) - } -+ /// Every running mod's cameras (AirDef fork), with the mod's index. -+ pub fn views(&self) -> impl Iterator { -+ self.entries.iter().enumerate().flat_map(|(i, e)| { -+ e.run -+ .iter() -+ .flat_map(move |r| r.store.data().views.iter().map(move |v| (i, v))) -+ }) -+ } - /// The columns each running mod asked the ground height of, taken. - pub fn take_ground_queries(&mut self) -> Vec<(usize, Vec<(i32, i32)>)> { - self.entries -@@ -1969,6 +1980,23 @@ impl render::Host for State { - .map(|v| if v.is_finite() { v } else { 0.0 }) - .collect(); - } -+ fn set_views(&mut self, views: Vec) { -+ let finite = |v: f64| v.is_finite() && v.abs() < 30_000_000.0; -+ let angle = |v: f32| if v.is_finite() { v } else { 0.0 }; -+ self.views = views -+ .into_iter() -+ .filter(|v| finite(v.x) && finite(v.y) && finite(v.z)) -+ .take(8) -+ .map(|mut v| { -+ v.yaw = angle(v.yaw); -+ v.pitch = angle(v.pitch).clamp(-90.0, 90.0); -+ v.fov = if v.fov.is_finite() { v.fov.clamp(1.0, 110.0) } else { 70.0 }; -+ v.width = v.width.clamp(16, 640); -+ v.height = v.height.clamp(16, 480); -+ v -+ }) -+ .collect(); -+ } - fn query_ground(&mut self, columns: Vec<(i32, i32)>) { - let room = 512usize.saturating_sub(self.ground_queries.len()); - self.ground_queries.extend(columns.into_iter().take(room)); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch b/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch deleted file mode 100644 index e6d368d..0000000 --- a/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 8d2261fc4f8072c32e454139cfea7a8c0cd9d664 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 23:08:21 +0300 -Subject: [PATCH 06/42] Mods: more fuel per tick and per event (AirDef plans - raids) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-mods/src/runtime.rs | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index 01000dfeb..a551b7376 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -108,9 +108,9 @@ const MEMORY: usize = 64 << 20; - /// Fuel (about one unit per WebAssembly instruction) per call: a bound on - /// runaway loops, far above the hooks' time budgets. - const FUEL_INIT: u64 = 2_000_000_000; --const FUEL_TICK: u64 = 50_000_000; -+const FUEL_TICK: u64 = 300_000_000; - const FUEL_FRAME: u64 = 10_000_000; --const FUEL_EVENT: u64 = 100_000_000; -+const FUEL_EVENT: u64 = 1_000_000_000; - /// Blocks a mod may set per `world.set-blocks` call. - pub const BLOCKS_PER_CALL: usize = 4096; - /// Chat lines a mod may show per call, HUD lines and their length. --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch b/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch deleted file mode 100644 index 58a9478..0000000 --- a/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 6c548de34def8e4a624d734243418090798bd16a Mon Sep 17 00:00:00 2001 -From: chexit -Date: Wed, 28 Oct 2026 23:25:29 +0300 -Subject: [PATCH 07/42] Mod cameras up to 1920x1080 (full-screen replays) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-mod-sdk/wit/horizon.wit | 2 +- - crates/horizon-mods/src/runtime.rs | 4 ++-- - 2 files changed, 3 insertions(+), 3 deletions(-) - -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index 436455a19..e30f3a30e 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -523,7 +523,7 @@ interface render { - pitch: f32, - /// Vertical field of view, degrees (1 to 110). - fov: f32, -- /// The picture's size in pixels (16 to 640 × 16 to 480). -+ /// The picture's size in pixels (16 to 1920 × 16 to 1080). - width: u32, - height: u32, - } -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index a551b7376..1389407be 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -1991,8 +1991,8 @@ impl render::Host for State { - v.yaw = angle(v.yaw); - v.pitch = angle(v.pitch).clamp(-90.0, 90.0); - v.fov = if v.fov.is_finite() { v.fov.clamp(1.0, 110.0) } else { 70.0 }; -- v.width = v.width.clamp(16, 640); -- v.height = v.height.clamp(16, 480); -+ v.width = v.width.clamp(16, 1920); -+ v.height = v.height.clamp(16, 1080); - v - }) - .collect(); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch b/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch deleted file mode 100644 index c26a7b5..0000000 --- a/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 9b9e93269d67401cc620567a884d5ce97f68d76e Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 11:15:11 +0300 -Subject: [PATCH 08/42] Mod cameras: distant terrain tiles, fog measured from - the camera, no impostors while one is shown - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game/mod_models.rs | 4 +- - crates/horizon-client/src/gfx.rs | 53 +++++++++++++++++--- - crates/horizon-client/src/gfx/mod_views.rs | 4 ++ - 3 files changed, 52 insertions(+), 9 deletions(-) - -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index 7ae58f664..87d46c843 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -164,6 +164,8 @@ impl Game<'_> { - self.settings.cloud_range, - )); - let reach = depth_far * 0.85; -+ // A mod camera (a replay's, a system's) must see them where they are: no impostors then. -+ let impostors = self.mods.models.views.is_empty(); - for p in self.mods.models.poses.values() { - let mut at = p.previous.lerp(p.position, t); - let d = at.distance(camera); -@@ -171,7 +173,7 @@ impl Game<'_> { - continue; - } - let mut shrink = 1.0f32; -- if d > reach { -+ if d > reach && impostors { - shrink = (reach / d) as f32; - at = camera + (at - camera) * (reach / d); - } -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index 3ad257728..9772e3a83 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -3078,6 +3078,9 @@ impl Renderer { - d.aspect, - (d.fov.to_radians() / 2.0).tan(), - ], -+ // Fog goes by the distance from the player's camera: pushed out by how far this -+ // camera is from it, so what is near this one stays clear. -+ fog: globals.fog.map(|f| f + d.away), - ..*globals - }; - self.uploads -@@ -3138,6 +3141,19 @@ impl Renderer { - let instance = d.first + k as u32; - pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); - } -+ for (k, v) in d.lods.iter().enumerate() { -+ let Some(tile) = self.lods.get(&v.key) else { continue }; -+ let s = &tile.mesh; -+ if s.solid == 0 { -+ continue; -+ } -+ if page != Some(s.page) { -+ pass.set_vertex_buffer(0, self.arena.buffer(s.page).slice(..)); -+ page = Some(s.page); -+ } -+ let instance = d.first + (d.visible.len() + k) as u32; -+ pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); -+ } - if entity_quads > 0 { - pass.set_pipeline(&self.entity_pipeline); - pass.set_bind_group(0, &self.entity_group, &[]); -@@ -3971,14 +3987,30 @@ impl Renderer { - let own = world_projection(v.fov, aspect, frame.depth_far) * view; - let planes = Frustum { planes: frustum(own) }; - let mut seen = Vec::new(); -- self.sections -- .visible(v.camera, radius, &planes, |_| true, |_, _| false, &mut seen); -+ // Where distant terrain covers a column, its tile draws instead, as in the world pass. -+ self.sections.visible( -+ v.camera, -+ radius, -+ &planes, -+ |_| true, -+ |x, z| !cutout.is_empty() && cutout.contains(&(x, z)), -+ &mut seen, -+ ); -+ let mut tiles: Vec = self -+ .lods -+ .iter() -+ .filter_map(|(&key, tile)| tile.visible(key, v.camera, frame.distance, &planes)) -+ .collect(); - // Relative to the player's camera, as the rest of the frame. - let shift = (v.camera - camera).as_vec3(); -+ let moved = |o: [f32; 3]| [o[0] + shift.x, o[1] + shift.y, o[2] + shift.z]; - for s in &mut seen { -- s.origin = [s.origin[0] + shift.x, s.origin[1] + shift.y, s.origin[2] + shift.z]; -+ s.origin = moved(s.origin); -+ } -+ for t in &mut tiles { -+ t.origin = moved(t.origin); - } -- let n = seen.len() as u32; -+ let n = (seen.len() + tiles.len()) as u32; - views_due.push(mod_views::Due { - id: v.id, - view_proj, -@@ -3986,14 +4018,16 @@ impl Renderer { - aspect, - fov: v.fov, - visible: seen, -+ lods: tiles, - first, -+ away: shift.length(), - }); - first += n; - } - } else if frame.mod_views.is_empty() { - self.mod_views = None; - } -- let view_sections: usize = views_due.iter().map(|d| d.visible.len()).sum(); -+ let view_sections: usize = views_due.iter().map(|d| d.visible.len() + d.lods.len()).sum(); - let instances = self.drawn_sections + shadow_visible.len() + view_sections; - if instances as u64 > self.instance_capacity { - self.instance_capacity = (instances as u64).next_power_of_two(); -@@ -4014,9 +4048,12 @@ impl Renderer { - .chain(visible_lods.iter().map(|v| lod::instance(v.origin))) - .chain(shadow_visible.iter().map(|v| section_instance(v.origin))) - .chain( -- views_due -- .iter() -- .flat_map(|d| d.visible.iter().map(|v| section_instance(v.origin))), -+ views_due.iter().flat_map(|d| { -+ d.visible -+ .iter() -+ .map(|v| section_instance(v.origin)) -+ .chain(d.lods.iter().map(|v| lod::instance(v.origin))) -+ }), - ), - ); - if !self.origins.is_empty() { -diff --git a/crates/horizon-client/src/gfx/mod_views.rs b/crates/horizon-client/src/gfx/mod_views.rs -index 91ae103a8..c0d886afd 100644 ---- a/crates/horizon-client/src/gfx/mod_views.rs -+++ b/crates/horizon-client/src/gfx/mod_views.rs -@@ -45,8 +45,12 @@ pub(super) struct Due { - pub aspect: f32, - pub fov: f32, - pub visible: Vec, -+ /// Distant terrain tiles in its view, after the sections. -+ pub lods: Vec, - /// The instance of `visible[0]` in the section origins. - pub first: u32, -+ /// How far it is from the player's camera: fog is measured from there. -+ pub away: f32, - } - - pub(super) struct Views { --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch b/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch deleted file mode 100644 index cc5fee9..0000000 --- a/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 065d9fa02966238a5bd9ff460392a1a809db0b11 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 11:25:45 +0300 -Subject: [PATCH 09/42] Mods: game.world-id, so mods keep state per world (API - level 9) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game.rs | 1 + - crates/horizon-client/src/game/mods.rs | 3 +++ - crates/horizon-client/src/game/remote.rs | 4 ++++ - crates/horizon-mod-sdk/wit/horizon.wit | 4 ++++ - crates/horizon-mods/src/api_level.rs | 3 ++- - crates/horizon-mods/src/runtime.rs | 5 +++++ - 6 files changed, 19 insertions(+), 1 deletion(-) - -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index e5ffb30c4..c6b642345 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -8918,6 +8918,7 @@ impl Game<'_> { - create: Option<&horizon_client::title::WorldOptions>, - ) -> Result<()> { - world_loading::world_data_used(); -+ self.mods.world_id = Some(format!("world:{folder}")); - if self.defer_world_entry(PendingWorldEntry::Local { - folder: folder.to_owned(), - name: name.map(str::to_owned), -diff --git a/crates/horizon-client/src/game/mods.rs b/crates/horizon-client/src/game/mods.rs -index 3eef96aa3..ebdd18a24 100644 ---- a/crates/horizon-client/src/game/mods.rs -+++ b/crates/horizon-client/src/game/mods.rs -@@ -38,6 +38,8 @@ use { - - #[derive(Default)] - pub(super) struct ModsState { -+ /// The open world, for `game.world-id` (AirDef fork): its folder, or the server's address. -+ pub(super) world_id: Option, - #[cfg(feature = "mods")] - pub(super) mods: Mods, - /// Capability `input`: a claimed item's use while the button is down. -@@ -482,6 +484,7 @@ impl Game<'_> { - known_recipes, - placing: self.mod_recipe_screen(), - creative: self.creative, -+ world: self.player.is_some().then(|| self.mods.world_id.clone()).flatten(), - } - } - /// The first block along the player's view within -diff --git a/crates/horizon-client/src/game/remote.rs b/crates/horizon-client/src/game/remote.rs -index 22b7c13df..89df0616b 100644 ---- a/crates/horizon-client/src/game/remote.rs -+++ b/crates/horizon-client/src/game/remote.rs -@@ -342,6 +342,10 @@ impl Game<'_> { - cookies: Option, - ) -> Result<()> { - self.perf.world_opening(); -+ // A server's own world; joining the world this window hosts keeps its folder. -+ if local.is_none() { -+ self.mods.world_id = Some(format!("server:{address}")); -+ } - let root = self.options.resources.clone(); - let builtin = Builtin::find(&root) - .and_then(|p| Builtin::load(&p).ok()) -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index e30f3a30e..b6758d9fb 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -47,6 +47,10 @@ interface game { - get-player: func() -> option; - /// None on the title screen. - get-clock: func() -> option; -+ /// Which world is open (AirDef fork): `world:` for a saved -+ /// world, `server:
` on a server; none on the title screen. -+ /// Mods keep their state apart by it. -+ world-id: func() -> option; - /// A block along the player's view. - record hit { - /// The block hit. -diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs -index 9d15851b4..cf2993186 100644 ---- a/crates/horizon-mods/src/api_level.rs -+++ b/crates/horizon-mods/src/api_level.rs -@@ -13,7 +13,7 @@ pub const LEVEL: u32 = 9; - /// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new - /// function takes the next level and raises [`LEVEL`] (the `api_reference` - /// test fails until it is listed here). --pub const FUNCTIONS: [(&str, u32); 55] = [ -+pub const FUNCTIONS: [(&str, u32); 56] = [ - ("log.write", 0), - ("game.get-player", 0), - ("game.get-clock", 0), -@@ -70,6 +70,7 @@ pub const FUNCTIONS: [(&str, u32); 55] = [ - ("render.clear-post", 8), - ("render.set-params", 8), - ("render.set-views", 9), -+ ("game.world-id", 9), - ]; - - /// What the game's program carries so the launcher can read its level -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index 1389407be..abed5b31f 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -168,6 +168,8 @@ pub struct Snapshot { - pub placing: bool, - /// The player is in creative mode (`recipes.give`). - pub creative: bool, -+ /// The open world (`game.world-id`, AirDef fork). -+ pub world: Option, - } - - /// Capability `recipes`'s tables: `items` in the registry's order, which -@@ -1475,6 +1477,9 @@ impl game::Host for State { - fn get_clock(&mut self) -> Option { - self.snapshot.clock - } -+ fn world_id(&mut self) -> Option { -+ self.snapshot.world.clone() -+ } - fn raycast(&mut self, distance: f32) -> Option { - let (at, hit) = self.snapshot.view_hit.as_ref()?; - // NaN compares false: no hit. --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch b/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch deleted file mode 100644 index 0c411c6..0000000 --- a/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 1bf28135fd9a31f837323dc3a8d81ed73343d816 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 12:01:08 +0300 -Subject: [PATCH 10/42] cargo-horizon-mod: trial runs behind feature full, so a - packing-only tool skips wasmtime - -Co-Authored-By: Claude Opus 5.5 ---- - crates/cargo-horizon-mod/Cargo.toml | 8 +++++++- - crates/cargo-horizon-mod/src/main.rs | 16 ++++++++++++++-- - 2 files changed, 21 insertions(+), 3 deletions(-) - -diff --git a/crates/cargo-horizon-mod/Cargo.toml b/crates/cargo-horizon-mod/Cargo.toml -index 34eefe227..21b8a9221 100644 ---- a/crates/cargo-horizon-mod/Cargo.toml -+++ b/crates/cargo-horizon-mod/Cargo.toml -@@ -6,8 +6,14 @@ license.workspace = true - rust-version = "1.86" - description = "cargo horizon-mod new/build/run/check: make Horizon mods in Rust (docs/mods.md)" - -+[features] -+default = ["full"] -+# The trial runs (`check`, `test`) and the registry's CI need wasmtime. Without this feature the -+# tool is new, build and run only, and builds in a fraction of the time (CI that only packs mods). -+full = ["horizon-mods/runtime", "horizon-mods/index"] -+ - [dependencies] --horizon-mods = { path = "../horizon-mods", default-features = false, features = ["pack", "runtime", "index"] } -+horizon-mods = { path = "../horizon-mods", default-features = false, features = ["pack"] } - serde_json = "1.0.151" - - [lints] -diff --git a/crates/cargo-horizon-mod/src/main.rs b/crates/cargo-horizon-mod/src/main.rs -index 95d0a9eb3..ff4ab663b 100644 ---- a/crates/cargo-horizon-mod/src/main.rs -+++ b/crates/cargo-horizon-mod/src/main.rs -@@ -11,12 +11,15 @@ - //! - `check [--json]`: what the registry checks of a submission - //! (`.hmod`, `.hpack`, resource pack `.zip`), with a trial run for a mod. - //! - `registry build|review|check|list`: the registry repository's CI. -+#[cfg(feature = "full")] -+use horizon_mods::testing::{Script, TestGame}; - use horizon_mods::{ - Capability, Content, Manifest, Package, - manifest::{MANIFEST, component_imports, componentize}, -- testing::{Script, TestGame}, - }; -+#[cfg(feature = "full")] - mod check; -+#[cfg(feature = "full")] - mod registry; - - use std::{ -@@ -39,9 +42,16 @@ fn main() -> ExitCode { - println!("Built {}", p.display()); - }), - Some("run") => run(&args[1..]), -+ #[cfg(feature = "full")] - Some("test") => test(&args[1..]), -+ #[cfg(feature = "full")] - Some("check") => check(&args[1..]), -+ #[cfg(feature = "full")] - Some("registry") => registry::main(&args[1..]), -+ #[cfg(not(feature = "full"))] -+ Some("test" | "check" | "registry") => { -+ Err("this cargo-horizon-mod was built without trial runs (feature `full`)".into()) -+ } - _ => Err("usage: cargo horizon-mod new [--template code|content] | build [--debug] | run [--game ] [-- ] | test [--debug] [--hmod ] [] | check [--kind ] [--key --signature ] [--json] | registry build|review|check|list".into()), - }; - match result { -@@ -249,6 +259,7 @@ fn compile(root: &Path, release: bool) -> Result<(Vec, PathBuf), String> { - - /// `check `: the registry's checks of a submission, printed; fails - /// when one does. -+#[cfg(feature = "full")] - fn check(args: &[String]) -> Result<(), String> { - let file = args - .first() -@@ -280,6 +291,7 @@ fn check(args: &[String]) -> Result<(), String> { - - /// `test`: every `tests/*.toml` script (those whose name holds ``) - /// against the built mod, or the `.hmod` given with `--hmod`. -+#[cfg(feature = "full")] - fn test(args: &[String]) -> Result<(), String> { - let hmod = match flag(args, "--hmod") { - Some(p) => PathBuf::from(p), -@@ -360,7 +372,7 @@ fn run(args: &[String]) -> Result<(), String> { - .ok_or_else(|| format!("the game exited with {status}")) - } - --#[cfg(test)] -+#[cfg(all(test, feature = "full"))] - mod tests { - use super::*; - --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch b/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch deleted file mode 100644 index f4f55c9..0000000 --- a/horizon-patches/0011-HD-lighting-G-buffer-pass-per-pixel-mod-point-lights.patch +++ /dev/null @@ -1,1152 +0,0 @@ -From 0a56f5616df4699588cbcc529d4720661baa810f Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 13:18:43 +0300 -Subject: [PATCH 11/42] HD lighting: G-buffer pass, per-pixel mod point lights - in HDR, bloom and tone curve; render.set-lights and render.set-lighting (API - level 10) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/entity.wgsl | 22 + - crates/horizon-client/src/game.rs | 5 + - crates/horizon-client/src/game/mod_models.rs | 58 ++- - crates/horizon-client/src/gfx.rs | 149 +++++- - crates/horizon-client/src/gfx/hd.rs | 520 +++++++++++++++++++ - crates/horizon-client/src/terrain.wgsl | 25 + - crates/horizon-mod-sdk/wit/horizon.wit | 28 + - crates/horizon-mods/src/api_level.rs | 6 +- - crates/horizon-mods/src/runtime.rs | 39 +- - 9 files changed, 845 insertions(+), 7 deletions(-) - create mode 100644 crates/horizon-client/src/gfx/hd.rs - -diff --git a/crates/horizon-client/src/entity.wgsl b/crates/horizon-client/src/entity.wgsl -index 919c5edbb..01c64def9 100644 ---- a/crates/horizon-client/src/entity.wgsl -+++ b/crates/horizon-client/src/entity.wgsl -@@ -56,6 +56,8 @@ struct Out { - @location(11) glint_uv: vec2, - // 1 drops the quad's back-facing pixels (Vertex::normal w = 1), like a culling render type. - @location(12) @interpolate(flat) cull: u32, -+ // AirDef fork, HD lighting's G-buffer: the camera-relative position. -+ @location(13) pos: vec3, - } - // Shipped light.glsl; normals are already transformed and RGBA8_SNORM packed. - fn entity_light(normal: vec3, nether: bool) -> f32 { -@@ -118,6 +120,7 @@ fn entity_vertex(position: vec3, uv: vec2, - out.spherical = select(length(position), 0.0, overlay >= 2u && overlay <= 4u); - out.cylindrical = select(max(length(position.xz), abs(position.y)), 0.0, overlay >= 2u && overlay <= 4u); - out.overlay = overlay; -+ out.pos = position; - out.glint_uv = vec2(0.0); - if overlay == 4u { out.spherical = length(position); } - return out; -@@ -261,6 +264,25 @@ fn crumbling_uv(uv: vec2, packed_origin: vec2, sprite_size: vec2, - return vec4(mix(color.rgb, globals.fog_color.rgb, f * globals.fog_color.a), color.a); - } - -+// AirDef fork: the HD lighting's G-buffer for entities (and the mods' models): the textured -+// colour without the lightmap; full-bright ones (unlit overlays) glow. -+struct EntityGBufferOut { -+ @location(0) albedo: vec4, -+ @location(1) normal: vec4, -+} -+@fragment fn fs_entity_gbuffer(in: Out, @builtin(front_facing) front: bool) -> EntityGBufferOut { -+ let color = textureSample(atlas, atlas_sampler, in.uv) * select(in.back_color, in.color, front); -+ if color.a < 0.1 || (in.cull != 0u && !front) || in.overlay == 4u || in.overlay == 9u || in.overlay == 10u { discard; } -+ var n = normalize(cross(dpdx(in.pos), dpdy(in.pos))); -+ if dot(n, in.pos) > 0.0 { n = -n; } -+ // The mods' glowing parts carry overlay 40 (game/mod_models.rs): they glow. -+ let glow = select(0.0, 1.0, in.overlay == 40u); -+ var out: EntityGBufferOut; -+ out.albedo = vec4(color.rgb, glow); -+ out.normal = vec4(n, 1.0); -+ return out; -+} -+ - // rendertype_outline.fsh: the vertex colour (the team colour; outline vertices carry no - // normal, so no cardinal light) wherever the texture is not fully clear. - @fragment fn fs_outline(in: Out) -> @location(0) vec4 { -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index c6b642345..3c97bbfb6 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -11808,8 +11808,11 @@ impl Game<'_> { - let entity_spans = std::mem::take(self.entity_spans.get_mut()); - let glowing = std::mem::take(self.glowing_vertices.get_mut()); - let mod_views = self.mod_views(alpha); -+ let hd_lights = self.mod_lights(camera, alpha); - let frame = Frame { - mod_views: &mod_views, -+ hd: self.mod_hd(), -+ hd_lights: &hd_lights, - weather: &weather, - weather_time: horizon_client::weather::Time::new(self.world.game_time, alpha as f32), - nether_lighting: self.dim == Dim::Nether, -@@ -14729,6 +14732,8 @@ fn loading_renderer( - fn loading_frame(hud: &HudBatch) -> Frame<'_> { - Frame { - mod_views: &[], -+ hd: None, -+ hd_lights: &[], - weather: &[], - weather_time: horizon_client::weather::Time::new(0, 0.0), - camera: glam::DVec3::new(0.5, 100.0, 0.5), -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index 87d46c843..e6dbc7953 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -11,7 +11,7 @@ use { - particles::{ParticleOptions, ParticleType}, - }, - horizon_core::random::RandomSource, -- horizon_mods::{CameraView, ParticleSpawn}, -+ horizon_mods::{CameraView, ParticleSpawn, PointLight}, - horizon_world::registry::BlockStateId, - std::collections::HashMap, - }; -@@ -40,6 +40,8 @@ pub(super) struct ModModels { - post: u64, - /// The mods' cameras by id: a tick ago and now. - views: Vec<(CameraView, CameraView)>, -+ /// The mods' lights by (mod, id): a tick ago and now. -+ lights: HashMap<(usize, u32), (PointLight, PointLight)>, - } - - /// Full brightness: sky and block light 15, as the entity shader packs them. -@@ -60,6 +62,12 @@ impl Game<'_> { - pub(super) fn mod_views(&self, _alpha: f64) -> Vec { - Vec::new() - } -+ pub(super) fn mod_hd(&self) -> Option { -+ None -+ } -+ pub(super) fn mod_lights(&self, _camera: glam::DVec3, _alpha: f64) -> Vec { -+ Vec::new() -+ } - } - - #[cfg(feature = "mods")] -@@ -116,6 +124,13 @@ impl Game<'_> { - let seen: std::collections::HashSet<_> = seen.into_iter().collect(); - models.poses.retain(|k, _| seen.contains(k)); - } -+ let mut lights: HashMap<(usize, u32), (PointLight, PointLight)> = HashMap::new(); -+ for (owner, l) in self.mods.mods.lights() { -+ let key = (owner, l.id); -+ let before = self.mods.models.lights.get(&key).map_or(*l, |(_, now)| *now); -+ lights.insert(key, (before, *l)); -+ } -+ self.mods.models.lights = lights; - let views: Vec = self.mods.mods.views().map(|(_, v)| *v).collect(); - let old = std::mem::take(&mut self.mods.models.views); - self.mods.models.views = views -@@ -127,6 +142,40 @@ impl Game<'_> { - .collect(); - } - -+ /// The HD lighting a mod asked for, if any. -+ pub(super) fn mod_hd(&self) -> Option { -+ let l = self.mods.mods.lighting()?; -+ l.hd.then_some(horizon_client::gfx::HdSettings { exposure: l.exposure, bloom: l.bloom }) -+ } -+ -+ /// The mods' lights between their last two settings, relative to the camera; the nearest and -+ /// brightest first when there are too many. -+ pub(super) fn mod_lights(&self, camera: DVec3, alpha: f64) -> Vec { -+ let t = alpha.clamp(0.0, 1.0); -+ let mut out: Vec<(f32, horizon_client::gfx::HdLight)> = self -+ .mods -+ .models -+ .lights -+ .values() -+ .filter_map(|(a, b)| { -+ let at = DVec3::new(a.x, a.y, a.z).lerp(DVec3::new(b.x, b.y, b.z), t) - camera; -+ let radius = b.radius; -+ let d = at.length() as f32; -+ // Too far to light anything in sight. -+ if d > radius + 512.0 { -+ return None; -+ } -+ let light = horizon_client::gfx::HdLight { -+ pos: [at.x as f32, at.y as f32, at.z as f32, radius], -+ color: [b.color.0, b.color.1, b.color.2, a.intensity + (b.intensity - a.intensity) * t as f32], -+ }; -+ Some((light.color[3] / (1.0 + d * d * 0.001), light)) -+ }) -+ .collect(); -+ out.sort_by(|x, y| y.0.total_cmp(&x.0)); -+ out.into_iter().take(512).map(|(_, l)| l).collect() -+ } -+ - /// The mods' cameras between their last two settings, for this frame. - pub(super) fn mod_views(&self, alpha: f64) -> Vec { - let t = alpha.clamp(0.0, 1.0); -@@ -198,7 +247,14 @@ impl Game<'_> { - rotation, - (at - camera).as_vec3(), - ) * Mat4::from_translation(Vec3::splat(-0.5)); -+ let start = out.len(); - self.block_model_vertices(p.state, 0, m, light, true, out); -+ // Overlay 40 marks a glowing part for the HD lighting (entity.wgsl): drawn as any other. -+ if p.glow { -+ for v in &mut out[start..] { -+ v.layer = 40; -+ } -+ } - } - } - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index 9772e3a83..be51a0289 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -77,8 +77,10 @@ mod lod; - mod native_material; - mod native_pack; - mod native_post; -+mod hd; - mod mod_post; - mod mod_views; -+pub use hd::{HdLight, HdSettings}; - pub use mod_views::ModView; - mod native_sky; - mod occlusion; -@@ -218,6 +220,9 @@ pub struct Frame<'a> { - pub camera: DVec3, - /// Mods' cameras (AirDef fork), drawn into the pictures the HUD shows. - pub mod_views: &'a [ModView], -+ /// The HD lighting (AirDef fork): on with its settings, and the mods' lights. -+ pub hd: Option, -+ pub hd_lights: &'a [HdLight], - pub yaw: f32, - pub pitch: f32, - pub fov_degrees: f32, -@@ -550,7 +555,15 @@ pub struct Renderer { - native: Option, - /// AirDef fork: a mod's post pass, its parameters and the partial tick they are for. - mod_post: Option, -+ /// The mod post pass's WGSL, and whether it was built for the HD target (it is rebuilt when -+ /// the HD lighting comes or goes). -+ mod_post_source: Option, -+ mod_post_hdr: bool, - mod_views: Option, -+ /// AirDef fork: the HD lighting's G-buffer pipelines (terrain, entities) and its targets. -+ gbuffer_terrain: wgpu::RenderPipeline, -+ gbuffer_entity: wgpu::RenderPipeline, -+ hd: Option, - mod_params: (Vec, f32), - /// Time the shader pack's passes on the GPU (F3, or `HORIZON_SHADER_PACK_TIMING`). - pass_timing: bool, -@@ -1332,6 +1345,39 @@ impl Renderer { - ) - }; - let tri = wgpu::PrimitiveTopology::TriangleList; -+ // AirDef fork: the HD lighting's G-buffer (terrain.wgsl's fs_gbuffer): albedo and normal. -+ let gbuffer_targets = [ -+ Some(wgpu::ColorTargetState { format: hd::ALBEDO, blend: None, write_mask: wgpu::ColorWrites::ALL }), -+ Some(wgpu::ColorTargetState { format: hd::NORMAL, blend: None, write_mask: wgpu::ColorWrites::ALL }), -+ ]; -+ let gbuffer_depth = Some(wgpu::DepthStencilState { -+ format: wgpu::TextureFormat::Depth32Float, -+ depth_write_enabled: Some(true), -+ depth_compare: Some(wgpu::CompareFunction::GreaterEqual), -+ stencil: Default::default(), -+ bias: Default::default(), -+ }); -+ let gbuffer_terrain = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("HD G-buffer terrain"), -+ layout: Some(&layout), -+ vertex: wgpu::VertexState { -+ module: &shader, -+ entry_point: Some("vs_terrain"), -+ compilation_options: Default::default(), -+ buffers: &terrain_buffers, -+ }, -+ primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), ..Default::default() }, -+ depth_stencil: gbuffer_depth.clone(), -+ multisample: Default::default(), -+ fragment: Some(wgpu::FragmentState { -+ module: &shader, -+ entry_point: Some("fs_gbuffer"), -+ compilation_options: wgpu::PipelineCompilationOptions { constants: &sprite_constants, ..Default::default() }, -+ targets: &gbuffer_targets, -+ }), -+ multiview_mask: None, -+ cache: None, -+ }); - let solid = pipeline( - "Terrain solid", - "vs_terrain", -@@ -1928,6 +1974,28 @@ impl Renderer { - cache: None, - }) - }; -+ // AirDef fork: the entities' G-buffer (entity.wgsl's fs_entity_gbuffer). -+ let gbuffer_entity = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("HD G-buffer entities"), -+ layout: Some(&entity_layout), -+ vertex: wgpu::VertexState { -+ module: &entity_shader, -+ entry_point: Some("vs_entity"), -+ compilation_options: Default::default(), -+ buffers: &entity_vertex, -+ }, -+ primitive: Default::default(), -+ depth_stencil: gbuffer_depth.clone(), -+ multisample: Default::default(), -+ fragment: Some(wgpu::FragmentState { -+ module: &entity_shader, -+ entry_point: Some("fs_entity_gbuffer"), -+ compilation_options: Default::default(), -+ targets: &gbuffer_targets, -+ }), -+ multiview_mask: None, -+ cache: None, -+ }); - let entity_pipeline = |label, cull_mode, depth_write, compare, bias, blend, clouds| { - entity_pipeline_with( - label, -@@ -2656,7 +2724,12 @@ impl Renderer { - pack_post: None, - native: None, - mod_post: None, -+ mod_post_source: None, -+ mod_post_hdr: false, - mod_views: None, -+ gbuffer_terrain, -+ gbuffer_entity, -+ hd: None, - mod_params: (Vec::new(), 0.0), - pass_timing: pack_timing::PassTiming::logging(), - pack_entity_rest: Vec::new(), -@@ -3441,10 +3514,15 @@ impl Renderer { - /// that ran keeps running. - pub fn set_mod_post(&mut self, source: Option<&str>) -> std::result::Result<(), String> { - match source { -- None => self.mod_post = None, -+ None => { -+ self.mod_post = None; -+ self.mod_post_source = None; -+ } - Some(source) => { -- let format = self.config.format.remove_srgb_suffix(); -+ // Under the HD lighting it draws into the HD target, else onto the screen. -+ let format = if self.mod_post_hdr { hd::HDR } else { self.config.format.remove_srgb_suffix() }; - self.mod_post = Some(mod_post::ModPost::new(&self.device, source, format)?); -+ self.mod_post_source = Some(source.to_owned()); - } - } - Ok(()) -@@ -4694,7 +4772,15 @@ impl Renderer { - } - // AirDef fork: a mod's post pass reads the world target too. - let mod_post = !frame.title && !pack_post && !native_post && self.mod_post.is_some(); -- let pack_reads_world = pack_post || native_post || mod_post; -+ // AirDef fork: the HD lighting (its own pipeline; a shader pack's replaces it). -+ let hd_on = !frame.title && !pack_post && !native_post && frame.hd.is_some(); -+ if hd_on != self.mod_post_hdr { -+ self.mod_post_hdr = hd_on; -+ if let Some(source) = self.mod_post_source.clone() { -+ let _ = self.set_mod_post(Some(&source)); -+ } -+ } -+ let pack_reads_world = pack_post || native_post || mod_post || hd_on; - // The pack's program reads the vertices this frame's draws bind. - let pack_draws_terrain = pack_terrain && flat_frame == self.pack_terrain_flat; - // The shadow camera's lists, drawn by the pack's shadow programs. -@@ -4744,6 +4830,47 @@ impl Renderer { - Some((color, depth)) => (color, depth), - None => (&view_target, &self.depth), - }; -+ // AirDef fork: the HD lighting's G-buffer — the solid terrain, distant tiles and -+ // entities once more, their colour without light and their normals. -+ if hd_on && let Some(size) = self.world_post.size() { -+ let format = self.config.format.remove_srgb_suffix(); -+ let device = &self.device; -+ let hd = self.hd.get_or_insert_with(|| hd::Hd::new(device, size, format)); -+ hd.resize(device, size); -+ if let Some(settings) = frame.hd { -+ hd.write(&self.queue, view_proj, frame.hd_lights, settings); -+ } -+ let mut pass = hd.gbuffer_pass(&mut encoder); -+ pass.set_bind_group(0, &self.group, &[]); -+ pass.set_pipeline(&self.gbuffer_terrain); -+ pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32); -+ pass.set_vertex_buffer(1, self.instances.slice(..)); -+ let mut page = None; -+ let sections = visible.iter().enumerate().map(|(k, v)| (k, self.sections.get(v.slot))); -+ let tiles = visible_lods -+ .iter() -+ .enumerate() -+ .filter_map(|(k, v)| Some((visible.len() + k, &self.lods.get(&v.key)?.mesh))); -+ for (instance, s) in sections.chain(tiles) { -+ if s.solid == 0 { -+ continue; -+ } -+ if page != Some(s.page) { -+ pass.set_vertex_buffer(0, self.arena.buffer(s.page).slice(..)); -+ page = Some(s.page); -+ } -+ let instance = instance as u32; -+ pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); -+ } -+ if entity_quads > 0 { -+ pass.set_pipeline(&self.gbuffer_entity); -+ pass.set_bind_group(0, &self.entity_group, &[]); -+ pass.set_vertex_buffer(0, self.entity_buffer.slice(..)); -+ pass.draw_indexed(0..entity_quads * 6, 0, 0..1); -+ } -+ } else if !hd_on { -+ self.hd = None; -+ } - if culling && let Some(cull) = &mut self.cull { - cull.bind_depth(&self.device, world_depth); - } -@@ -5264,6 +5391,22 @@ impl Renderer { - size, - native.timing.as_mut(), - ); -+ } else if let Some((color, depth)) = &scaled -+ && hd_on -+ && let Some(hd) = &self.hd -+ { -+ // AirDef fork: lit by the mods' lights into the HD target, the mod's post pass on -+ // that, then bloom and the tone curve onto the screen. -+ hd.light(&self.device, &mut encoder, (color, depth)); -+ match &mut self.mod_post { -+ Some(post) if mod_post => { -+ let size = self.world_post.size().unwrap_or([1, 1]); -+ post.write(&self.queue, frame, size, &self.mod_params.0, self.mod_params.1); -+ post.run(&self.device, &mut encoder, (&hd.lit, depth), &hd.fx); -+ hd.finish(&self.device, &mut encoder, &hd.fx, &view_target); -+ } -+ _ => hd.finish(&self.device, &mut encoder, &hd.lit, &view_target), -+ } - } else if let Some((color, depth)) = &scaled - && mod_post - && let Some(post) = &mut self.mod_post -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -new file mode 100644 -index 000000000..b40484e5e ---- /dev/null -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -0,0 +1,520 @@ -+//! AirDef fork: HD lighting. Before the world pass the solid terrain and the -+//! entities (with the mods' models) are drawn once more into a G-buffer: the -+//! surface colour without light and its normal. After the world pass the -+//! mods' point lights (rocket flames, flares, blasts) light every pixel of it -+//! into a high-dynamic-range target; the mod's post pass runs on that; then -+//! bright parts bloom and a tone curve brings it to the screen — vanilla's -+//! range (up to about 0.8) left as it is, only what is brighter rolled off. -+use glam::Mat4; -+ -+/// The G-buffer's formats. -+pub const ALBEDO: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; -+pub const NORMAL: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; -+/// The lit world and the post pass's output. -+pub const HDR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; -+/// The most lights drawn a frame. -+pub const MAX_LIGHTS: usize = 512; -+/// Bloom's mip levels. -+const BLOOM_LEVELS: usize = 6; -+ -+/// A light for this frame, relative to the camera. -+#[repr(C)] -+#[derive(Clone, Copy, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)] -+pub struct HdLight { -+ /// xyz: camera-relative position; w: radius. -+ pub pos: [f32; 4], -+ /// rgb: colour; w: intensity. -+ pub color: [f32; 4], -+} -+ -+/// The frame's HD settings. -+#[derive(Clone, Copy, Debug)] -+pub struct HdSettings { -+ pub exposure: f32, -+ pub bloom: f32, -+} -+ -+#[repr(C)] -+#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -+struct HdFrame { -+ /// Clip space to camera-relative position. -+ inverse_view_proj: [[f32; 4]; 4], -+ /// width, height, 1/width, 1/height. -+ resolution: [f32; 4], -+ /// lights, exposure, bloom, -. -+ params: [f32; 4], -+} -+ -+const COMMON: &str = r#" -+struct HdFrame { -+ inverse_view_proj: mat4x4, -+ resolution: vec4, -+ params: vec4, -+} -+struct FullOut { -+ @builtin(position) position: vec4, -+ @location(0) uv: vec2, -+} -+@vertex -+fn vs_full(@builtin(vertex_index) i: u32) -> FullOut { -+ let uv = vec2(f32((i << 1u) & 2u), f32(i & 2u)); -+ var out: FullOut; -+ out.position = vec4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0); -+ out.uv = uv; -+ return out; -+} -+"#; -+ -+const LIGHT: &str = r#" -+struct Light { -+ pos: vec4, -+ color: vec4, -+} -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var scene_color: texture_2d; -+@group(0) @binding(2) var scene_depth: texture_depth_2d; -+@group(0) @binding(3) var albedo_tex: texture_2d; -+@group(0) @binding(4) var normal_tex: texture_2d; -+@group(0) @binding(5) var gbuffer_depth: texture_depth_2d; -+@group(0) @binding(6) var lights: array; -+ -+@fragment -+fn fs_light(in: FullOut) -> @location(0) vec4 { -+ let px = vec2(in.position.xy); -+ var c = textureLoad(scene_color, px, 0).rgb; -+ let d = textureLoad(scene_depth, px, 0); -+ let g = textureLoad(gbuffer_depth, px, 0); -+ // Only where the G-buffer holds what the world shows (not the sky, water, particles). -+ if d <= 0.0 || abs(d - g) > max(d, g) * 0.002 { -+ return vec4(c, 1.0); -+ } -+ let ndc = vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, d, 1.0); -+ let h = frame.inverse_view_proj * ndc; -+ let p = h.xyz / h.w; -+ let n = normalize(textureLoad(normal_tex, px, 0).xyz); -+ let albedo = textureLoad(albedo_tex, px, 0); -+ var light = vec3(0.0); -+ let count = u32(frame.params.x); -+ for (var i = 0u; i < count; i++) { -+ let l = lights[i]; -+ let to = l.pos.xyz - p; -+ let dist = length(to); -+ if dist >= l.pos.w { continue; } -+ // Inverse square, softened near the light, faded to nothing at its radius. -+ let window = pow(clamp(1.0 - pow(dist / l.pos.w, 4.0), 0.0, 1.0), 2.0); -+ let falloff = l.color.w / (dist * dist + 1.0) * window; -+ let ndl = max(dot(n, to / max(dist, 0.001)) * 0.85 + 0.15, 0.0); -+ light += l.color.rgb * falloff * ndl; -+ } -+ c += albedo.rgb * light; -+ // Glowing parts (flames, flares) shine past white, for the bloom. -+ c += albedo.rgb * albedo.a * 1.5; -+ return vec4(c, 1.0); -+} -+"#; -+ -+const BLOOM: &str = r#" -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var source: texture_2d; -+@group(0) @binding(2) var linear: sampler; -+ -+// The first step down keeps only what is brighter than white (a soft knee). -+@fragment -+fn fs_prefilter(in: FullOut) -> @location(0) vec4 { -+ let c = textureSampleLevel(source, linear, in.uv, 0.0).rgb; -+ let peak = max(c.r, max(c.g, c.b)); -+ let knee = 0.35; -+ let soft = clamp(peak - 0.85 + knee, 0.0, 2.0 * knee); -+ let weight = max(soft * soft / (4.0 * knee + 0.0001), peak - 0.85) / max(peak, 0.0001); -+ return vec4(c * weight, 1.0); -+} -+// 13 taps (Call of Duty: Advanced Warfare's bloom), half size each step. -+@fragment -+fn fs_down(in: FullOut) -> @location(0) vec4 { -+ let t = 1.0 / vec2(textureDimensions(source)); -+ let s = in.uv; -+ let a = textureSampleLevel(source, linear, s + t * vec2(-2.0, -2.0), 0.0).rgb; -+ let b = textureSampleLevel(source, linear, s + t * vec2(0.0, -2.0), 0.0).rgb; -+ let c = textureSampleLevel(source, linear, s + t * vec2(2.0, -2.0), 0.0).rgb; -+ let d = textureSampleLevel(source, linear, s + t * vec2(-2.0, 0.0), 0.0).rgb; -+ let e = textureSampleLevel(source, linear, s, 0.0).rgb; -+ let f = textureSampleLevel(source, linear, s + t * vec2(2.0, 0.0), 0.0).rgb; -+ let g = textureSampleLevel(source, linear, s + t * vec2(-2.0, 2.0), 0.0).rgb; -+ let h = textureSampleLevel(source, linear, s + t * vec2(0.0, 2.0), 0.0).rgb; -+ let i = textureSampleLevel(source, linear, s + t * vec2(2.0, 2.0), 0.0).rgb; -+ let j = textureSampleLevel(source, linear, s + t * vec2(-1.0, -1.0), 0.0).rgb; -+ let k = textureSampleLevel(source, linear, s + t * vec2(1.0, -1.0), 0.0).rgb; -+ let l = textureSampleLevel(source, linear, s + t * vec2(-1.0, 1.0), 0.0).rgb; -+ let m = textureSampleLevel(source, linear, s + t * vec2(1.0, 1.0), 0.0).rgb; -+ var o = e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125; -+ return vec4(o, 1.0); -+} -+// A 3×3 tent, added onto the bigger level. -+@fragment -+fn fs_up(in: FullOut) -> @location(0) vec4 { -+ let t = 1.0 / vec2(textureDimensions(source)); -+ let s = in.uv; -+ var o = textureSampleLevel(source, linear, s, 0.0).rgb * 4.0; -+ o += (textureSampleLevel(source, linear, s + t * vec2(-1.0, 0.0), 0.0).rgb -+ + textureSampleLevel(source, linear, s + t * vec2(1.0, 0.0), 0.0).rgb -+ + textureSampleLevel(source, linear, s + t * vec2(0.0, -1.0), 0.0).rgb -+ + textureSampleLevel(source, linear, s + t * vec2(0.0, 1.0), 0.0).rgb) * 2.0; -+ o += textureSampleLevel(source, linear, s + t * vec2(-1.0, -1.0), 0.0).rgb -+ + textureSampleLevel(source, linear, s + t * vec2(1.0, -1.0), 0.0).rgb -+ + textureSampleLevel(source, linear, s + t * vec2(-1.0, 1.0), 0.0).rgb -+ + textureSampleLevel(source, linear, s + t * vec2(1.0, 1.0), 0.0).rgb; -+ return vec4(o / 16.0, 1.0); -+} -+"#; -+ -+const TONEMAP: &str = r#" -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var hdr: texture_2d; -+@group(0) @binding(2) var bloom: texture_2d; -+@group(0) @binding(3) var linear: sampler; -+ -+@fragment -+fn fs_tonemap(in: FullOut) -> @location(0) vec4 { -+ var c = textureSampleLevel(hdr, linear, in.uv, 0.0).rgb * frame.params.y; -+ c += textureSampleLevel(bloom, linear, in.uv, 0.0).rgb * frame.params.z; -+ // Up to 0.8 as it is (vanilla's look); above it, rolled off towards white by the -+ // brightest channel, so hues hold instead of clipping. -+ let peak = max(c.r, max(c.g, c.b)); -+ let t = 0.8; -+ if peak > t { -+ let x = (peak - t) / (1.0 - t); -+ let rolled = t + (1.0 - t) * (x / (1.0 + x)); -+ c = c * (rolled / peak); -+ // Very bright light bleaches towards white, as film does. -+ c = mix(c, vec3(rolled), clamp((peak - 2.0) / 8.0, 0.0, 0.6)); -+ } -+ return vec4(c, 1.0); -+} -+"#; -+ -+struct Target { -+ view: wgpu::TextureView, -+} -+ -+fn target(device: &wgpu::Device, label: &str, size: [u32; 2], format: wgpu::TextureFormat) -> Target { -+ let view = device -+ .create_texture(&wgpu::TextureDescriptor { -+ label: Some(label), -+ size: wgpu::Extent3d { width: size[0].max(1), height: size[1].max(1), depth_or_array_layers: 1 }, -+ mip_level_count: 1, -+ sample_count: 1, -+ dimension: wgpu::TextureDimension::D2, -+ format, -+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, -+ view_formats: &[], -+ }) -+ .create_view(&Default::default()); -+ Target { view } -+} -+ -+pub struct Hd { -+ size: [u32; 2], -+ pub albedo: wgpu::TextureView, -+ pub normal: wgpu::TextureView, -+ pub depth: wgpu::TextureView, -+ /// The lit world, and the post pass's output. -+ pub lit: wgpu::TextureView, -+ pub fx: wgpu::TextureView, -+ bloom: Vec, -+ frame: wgpu::Buffer, -+ lights: wgpu::Buffer, -+ sampler: wgpu::Sampler, -+ light_layout: wgpu::BindGroupLayout, -+ light_pipeline: wgpu::RenderPipeline, -+ bloom_layout: wgpu::BindGroupLayout, -+ prefilter: wgpu::RenderPipeline, -+ down: wgpu::RenderPipeline, -+ up: wgpu::RenderPipeline, -+ tone_layout: wgpu::BindGroupLayout, -+ tone: wgpu::RenderPipeline, -+} -+ -+fn module(device: &wgpu::Device, label: &str, body: &str) -> wgpu::ShaderModule { -+ device.create_shader_module(wgpu::ShaderModuleDescriptor { -+ label: Some(label), -+ source: wgpu::ShaderSource::Wgsl(format!("{COMMON}{body}").into()), -+ }) -+} -+ -+fn fullscreen( -+ device: &wgpu::Device, -+ label: &str, -+ layout: &wgpu::BindGroupLayout, -+ module: &wgpu::ShaderModule, -+ entry: &str, -+ format: wgpu::TextureFormat, -+ blend: Option, -+) -> wgpu::RenderPipeline { -+ let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { -+ label: Some(label), -+ bind_group_layouts: &[Some(layout)], -+ ..Default::default() -+ }); -+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some(label), -+ layout: Some(&pipeline_layout), -+ vertex: wgpu::VertexState { module, entry_point: Some("vs_full"), compilation_options: Default::default(), buffers: &[] }, -+ fragment: Some(wgpu::FragmentState { -+ module, -+ entry_point: Some(entry), -+ compilation_options: Default::default(), -+ targets: &[Some(wgpu::ColorTargetState { format, blend, write_mask: wgpu::ColorWrites::ALL })], -+ }), -+ primitive: Default::default(), -+ depth_stencil: None, -+ multisample: Default::default(), -+ multiview_mask: None, -+ cache: None, -+ }) -+} -+ -+fn entries(kinds: &[wgpu::BindingType]) -> Vec { -+ kinds -+ .iter() -+ .enumerate() -+ .map(|(i, ty)| wgpu::BindGroupLayoutEntry { -+ binding: i as u32, -+ visibility: wgpu::ShaderStages::FRAGMENT, -+ ty: *ty, -+ count: None, -+ }) -+ .collect() -+} -+ -+const UNIFORM: wgpu::BindingType = -+ wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }; -+const FLOAT: wgpu::BindingType = wgpu::BindingType::Texture { -+ sample_type: wgpu::TextureSampleType::Float { filterable: true }, -+ view_dimension: wgpu::TextureViewDimension::D2, -+ multisampled: false, -+}; -+const UNFILTERED: wgpu::BindingType = wgpu::BindingType::Texture { -+ sample_type: wgpu::TextureSampleType::Float { filterable: false }, -+ view_dimension: wgpu::TextureViewDimension::D2, -+ multisampled: false, -+}; -+const DEPTH: wgpu::BindingType = wgpu::BindingType::Texture { -+ sample_type: wgpu::TextureSampleType::Depth, -+ view_dimension: wgpu::TextureViewDimension::D2, -+ multisampled: false, -+}; -+const SAMPLER: wgpu::BindingType = wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering); -+ -+impl Hd { -+ pub fn new(device: &wgpu::Device, size: [u32; 2], screen: wgpu::TextureFormat) -> Self { -+ let light_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD lighting"), -+ entries: &entries(&[ -+ UNIFORM, -+ UNFILTERED, -+ DEPTH, -+ UNFILTERED, -+ UNFILTERED, -+ DEPTH, -+ wgpu::BindingType::Buffer { -+ ty: wgpu::BufferBindingType::Storage { read_only: true }, -+ has_dynamic_offset: false, -+ min_binding_size: None, -+ }, -+ ]), -+ }); -+ let bloom_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD bloom"), -+ entries: &entries(&[UNIFORM, FLOAT, SAMPLER]), -+ }); -+ let tone_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD tone mapping"), -+ entries: &entries(&[UNIFORM, FLOAT, FLOAT, SAMPLER]), -+ }); -+ let light = module(device, "HD lighting", LIGHT); -+ let bloom = module(device, "HD bloom", BLOOM); -+ let tone = module(device, "HD tone mapping", TONEMAP); -+ let add = wgpu::BlendState { -+ color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add }, -+ alpha: wgpu::BlendComponent::REPLACE, -+ }; -+ let light_pipeline = fullscreen(device, "HD lighting", &light_layout, &light, "fs_light", HDR, None); -+ let prefilter = fullscreen(device, "HD bloom prefilter", &bloom_layout, &bloom, "fs_prefilter", HDR, None); -+ let down = fullscreen(device, "HD bloom down", &bloom_layout, &bloom, "fs_down", HDR, None); -+ let up = fullscreen(device, "HD bloom up", &bloom_layout, &bloom, "fs_up", HDR, Some(add)); -+ let tone = fullscreen(device, "HD tone mapping", &tone_layout, &tone, "fs_tonemap", screen, None); -+ let frame = device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some("HD frame"), -+ size: std::mem::size_of::() as u64, -+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }); -+ let lights = device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some("HD lights"), -+ size: (MAX_LIGHTS * std::mem::size_of::()) as u64, -+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }); -+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor { -+ label: Some("HD"), -+ mag_filter: wgpu::FilterMode::Linear, -+ min_filter: wgpu::FilterMode::Linear, -+ address_mode_u: wgpu::AddressMode::ClampToEdge, -+ address_mode_v: wgpu::AddressMode::ClampToEdge, -+ ..Default::default() -+ }); -+ let mut hd = Hd { -+ size: [0, 0], -+ albedo: target(device, "HD albedo", [1, 1], ALBEDO).view, -+ normal: target(device, "HD normal", [1, 1], NORMAL).view, -+ depth: target(device, "HD depth", [1, 1], wgpu::TextureFormat::Depth32Float).view, -+ lit: target(device, "HD lit", [1, 1], HDR).view, -+ fx: target(device, "HD post", [1, 1], HDR).view, -+ bloom: Vec::new(), -+ frame, -+ lights, -+ sampler, -+ light_layout, -+ light_pipeline, -+ bloom_layout, -+ prefilter, -+ down, -+ up, -+ tone_layout, -+ tone, -+ }; -+ hd.resize(device, size); -+ hd -+ } -+ -+ /// The targets, at the world target's size. -+ pub fn resize(&mut self, device: &wgpu::Device, size: [u32; 2]) { -+ if self.size == size { -+ return; -+ } -+ self.size = size; -+ self.albedo = target(device, "HD albedo", size, ALBEDO).view; -+ self.normal = target(device, "HD normal", size, NORMAL).view; -+ self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; -+ self.lit = target(device, "HD lit", size, HDR).view; -+ self.fx = target(device, "HD post", size, HDR).view; -+ self.bloom = (0..BLOOM_LEVELS) -+ .map(|k| { -+ let s = [(size[0] >> (k + 1)).max(1), (size[1] >> (k + 1)).max(1)]; -+ target(device, "HD bloom", s, HDR) -+ }) -+ .collect(); -+ } -+ -+ /// This frame's camera, lights and settings. -+ pub fn write(&mut self, queue: &wgpu::Queue, view_proj: Mat4, lights: &[HdLight], settings: HdSettings) { -+ let n = lights.len().min(MAX_LIGHTS); -+ if n > 0 { -+ queue.write_buffer(&self.lights, 0, bytemuck::cast_slice(&lights[..n])); -+ } -+ let frame = HdFrame { -+ inverse_view_proj: view_proj.inverse().to_cols_array_2d(), -+ resolution: [self.size[0] as f32, self.size[1] as f32, 1.0 / self.size[0] as f32, 1.0 / self.size[1] as f32], -+ params: [n as f32, settings.exposure, settings.bloom, 0.0], -+ }; -+ queue.write_buffer(&self.frame, 0, bytemuck::bytes_of(&frame)); -+ } -+ -+ /// The G-buffer pass's attachments: albedo and normal cleared, its own depth. -+ pub fn gbuffer_pass<'e>(&self, encoder: &'e mut wgpu::CommandEncoder) -> wgpu::RenderPass<'e> { -+ let clear = |view| { -+ Some(wgpu::RenderPassColorAttachment { -+ view, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store }, -+ }) -+ }; -+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("HD G-buffer"), -+ color_attachments: &[clear(&self.albedo), clear(&self.normal)], -+ depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { -+ view: &self.depth, -+ depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(0.0), store: wgpu::StoreOp::Store }), -+ stencil_ops: None, -+ }), -+ ..Default::default() -+ }) -+ .forget_lifetime() -+ } -+ -+ fn draw(encoder: &mut wgpu::CommandEncoder, label: &str, to: &wgpu::TextureView, load: bool, pipeline: &wgpu::RenderPipeline, group: &wgpu::BindGroup) { -+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some(label), -+ color_attachments: &[Some(wgpu::RenderPassColorAttachment { -+ view: to, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { -+ load: if load { wgpu::LoadOp::Load } else { wgpu::LoadOp::Clear(wgpu::Color::BLACK) }, -+ store: wgpu::StoreOp::Store, -+ }, -+ })], -+ ..Default::default() -+ }); -+ pass.set_pipeline(pipeline); -+ pass.set_bind_group(0, group, &[]); -+ pass.draw(0..3, 0..1); -+ } -+ -+ /// Lights the world (its colour and depth) into `lit`. -+ pub fn light(&self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, world: (&wgpu::TextureView, &wgpu::TextureView)) { -+ let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("HD lighting"), -+ layout: &self.light_layout, -+ entries: &[ -+ wgpu::BindGroupEntry { binding: 0, resource: self.frame.as_entire_binding() }, -+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(world.0) }, -+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(world.1) }, -+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.albedo) }, -+ wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.normal) }, -+ wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.depth) }, -+ wgpu::BindGroupEntry { binding: 6, resource: self.lights.as_entire_binding() }, -+ ], -+ }); -+ Self::draw(encoder, "HD lighting", &self.lit, false, &self.light_pipeline, &group); -+ } -+ -+ fn bloom_group(&self, device: &wgpu::Device, source: &wgpu::TextureView) -> wgpu::BindGroup { -+ device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("HD bloom"), -+ layout: &self.bloom_layout, -+ entries: &[ -+ wgpu::BindGroupEntry { binding: 0, resource: self.frame.as_entire_binding() }, -+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(source) }, -+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.sampler) }, -+ ], -+ }) -+ } -+ -+ /// Bloom from `source` (the lit world or the post pass's output), then the tone curve onto `screen`. -+ pub fn finish(&self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, source: &wgpu::TextureView, screen: &wgpu::TextureView) { -+ // Down: the prefilter into the first level, then each level from the one above. -+ Self::draw(encoder, "HD bloom prefilter", &self.bloom[0].view, false, &self.prefilter, &self.bloom_group(device, source)); -+ for k in 1..self.bloom.len() { -+ let g = self.bloom_group(device, &self.bloom[k - 1].view); -+ Self::draw(encoder, "HD bloom down", &self.bloom[k].view, false, &self.down, &g); -+ } -+ // Up: each level added onto the bigger one. -+ for k in (1..self.bloom.len()).rev() { -+ let g = self.bloom_group(device, &self.bloom[k].view); -+ Self::draw(encoder, "HD bloom up", &self.bloom[k - 1].view, true, &self.up, &g); -+ } -+ let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("HD tone mapping"), -+ layout: &self.tone_layout, -+ entries: &[ -+ wgpu::BindGroupEntry { binding: 0, resource: self.frame.as_entire_binding() }, -+ wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(source) }, -+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.bloom[0].view) }, -+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self.sampler) }, -+ ], -+ }); -+ Self::draw(encoder, "HD tone mapping", screen, false, &self.tone, &group); -+ } -+} -diff --git a/crates/horizon-client/src/terrain.wgsl b/crates/horizon-client/src/terrain.wgsl -index 3aea6fc1a..293729e33 100644 ---- a/crates/horizon-client/src/terrain.wgsl -+++ b/crates/horizon-client/src/terrain.wgsl -@@ -71,6 +71,9 @@ struct TerrainOut { - @location(4) cylindrical: f32, - // The light the face emits, 0 to 15 (mesher.rs's `EMISSION_BITS`). - @location(5) @interpolate(flat) emission: u32, -+ // AirDef fork, HD lighting's G-buffer: the camera-relative position and the unlit colour. -+ @location(6) pos: vec3, -+ @location(7) tint: vec4, - } - // Horizon Extras' dynamic lights (dynamic_light.rs): the block light, in the vertex's units - // (16 per level), a vertex at camera-relative `pos` gets from the frame's light sources, one -@@ -127,6 +130,8 @@ fn terrain_vertex(position: vec3, uv: vec2, layer: u32, color: vec4 f32 { -@@ -148,6 +153,26 @@ fn apply_fog(color: vec4, spherical: f32, cylindrical: f32) -> vec4 { - let emissive = select(1.0, 0.0, in.emission > 0u); - return apply_fog(vec4(color.rgb, emissive), in.spherical, in.cylindrical); - } -+// AirDef fork: the HD lighting's G-buffer. The surface's colour without light (alpha: how much -+// it glows) and its normal, found from how the position changes across the pixel (faces are -+// flat, so this is exact). -+struct GBufferOut { -+ @location(0) albedo: vec4, -+ @location(1) normal: vec4, -+} -+fn face_normal(pos: vec3) -> vec3 { -+ var n = normalize(cross(dpdx(pos), dpdy(pos))); -+ if dot(n, pos) > 0.0 { n = -n; } -+ return n; -+} -+@fragment fn fs_gbuffer(in: TerrainOut) -> GBufferOut { -+ let color = sprite(in.uv, in.layer & 0x7fffu) * in.tint; -+ if color.a < 0.5 && (in.layer & 0x8000u) == 0u { discard; } -+ var out: GBufferOut; -+ out.albedo = vec4(color.rgb, f32(in.emission) / 15.0); -+ out.normal = vec4(face_normal(in.pos), 1.0); -+ return out; -+} - @fragment fn fs_translucent(in: TerrainOut) -> @location(0) vec4 { - let color = sprite(in.uv, in.layer & 0x7fffu) * in.color; - if color.a < 0.01 { discard; } -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index b6758d9fb..6696fa134 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -534,6 +534,34 @@ interface render { - /// Replaces the mod's cameras: up to 8. A `view` shape shows one; the - /// game renders up to three a frame, in turn. - set-views: func(views: list); -+ /// A light the mod's things give off (AirDef fork): rocket flames, -+ /// flares, blasts. Drawn by the HD lighting (`set-lighting`). -+ record point-light { -+ /// Stays the same while the light lives: the game moves it smoothly. -+ id: u32, -+ x: f64, -+ y: f64, -+ z: f64, -+ /// Linear RGB, 0 to 1. -+ color: tuple, -+ /// Brightness: about what it lights one block away (0 to 100000). -+ intensity: f32, -+ /// Nothing past this many blocks (1 to 256). -+ radius: f32, -+ } -+ /// Replaces the mod's lights: up to 512, interpolated per id between -+ /// the last two calls. -+ set-lights: func(lights: list); -+ /// The HD lighting (AirDef fork): the world lit per pixel by the mods' -+ /// lights, in high dynamic range, with bloom and tone mapping. -+ record lighting { -+ hd: bool, -+ /// 1 as is; more brightens. -+ exposure: f32, -+ /// How much bright things glow (0 none, 1 strong). -+ bloom: f32, -+ } -+ set-lighting: func(lighting: lighting); - } - - /// What the game calls. Only the hooks `init` asks for are ever called. -diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs -index cf2993186..80ac74b6f 100644 ---- a/crates/horizon-mods/src/api_level.rs -+++ b/crates/horizon-mods/src/api_level.rs -@@ -7,13 +7,13 @@ - //! version's level so the launcher can tell before it installs. - - /// The newest level this Horizon serves. --pub const LEVEL: u32 = 9; -+pub const LEVEL: u32 = 10; - - /// Every function the game offers mods, as `interface.function`, with its - /// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new - /// function takes the next level and raises [`LEVEL`] (the `api_reference` - /// test fails until it is listed here). --pub const FUNCTIONS: [(&str, u32); 56] = [ -+pub const FUNCTIONS: [(&str, u32); 58] = [ - ("log.write", 0), - ("game.get-player", 0), - ("game.get-clock", 0), -@@ -71,6 +71,8 @@ pub const FUNCTIONS: [(&str, u32); 56] = [ - ("render.set-params", 8), - ("render.set-views", 9), - ("game.world-id", 9), -+ ("render.set-lights", 10), -+ ("render.set-lighting", 10), - ]; - - /// What the game's program carries so the launcher can read its level -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index abed5b31f..57713e228 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -81,7 +81,7 @@ pub use render_bindings::horizon::mods::input::{Event as InputEvent, HeldItem, I - pub use render_bindings::horizon::mods::render::{ - Corner as RenderCorner, FarTerrain as ModFarTerrain, FillShape, FrameRate, ItemShape, - Minimap as ModMinimap, ModelInstance, ParticleSpawn, Point as RenderPoint, QuadShape, -- CameraView, Rect as RenderRect, Shape, SpriteShape, TextShape, ViewShape, Zoom as ModZoom, -+ CameraView, Lighting as ModLighting, PointLight, Rect as RenderRect, Shape, SpriteShape, TextShape, ViewShape, Zoom as ModZoom, - }; - use render_bindings::{RenderHost, horizon::mods::render}; - /// Capability `recipes`'s imports (docs/mods.md): the game's recipes and items. -@@ -439,6 +439,9 @@ struct State { - post_params: Vec, - /// `render.set-views`' cameras (AirDef fork). - views: Vec, -+ /// `render.set-lights` and `render.set-lighting` (AirDef fork). -+ lights: Vec, -+ lighting: Option, - input: InputState, - browser: BrowserState, - /// Capability `storage`'s folder; made on first `set`. -@@ -648,6 +651,8 @@ impl Mods { - post: None, - post_params: Vec::new(), - views: Vec::new(), -+ lights: Vec::new(), -+ lighting: None, - input: InputState::default(), - browser: BrowserState::default(), - data_dir, -@@ -1219,6 +1224,18 @@ impl Mods { - .flat_map(move |r| r.store.data().views.iter().map(move |v| (i, v))) - }) - } -+ /// Every running mod's lights (AirDef fork), with the mod's index. -+ pub fn lights(&self) -> impl Iterator { -+ self.entries.iter().enumerate().flat_map(|(i, e)| { -+ e.run -+ .iter() -+ .flat_map(move |r| r.store.data().lights.iter().map(move |l| (i, l))) -+ }) -+ } -+ /// The first running mod's lighting, if it set one. -+ pub fn lighting(&self) -> Option { -+ self.entries.iter().find_map(|e| e.run.as_ref()?.store.data().lighting) -+ } - /// The columns each running mod asked the ground height of, taken. - pub fn take_ground_queries(&mut self) -> Vec<(usize, Vec<(i32, i32)>)> { - self.entries -@@ -2002,6 +2019,26 @@ impl render::Host for State { - }) - .collect(); - } -+ fn set_lights(&mut self, lights: Vec) { -+ let finite = |v: f64| v.is_finite() && v.abs() < 30_000_000.0; -+ let unit = |v: f32| if v.is_finite() { v.clamp(0.0, 1.0) } else { 0.0 }; -+ self.lights = lights -+ .into_iter() -+ .filter(|l| finite(l.x) && finite(l.y) && finite(l.z)) -+ .take(512) -+ .map(|mut l| { -+ l.color = (unit(l.color.0), unit(l.color.1), unit(l.color.2)); -+ l.intensity = if l.intensity.is_finite() { l.intensity.clamp(0.0, 100_000.0) } else { 0.0 }; -+ l.radius = if l.radius.is_finite() { l.radius.clamp(1.0, 256.0) } else { 1.0 }; -+ l -+ }) -+ .collect(); -+ } -+ fn set_lighting(&mut self, mut lighting: ModLighting) { -+ lighting.exposure = if lighting.exposure.is_finite() { lighting.exposure.clamp(0.05, 8.0) } else { 1.0 }; -+ lighting.bloom = if lighting.bloom.is_finite() { lighting.bloom.clamp(0.0, 1.0) } else { 0.0 }; -+ self.lighting = Some(lighting); -+ } - fn query_ground(&mut self, columns: Vec<(i32, i32)>) { - let room = 512usize.saturating_sub(self.ground_queries.len()); - self.ground_queries.extend(columns.into_iter().take(room)); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch b/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch deleted file mode 100644 index d9d5f87..0000000 --- a/horizon-patches/0012-HD-lighting-stage-2-sun-and-moon-shadow-cascades-the.patch +++ /dev/null @@ -1,1684 +0,0 @@ -From 9ebf0031fe9ba6b7f02afb26e4fd3a1ba4ddb0d0 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 13:59:43 +0300 -Subject: [PATCH 12/42] HD lighting stage 2: sun and moon shadow cascades, the - world relit (sun by face angle and shadow, sky and block light through the - lightmap), light shafts and halos, fog over it again, a light colour grade - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/entity.wgsl | 11 +- - crates/horizon-client/src/game.rs | 2 +- - crates/horizon-client/src/game/mod_models.rs | 40 +- - crates/horizon-client/src/game/mods.rs | 6 +- - crates/horizon-client/src/gfx.rs | 271 +++++++- - crates/horizon-client/src/gfx/hd.rs | 673 ++++++++++++++++--- - crates/horizon-client/src/gfx/mod_post.rs | 64 +- - crates/horizon-client/src/gfx/mod_views.rs | 9 +- - crates/horizon-client/src/terrain.wgsl | 10 + - 9 files changed, 957 insertions(+), 129 deletions(-) - -diff --git a/crates/horizon-client/src/entity.wgsl b/crates/horizon-client/src/entity.wgsl -index 01c64def9..61bc1f92a 100644 ---- a/crates/horizon-client/src/entity.wgsl -+++ b/crates/horizon-client/src/entity.wgsl -@@ -56,8 +56,9 @@ struct Out { - @location(11) glint_uv: vec2, - // 1 drops the quad's back-facing pixels (Vertex::normal w = 1), like a culling render type. - @location(12) @interpolate(flat) cull: u32, -- // AirDef fork, HD lighting's G-buffer: the camera-relative position. -+ // AirDef fork, HD lighting's G-buffer: the camera-relative position, sky and block light. - @location(13) pos: vec3, -+ @location(14) lm: vec2, - } - // Shipped light.glsl; normals are already transformed and RGBA8_SNORM packed. - fn entity_light(normal: vec3, nether: bool) -> f32 { -@@ -121,6 +122,7 @@ fn entity_vertex(position: vec3, uv: vec2, - out.cylindrical = select(max(length(position.xz), abs(position.y)), 0.0, overlay >= 2u && overlay <= 4u); - out.overlay = overlay; - out.pos = position; -+ out.lm = vec2(l.y, l.x) / 240.0; - out.glint_uv = vec2(0.0); - if overlay == 4u { out.spherical = length(position); } - return out; -@@ -269,6 +271,7 @@ fn crumbling_uv(uv: vec2, packed_origin: vec2, sprite_size: vec2, - struct EntityGBufferOut { - @location(0) albedo: vec4, - @location(1) normal: vec4, -+ @location(2) light: vec4, - } - @fragment fn fs_entity_gbuffer(in: Out, @builtin(front_facing) front: bool) -> EntityGBufferOut { - let color = textureSample(atlas, atlas_sampler, in.uv) * select(in.back_color, in.color, front); -@@ -280,8 +283,14 @@ struct EntityGBufferOut { - var out: EntityGBufferOut; - out.albedo = vec4(color.rgb, glow); - out.normal = vec4(n, 1.0); -+ out.light = vec4(clamp(in.lm, vec2(0.0), vec2(1.0)), 0.0, 1.0); - return out; - } -+// AirDef fork: entities in the sun's shadow map. -+@fragment fn fs_entity_shadow(in: Out) { -+ let a = textureSample(atlas, atlas_sampler, in.uv).a * in.color.a; -+ if a < 0.1 || in.overlay == 4u || in.overlay == 9u || in.overlay == 10u || in.overlay == 40u { discard; } -+} - - // rendertype_outline.fsh: the vertex colour (the team colour; outline vertices carry no - // normal, so no cardinal light) wherever the texture is not fully clear. -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index 3c97bbfb6..3a11ee283 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -112,10 +112,10 @@ mod mod_browser; - #[cfg(feature = "mods")] - mod mod_browser_recipes; - mod mod_input; -+mod mod_models; - #[cfg(feature = "mods")] - mod mod_recipes; - mod mod_render; --mod mod_models; - mod mods; - mod mount_menu; - mod mounts; -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index e6dbc7953..2d1b4780a 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -65,7 +65,11 @@ impl Game<'_> { - pub(super) fn mod_hd(&self) -> Option { - None - } -- pub(super) fn mod_lights(&self, _camera: glam::DVec3, _alpha: f64) -> Vec { -+ pub(super) fn mod_lights( -+ &self, -+ _camera: glam::DVec3, -+ _alpha: f64, -+ ) -> Vec { - Vec::new() - } - } -@@ -127,7 +131,12 @@ impl Game<'_> { - let mut lights: HashMap<(usize, u32), (PointLight, PointLight)> = HashMap::new(); - for (owner, l) in self.mods.mods.lights() { - let key = (owner, l.id); -- let before = self.mods.models.lights.get(&key).map_or(*l, |(_, now)| *now); -+ let before = self -+ .mods -+ .models -+ .lights -+ .get(&key) -+ .map_or(*l, |(_, now)| *now); - lights.insert(key, (before, *l)); - } - self.mods.models.lights = lights; -@@ -136,7 +145,10 @@ impl Game<'_> { - self.mods.models.views = views - .into_iter() - .map(|v| { -- let before = old.iter().find(|(_, o)| o.id == v.id).map_or(v, |(_, o)| *o); -+ let before = old -+ .iter() -+ .find(|(_, o)| o.id == v.id) -+ .map_or(v, |(_, o)| *o); - (before, v) - }) - .collect(); -@@ -145,12 +157,19 @@ impl Game<'_> { - /// The HD lighting a mod asked for, if any. - pub(super) fn mod_hd(&self) -> Option { - let l = self.mods.mods.lighting()?; -- l.hd.then_some(horizon_client::gfx::HdSettings { exposure: l.exposure, bloom: l.bloom }) -+ l.hd.then_some(horizon_client::gfx::HdSettings { -+ exposure: l.exposure, -+ bloom: l.bloom, -+ }) - } - - /// The mods' lights between their last two settings, relative to the camera; the nearest and - /// brightest first when there are too many. -- pub(super) fn mod_lights(&self, camera: DVec3, alpha: f64) -> Vec { -+ pub(super) fn mod_lights( -+ &self, -+ camera: DVec3, -+ alpha: f64, -+ ) -> Vec { - let t = alpha.clamp(0.0, 1.0); - let mut out: Vec<(f32, horizon_client::gfx::HdLight)> = self - .mods -@@ -167,7 +186,12 @@ impl Game<'_> { - } - let light = horizon_client::gfx::HdLight { - pos: [at.x as f32, at.y as f32, at.z as f32, radius], -- color: [b.color.0, b.color.1, b.color.2, a.intensity + (b.intensity - a.intensity) * t as f32], -+ color: [ -+ b.color.0, -+ b.color.1, -+ b.color.2, -+ a.intensity + (b.intensity - a.intensity) * t as f32, -+ ], - }; - Some((light.color[3] / (1.0 + d * d * 0.001), light)) - }) -@@ -266,7 +290,9 @@ impl Game<'_> { - None => (0, None, Vec::new()), - }; - let mut error = None; -- let Some(renderer) = self.renderer.as_mut() else { return }; -+ let Some(renderer) = self.renderer.as_mut() else { -+ return; -+ }; - if version != self.mods.models.post { - self.mods.models.post = version; - error = renderer.set_mod_post(source.as_deref()).err(); -diff --git a/crates/horizon-client/src/game/mods.rs b/crates/horizon-client/src/game/mods.rs -index ebdd18a24..a760c7763 100644 ---- a/crates/horizon-client/src/game/mods.rs -+++ b/crates/horizon-client/src/game/mods.rs -@@ -484,7 +484,11 @@ impl Game<'_> { - known_recipes, - placing: self.mod_recipe_screen(), - creative: self.creative, -- world: self.player.is_some().then(|| self.mods.world_id.clone()).flatten(), -+ world: self -+ .player -+ .is_some() -+ .then(|| self.mods.world_id.clone()) -+ .flatten(), - } - } - /// The first block along the player's view within -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index be51a0289..e351763fd 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -71,15 +71,15 @@ mod arena; - mod cull; - mod end_sky; - mod flat; -+mod hd; - mod hdr; - pub mod lightning; - mod lod; -+mod mod_post; -+mod mod_views; - mod native_material; - mod native_pack; - mod native_post; --mod hd; --mod mod_post; --mod mod_views; - pub use hd::{HdLight, HdSettings}; - pub use mod_views::ModView; - mod native_sky; -@@ -563,6 +563,10 @@ pub struct Renderer { - /// AirDef fork: the HD lighting's G-buffer pipelines (terrain, entities) and its targets. - gbuffer_terrain: wgpu::RenderPipeline, - gbuffer_entity: wgpu::RenderPipeline, -+ /// The HD sun shadows: depth only, from the light, and the sections in its farther cascade. -+ shadow_terrain: wgpu::RenderPipeline, -+ shadow_entity: wgpu::RenderPipeline, -+ hd_shadow_visible: Vec, - hd: Option, - mod_params: (Vec, f32), - /// Time the shader pack's passes on the GPU (F3, or `HORIZON_SHADER_PACK_TIMING`). -@@ -1347,9 +1351,34 @@ impl Renderer { - let tri = wgpu::PrimitiveTopology::TriangleList; - // AirDef fork: the HD lighting's G-buffer (terrain.wgsl's fs_gbuffer): albedo and normal. - let gbuffer_targets = [ -- Some(wgpu::ColorTargetState { format: hd::ALBEDO, blend: None, write_mask: wgpu::ColorWrites::ALL }), -- Some(wgpu::ColorTargetState { format: hd::NORMAL, blend: None, write_mask: wgpu::ColorWrites::ALL }), -+ Some(wgpu::ColorTargetState { -+ format: hd::ALBEDO, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ }), -+ Some(wgpu::ColorTargetState { -+ format: hd::NORMAL, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ }), -+ Some(wgpu::ColorTargetState { -+ format: hd::LIGHT_LEVELS, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ }), - ]; -+ // The shadow maps: plain depth from the light, biased against acne, both faces. -+ let shadow_depth = Some(wgpu::DepthStencilState { -+ format: wgpu::TextureFormat::Depth32Float, -+ depth_write_enabled: Some(true), -+ depth_compare: Some(wgpu::CompareFunction::Less), -+ stencil: Default::default(), -+ bias: wgpu::DepthBiasState { -+ constant: 2, -+ slope_scale: 2.0, -+ clamp: 0.0, -+ }, -+ }); - let gbuffer_depth = Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: Some(true), -@@ -1366,18 +1395,48 @@ impl Renderer { - compilation_options: Default::default(), - buffers: &terrain_buffers, - }, -- primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), ..Default::default() }, -+ primitive: wgpu::PrimitiveState { -+ cull_mode: Some(wgpu::Face::Back), -+ ..Default::default() -+ }, - depth_stencil: gbuffer_depth.clone(), - multisample: Default::default(), - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_gbuffer"), -- compilation_options: wgpu::PipelineCompilationOptions { constants: &sprite_constants, ..Default::default() }, -+ compilation_options: wgpu::PipelineCompilationOptions { -+ constants: &sprite_constants, -+ ..Default::default() -+ }, - targets: &gbuffer_targets, - }), - multiview_mask: None, - cache: None, - }); -+ let shadow_terrain = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("HD shadows terrain"), -+ layout: Some(&layout), -+ vertex: wgpu::VertexState { -+ module: &shader, -+ entry_point: Some("vs_terrain"), -+ compilation_options: Default::default(), -+ buffers: &terrain_buffers, -+ }, -+ primitive: Default::default(), -+ depth_stencil: shadow_depth.clone(), -+ multisample: Default::default(), -+ fragment: Some(wgpu::FragmentState { -+ module: &shader, -+ entry_point: Some("fs_shadow"), -+ compilation_options: wgpu::PipelineCompilationOptions { -+ constants: &sprite_constants, -+ ..Default::default() -+ }, -+ targets: &[], -+ }), -+ multiview_mask: None, -+ cache: None, -+ }); - let solid = pipeline( - "Terrain solid", - "vs_terrain", -@@ -1996,6 +2055,27 @@ impl Renderer { - multiview_mask: None, - cache: None, - }); -+ let shadow_entity = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("HD shadows entities"), -+ layout: Some(&entity_layout), -+ vertex: wgpu::VertexState { -+ module: &entity_shader, -+ entry_point: Some("vs_entity"), -+ compilation_options: Default::default(), -+ buffers: &entity_vertex, -+ }, -+ primitive: Default::default(), -+ depth_stencil: shadow_depth.clone(), -+ multisample: Default::default(), -+ fragment: Some(wgpu::FragmentState { -+ module: &entity_shader, -+ entry_point: Some("fs_entity_shadow"), -+ compilation_options: Default::default(), -+ targets: &[], -+ }), -+ multiview_mask: None, -+ cache: None, -+ }); - let entity_pipeline = |label, cull_mode, depth_write, compare, bias, blend, clouds| { - entity_pipeline_with( - label, -@@ -2729,6 +2809,9 @@ impl Renderer { - mod_views: None, - gbuffer_terrain, - gbuffer_entity, -+ shadow_terrain, -+ shadow_entity, -+ hd_shadow_visible: Vec::new(), - hd: None, - mod_params: (Vec::new(), 0.0), - pass_timing: pack_timing::PassTiming::logging(), -@@ -3112,14 +3195,17 @@ impl Renderer { - let mut at = range.start; - if let Some(views) = &self.mod_views { - for v in batch.views.iter().filter(|v| { -- v.at >= range.start && (v.at < range.end || (v.at == range.end && range.end == last)) -+ v.at >= range.start -+ && (v.at < range.end || (v.at == range.end && range.end == last)) - }) { -- self.hud.draw_range(encoder, target, &self.depth, batch, size, at..v.at); -+ self.hud -+ .draw_range(encoder, target, &self.depth, batch, size, at..v.at); - views.blit(encoder, target, size, v, batch.scale); - at = v.at; - } - } -- self.hud.draw_range(encoder, target, &self.depth, batch, size, at..range.end); -+ self.hud -+ .draw_range(encoder, target, &self.depth, batch, size, at..range.end); - } - /// The mod cameras due this frame, each into its picture: sky, solid - /// terrain and entities, through the world's own pipelines with the -@@ -3215,7 +3301,9 @@ impl Renderer { - pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); - } - for (k, v) in d.lods.iter().enumerate() { -- let Some(tile) = self.lods.get(&v.key) else { continue }; -+ let Some(tile) = self.lods.get(&v.key) else { -+ continue; -+ }; - let s = &tile.mesh; - if s.solid == 0 { - continue; -@@ -3520,7 +3608,11 @@ impl Renderer { - } - Some(source) => { - // Under the HD lighting it draws into the HD target, else onto the screen. -- let format = if self.mod_post_hdr { hd::HDR } else { self.config.format.remove_srgb_suffix() }; -+ let format = if self.mod_post_hdr { -+ hd::HDR -+ } else { -+ self.config.format.remove_srgb_suffix() -+ }; - self.mod_post = Some(mod_post::ModPost::new(&self.device, source, format)?); - self.mod_post_source = Some(source.to_owned()); - } -@@ -3530,7 +3622,9 @@ impl Renderer { - /// AirDef fork: the mod post pass's parameters for the next frames. - pub fn set_mod_params(&mut self, params: &[f32], partial: f32) { - self.mod_params.0.clear(); -- self.mod_params.0.extend_from_slice(¶ms[..params.len().min(mod_post::PARAMS)]); -+ self.mod_params -+ .0 -+ .extend_from_slice(¶ms[..params.len().min(mod_post::PARAMS)]); - self.mod_params.1 = partial; - } - pub fn drop_shader_packs(&mut self) { -@@ -4049,6 +4143,41 @@ impl Renderer { - }); - } - self.drawn_sections = visible.len() + visible_lods.len(); -+ // AirDef fork: the HD sun shadows draw every section in the farther cascade's box. -+ let hd_wanted = !frame.title -+ && frame.hd.is_some() -+ && !self.pack_post.as_ref().is_some_and(|p| p.1.is_some()) -+ && !self.native.as_ref().is_some_and(|n| n.post.is_some()); -+ let hd_scene = hd::HdScene { -+ view_proj, -+ camera, -+ // The world's sun: vanilla turns the sky -90° about Y, so it crosses from east to west. -+ sun: Vec3::new(-sun_angle.sin(), sun_angle.cos(), 0.0), -+ dark: 1.0 - frame.sky.sky_light_factor, -+ rain: if frame.weather.is_empty() { 0.0 } else { 0.7 }, -+ fog_color, -+ fog, -+ sky: frame.skybox && !frame.end_sky && !frame.nether_lighting && !frame.underwater, -+ }; -+ let (shadow_dir, shadow_strength, _) = hd::light_of(&hd_scene); -+ let hd_cascades = hd::cascades(camera, shadow_dir); -+ let mut hd_shadow_visible = std::mem::take(&mut self.hd_shadow_visible); -+ hd_shadow_visible.clear(); -+ if hd_wanted && shadow_strength > 0.0 { -+ let radius = -+ ((frame.detail_distance / 16.0) as i32).min((hd::CASCADES[1] / 16.0) as i32 + 2); -+ let light = Frustum { -+ planes: frustum(hd_cascades[1]), -+ }; -+ self.sections.visible( -+ camera, -+ radius, -+ &light, -+ |_| true, -+ |_, _| false, -+ &mut hd_shadow_visible, -+ ); -+ } - // Mods' cameras: their sections round them, all of them (no occlusion), after the shadow's. - let globals_size = std::mem::size_of::() as u64; - let mut views_due = Vec::new(); -@@ -4063,7 +4192,9 @@ impl Renderer { - for v in views.choose(device, frame.mod_views, globals_size) { - let (view_proj, view, aspect) = mod_views::matrices(v, camera, frame.depth_far); - let own = world_projection(v.fov, aspect, frame.depth_far) * view; -- let planes = Frustum { planes: frustum(own) }; -+ let planes = Frustum { -+ planes: frustum(own), -+ }; - let mut seen = Vec::new(); - // Where distant terrain covers a column, its tile draws instead, as in the world pass. - self.sections.visible( -@@ -4105,8 +4236,13 @@ impl Renderer { - } else if frame.mod_views.is_empty() { - self.mod_views = None; - } -- let view_sections: usize = views_due.iter().map(|d| d.visible.len() + d.lods.len()).sum(); -- let instances = self.drawn_sections + shadow_visible.len() + view_sections; -+ let view_sections: usize = views_due -+ .iter() -+ .map(|d| d.visible.len() + d.lods.len()) -+ .sum(); -+ let instances = -+ self.drawn_sections + shadow_visible.len() + view_sections + hd_shadow_visible.len(); -+ let hd_shadow_first = (self.drawn_sections + shadow_visible.len() + view_sections) as u32; - if instances as u64 > self.instance_capacity { - self.instance_capacity = (instances as u64).next_power_of_two(); - self.instances = self.device.create_buffer(&wgpu::BufferDescriptor { -@@ -4125,14 +4261,13 @@ impl Renderer { - .map(|v| section_instance(v.origin)) - .chain(visible_lods.iter().map(|v| lod::instance(v.origin))) - .chain(shadow_visible.iter().map(|v| section_instance(v.origin))) -- .chain( -- views_due.iter().flat_map(|d| { -- d.visible -- .iter() -- .map(|v| section_instance(v.origin)) -- .chain(d.lods.iter().map(|v| lod::instance(v.origin))) -- }), -- ), -+ .chain(views_due.iter().flat_map(|d| { -+ d.visible -+ .iter() -+ .map(|v| section_instance(v.origin)) -+ .chain(d.lods.iter().map(|v| lod::instance(v.origin))) -+ })) -+ .chain(hd_shadow_visible.iter().map(|v| section_instance(v.origin))), - ); - if !self.origins.is_empty() { - self.uploads.buffer( -@@ -4835,10 +4970,65 @@ impl Renderer { - if hd_on && let Some(size) = self.world_post.size() { - let format = self.config.format.remove_srgb_suffix(); - let device = &self.device; -- let hd = self.hd.get_or_insert_with(|| hd::Hd::new(device, size, format)); -+ let globals_size = std::mem::size_of::() as u64; -+ let hd = self -+ .hd -+ .get_or_insert_with(|| hd::Hd::new(device, size, format, globals_size)); - hd.resize(device, size); - if let Some(settings) = frame.hd { -- hd.write(&self.queue, view_proj, frame.hd_lights, settings); -+ hd.write(&self.queue, &hd_scene, frame.hd_lights, settings); -+ } -+ // The sun's (or moon's) shadow cascades: the world's globals swapped for the light's. -+ if shadow_strength > 0.0 { -+ self.queue -+ .write_buffer(&hd.main_globals, 0, bytemuck::bytes_of(&globals)); -+ for (c, cascade) in hd_cascades.iter().enumerate() { -+ let light = Globals { -+ view_proj: cascade.to_cols_array_2d(), -+ ..globals -+ }; -+ self.queue -+ .write_buffer(&hd.shadow_globals[c], 0, bytemuck::bytes_of(&light)); -+ encoder.copy_buffer_to_buffer( -+ &hd.shadow_globals[c], -+ 0, -+ &self.globals, -+ 0, -+ globals_size, -+ ); -+ let box_ = Frustum { -+ planes: frustum(*cascade), -+ }; -+ let mut pass = hd.shadow_pass(&mut encoder, c); -+ pass.set_bind_group(0, &self.group, &[]); -+ pass.set_pipeline(&self.shadow_terrain); -+ pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32); -+ pass.set_vertex_buffer(1, self.instances.slice(..)); -+ let mut page = None; -+ for (k, v) in hd_shadow_visible.iter().enumerate() { -+ let s = self.sections.get(v.slot); -+ if s.solid == 0 { -+ continue; -+ } -+ let min = Vec3::from(v.origin); -+ if c == 0 && !box_.intersects(min, min + Vec3::splat(16.0)) { -+ continue; -+ } -+ if page != Some(s.page) { -+ pass.set_vertex_buffer(0, self.arena.buffer(s.page).slice(..)); -+ page = Some(s.page); -+ } -+ let instance = hd_shadow_first + k as u32; -+ pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); -+ } -+ if entity_quads > 0 { -+ pass.set_pipeline(&self.shadow_entity); -+ pass.set_bind_group(0, &self.entity_group, &[]); -+ pass.set_vertex_buffer(0, self.entity_buffer.slice(..)); -+ pass.draw_indexed(0..entity_quads * 6, 0, 0..1); -+ } -+ } -+ encoder.copy_buffer_to_buffer(&hd.main_globals, 0, &self.globals, 0, globals_size); - } - let mut pass = hd.gbuffer_pass(&mut encoder); - pass.set_bind_group(0, &self.group, &[]); -@@ -4846,7 +5036,10 @@ impl Renderer { - pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32); - pass.set_vertex_buffer(1, self.instances.slice(..)); - let mut page = None; -- let sections = visible.iter().enumerate().map(|(k, v)| (k, self.sections.get(v.slot))); -+ let sections = visible -+ .iter() -+ .enumerate() -+ .map(|(k, v)| (k, self.sections.get(v.slot))); - let tiles = visible_lods - .iter() - .enumerate() -@@ -5397,11 +5590,22 @@ impl Renderer { - { - // AirDef fork: lit by the mods' lights into the HD target, the mod's post pass on - // that, then bloom and the tone curve onto the screen. -- hd.light(&self.device, &mut encoder, (color, depth)); -+ hd.light( -+ &self.device, -+ &mut encoder, -+ (color, depth), -+ (&self.lightmap_view, &self.lightmap_sampler), -+ ); - match &mut self.mod_post { - Some(post) if mod_post => { - let size = self.world_post.size().unwrap_or([1, 1]); -- post.write(&self.queue, frame, size, &self.mod_params.0, self.mod_params.1); -+ post.write( -+ &self.queue, -+ frame, -+ size, -+ &self.mod_params.0, -+ self.mod_params.1, -+ ); - post.run(&self.device, &mut encoder, (&hd.lit, depth), &hd.fx); - hd.finish(&self.device, &mut encoder, &hd.fx, &view_target); - } -@@ -5412,7 +5616,13 @@ impl Renderer { - && let Some(post) = &mut self.mod_post - { - let size = self.world_post.size().unwrap_or([1, 1]); -- post.write(&self.queue, frame, size, &self.mod_params.0, self.mod_params.1); -+ post.write( -+ &self.queue, -+ frame, -+ size, -+ &self.mod_params.0, -+ self.mod_params.1, -+ ); - post.run(&self.device, &mut encoder, (color, depth), &view_target); - } else if scaled.is_some() { - self.world_post.finish(&mut encoder, &view_target); -@@ -5635,6 +5845,7 @@ impl Renderer { - self.visible = visible; - self.visible_lods = visible_lods; - self.shadow_visible = shadow_visible; -+ self.hd_shadow_visible = hd_shadow_visible; - Ok(true) - } - } -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index b40484e5e..c36c1795d 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -10,6 +10,8 @@ use glam::Mat4; - /// The G-buffer's formats. - pub const ALBEDO: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; - pub const NORMAL: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; -+/// Sky and block light per pixel. -+pub const LIGHT_LEVELS: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; - /// The lit world and the post pass's output. - pub const HDR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; - /// The most lights drawn a frame. -@@ -41,15 +43,54 @@ struct HdFrame { - inverse_view_proj: [[f32; 4]; 4], - /// width, height, 1/width, 1/height. - resolution: [f32; 4], -- /// lights, exposure, bloom, -. -+ /// lights, exposure, bloom, a frame counter. - params: [f32; 4], -+ /// The shadow cascades: camera-relative position to the light's clip space. -+ cascades: [[[f32; 4]; 4]; 2], -+ /// xyz: towards the sun (or the moon at night); w: how much it lights (0 set or hidden). -+ sun: [f32; 4], -+ sun_color: [f32; 4], -+ fog_color: [f32; 4], -+ /// Environmental start, end; render-distance start, end. -+ fog: [f32; 4], -+ /// x: sky light kept in the shade; y: blocks' light; z: the air's density (rays, halos). -+ ambient: [f32; 4], -+ /// x: shadows on; y, z: the cascades' half sizes. -+ shadow: [f32; 4], - } - -+/// What the world around looks like this frame (from the renderer's globals). -+#[derive(Clone, Copy, Debug)] -+pub struct HdScene { -+ pub view_proj: Mat4, -+ /// The camera in the world, for steady shadows. -+ pub camera: glam::DVec3, -+ /// Towards the sun, and the sky's darkening (0 day, 1 night). -+ pub sun: glam::Vec3, -+ pub dark: f32, -+ pub rain: f32, -+ pub fog_color: [f32; 4], -+ pub fog: [f32; 4], -+ /// The sky has a sun at all (the Overworld). -+ pub sky: bool, -+} -+ -+/// The cascades' half sizes (blocks) and shadow map size. -+pub const CASCADES: [f32; 2] = [40.0, 176.0]; -+pub const SHADOW_SIZE: u32 = 2048; -+ - const COMMON: &str = r#" - struct HdFrame { - inverse_view_proj: mat4x4, - resolution: vec4, - params: vec4, -+ cascades: array, 2>, -+ sun: vec4, -+ sun_color: vec4, -+ fog_color: vec4, -+ fog: vec4, -+ ambient: vec4, -+ shadow: vec4, - } - struct FullOut { - @builtin(position) position: vec4, -@@ -77,38 +118,131 @@ struct Light { - @group(0) @binding(4) var normal_tex: texture_2d; - @group(0) @binding(5) var gbuffer_depth: texture_depth_2d; - @group(0) @binding(6) var lights: array; -+@group(0) @binding(7) var light_tex: texture_2d; -+@group(0) @binding(8) var shadow_map: texture_depth_2d_array; -+@group(0) @binding(9) var shadow_sampler: sampler_comparison; -+@group(0) @binding(10) var lightmap: texture_2d; -+@group(0) @binding(11) var linear: sampler; -+ -+// Interleaved gradient noise: a different offset each pixel and frame, for the ray march. -+fn ign(px: vec2) -> f32 { -+ let p = px + vec2(5.588238 * frame.params.w); -+ return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); -+} -+ -+// Where a camera-relative point falls in cascade c: uv and depth. -+fn cascade_at(p: vec3, c: i32) -> vec3 { -+ let s = frame.cascades[c] * vec4(p, 1.0); -+ return vec3(s.x * 0.5 + 0.5, 0.5 - s.y * 0.5, s.z); -+} -+ -+// The sun's (or moon's) light reaching p: 1 lit, 0 in shadow; soft-edged. -+fn shadow(p: vec3, n: vec3) -> f32 { -+ let dist = length(p); -+ let c = select(1, 0, dist < frame.shadow.y * 0.92); -+ let texel = select(frame.shadow.z, frame.shadow.y, c == 0) * 2.0 / 2048.0; -+ let q = p + n * texel * 1.5; -+ let s = cascade_at(q, c); -+ if any(s.xy < vec2(0.0)) || any(s.xy > vec2(1.0)) || s.z > 1.0 { return 1.0; } -+ var lit = 0.0; -+ let step = 1.0 / 2048.0; -+ for (var y = -1; y <= 1; y++) { -+ for (var x = -1; x <= 1; x++) { -+ lit += textureSampleCompareLevel(shadow_map, shadow_sampler, s.xy + vec2(f32(x), f32(y)) * step, c, s.z - 0.0005); -+ } -+ } -+ let fade = clamp((frame.shadow.z - dist) / 16.0, 0.0, 1.0); -+ return mix(1.0, lit / 9.0, fade); -+} -+ -+// One shadow sample, for the rays through the air. -+fn lit_at(p: vec3) -> f32 { -+ let s = cascade_at(p, 1); -+ if any(s.xy < vec2(0.0)) || any(s.xy > vec2(1.0)) || s.z > 1.0 { return 1.0; } -+ return textureSampleCompareLevel(shadow_map, shadow_sampler, s.xy, 1, s.z - 0.001); -+} -+ -+fn linear_fog(d: f32, start: f32, end: f32) -> f32 { -+ if d <= start { return 0.0; } -+ if d >= end { return 1.0; } -+ return (d - start) / (end - start); -+} - - @fragment - fn fs_light(in: FullOut) -> @location(0) vec4 { - let px = vec2(in.position.xy); -- var c = textureLoad(scene_color, px, 0).rgb; -+ let scene = textureLoad(scene_color, px, 0).rgb; - let d = textureLoad(scene_depth, px, 0); - let g = textureLoad(gbuffer_depth, px, 0); -- // Only where the G-buffer holds what the world shows (not the sky, water, particles). -- if d <= 0.0 || abs(d - g) > max(d, g) * 0.002 { -- return vec4(c, 1.0); -- } -- let ndc = vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, d, 1.0); -+ let ndc = vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, max(d, 0.000001), 1.0); - let h = frame.inverse_view_proj * ndc; - let p = h.xyz / h.w; -- let n = normalize(textureLoad(normal_tex, px, 0).xyz); -- let albedo = textureLoad(albedo_tex, px, 0); -- var light = vec3(0.0); -+ let view = normalize(p); -+ let sun = frame.sun.xyz; -+ let sun_on = frame.sun.w; - let count = u32(frame.params.x); -+ var c = scene; -+ let surface = d > 0.0 && abs(d - g) <= max(d, g) * 0.002; -+ if surface { -+ let n = normalize(textureLoad(normal_tex, px, 0).xyz); -+ let albedo = textureLoad(albedo_tex, px, 0); -+ let lm = textureLoad(light_tex, px, 0).xy; -+ // Vanilla's light (sky and blocks through the lightmap), shaded where the sun can't reach. -+ let base = textureSampleLevel(lightmap, linear, clamp(vec2(lm.y, lm.x) * (15.0 / 16.0) + 0.5 / 16.0, vec2(0.5 / 16.0), vec2(15.5 / 16.0)), 0.0).rgb; -+ let block = textureSampleLevel(lightmap, linear, vec2(lm.y * (15.0 / 16.0) + 0.5 / 16.0, 0.5 / 16.0), 0.0).rgb; -+ let sky_reach = smoothstep(0.35, 0.95, lm.x); -+ var s = 1.0; -+ let ndl = dot(n, sun); -+ if sun_on > 0.0 && ndl > 0.0 && sky_reach > 0.0 { s = shadow(p, n); } else { s = 0.0; } -+ // Shadowed sky light (ambient) keeps vanilla's look, blocks' light stays as is; the sun adds -+ // direct light by the face's angle. -+ let direct = frame.sun_color.rgb * sun_on * max(ndl, 0.0) * s * sky_reach; -+ var light = max(base * frame.ambient.x, block * frame.ambient.y) + direct; -+ // The mods' lights. -+ for (var i = 0u; i < count; i++) { -+ let l = lights[i]; -+ let to = l.pos.xyz - p; -+ let dist = length(to); -+ if dist >= l.pos.w { continue; } -+ let window = pow(clamp(1.0 - pow(dist / l.pos.w, 4.0), 0.0, 1.0), 2.0); -+ let falloff = l.color.w / (dist * dist + 1.0) * window; -+ let k = max(dot(n, to / max(dist, 0.001)) * 0.85 + 0.15, 0.0); -+ light += l.color.rgb * falloff * k; -+ } -+ c = albedo.rgb * light + albedo.rgb * albedo.a * 1.5; -+ // Vanilla's fog over it again. -+ let sph = length(p); -+ let cyl = max(length(p.xz), abs(p.y)); -+ let f = max(linear_fog(sph, frame.fog.x, frame.fog.y), linear_fog(cyl, frame.fog.z, frame.fog.w)); -+ c = mix(c, frame.fog_color.rgb, f * frame.fog_color.a); -+ } -+ // Light in the air: the sun's rays between the shadows, and halos round the mods' lights. -+ let far = select(min(length(p), frame.shadow.z), frame.shadow.z, d <= 0.0); -+ let density = frame.ambient.z; -+ if sun_on > 0.0 && density > 0.0 { -+ let steps = 14; -+ let jitter = ign(in.position.xy); -+ var acc = 0.0; -+ for (var i = 0; i < steps; i++) { -+ let t = (f32(i) + jitter) / f32(steps) * far; -+ acc += lit_at(view * t); -+ } -+ // Henyey-Greenstein, forward scattering: brightest looking towards the sun. -+ let cos_t = dot(view, sun); -+ let gk = 0.6; -+ let phase = (1.0 - gk * gk) / pow(1.0 + gk * gk - 2.0 * gk * cos_t, 1.5) * 0.08; -+ c += frame.sun_color.rgb * sun_on * (acc / f32(steps)) * phase * (1.0 - exp(-far * density)); -+ } - for (var i = 0u; i < count; i++) { - let l = lights[i]; -- let to = l.pos.xyz - p; -- let dist = length(to); -- if dist >= l.pos.w { continue; } -- // Inverse square, softened near the light, faded to nothing at its radius. -- let window = pow(clamp(1.0 - pow(dist / l.pos.w, 4.0), 0.0, 1.0), 2.0); -- let falloff = l.color.w / (dist * dist + 1.0) * window; -- let ndl = max(dot(n, to / max(dist, 0.001)) * 0.85 + 0.15, 0.0); -- light += l.color.rgb * falloff * ndl; -+ // Nearest point of the view ray to the light, and the glow along the ray in front of it. -+ let t0 = dot(l.pos.xyz, view); -+ let hh = length(l.pos.xyz - view * t0) + 0.5; -+ if hh > l.pos.w * 0.5 { continue; } -+ let a0 = atan((0.0 - t0) / hh); -+ let a1 = atan((far - t0) / hh); -+ c += l.color.rgb * l.color.w * (a1 - a0) / hh * 0.004 * (0.3 + density * 300.0); - } -- c += albedo.rgb * light; -- // Glowing parts (flames, flares) shine past white, for the bloom. -- c += albedo.rgb * albedo.a * 1.5; - return vec4(c, 1.0); - } - "#; -@@ -188,6 +322,10 @@ fn fs_tonemap(in: FullOut) -> @location(0) vec4 { - // Very bright light bleaches towards white, as film does. - c = mix(c, vec3(rolled), clamp((peak - 2.0) / 8.0, 0.0, 0.6)); - } -+ // A light grade: a touch more colour, and a gentle S round the middle grey. -+ let luma = dot(c, vec3(0.2126, 0.7152, 0.0722)); -+ c = max(mix(vec3(luma), c, 1.12), vec3(0.0)); -+ c = mix(c, c * c * (3.0 - 2.0 * c), 0.18); - return vec4(c, 1.0); - } - "#; -@@ -196,11 +334,20 @@ struct Target { - view: wgpu::TextureView, - } - --fn target(device: &wgpu::Device, label: &str, size: [u32; 2], format: wgpu::TextureFormat) -> Target { -+fn target( -+ device: &wgpu::Device, -+ label: &str, -+ size: [u32; 2], -+ format: wgpu::TextureFormat, -+) -> Target { - let view = device - .create_texture(&wgpu::TextureDescriptor { - label: Some(label), -- size: wgpu::Extent3d { width: size[0].max(1), height: size[1].max(1), depth_or_array_layers: 1 }, -+ size: wgpu::Extent3d { -+ width: size[0].max(1), -+ height: size[1].max(1), -+ depth_or_array_layers: 1, -+ }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, -@@ -216,6 +363,15 @@ pub struct Hd { - size: [u32; 2], - pub albedo: wgpu::TextureView, - pub normal: wgpu::TextureView, -+ pub levels: wgpu::TextureView, -+ /// The shadow cascades: the array to sample, one view per layer to draw. -+ shadow_array: wgpu::TextureView, -+ shadow_layers: Vec, -+ shadow_sampler: wgpu::Sampler, -+ /// The globals the shadow passes draw with (the light's view), and the camera's to put back. -+ pub shadow_globals: [wgpu::Buffer; 2], -+ pub main_globals: wgpu::Buffer, -+ frames: u32, - pub depth: wgpu::TextureView, - /// The lit world, and the post pass's output. - pub lit: wgpu::TextureView, -@@ -258,12 +414,21 @@ fn fullscreen( - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some(label), - layout: Some(&pipeline_layout), -- vertex: wgpu::VertexState { module, entry_point: Some("vs_full"), compilation_options: Default::default(), buffers: &[] }, -+ vertex: wgpu::VertexState { -+ module, -+ entry_point: Some("vs_full"), -+ compilation_options: Default::default(), -+ buffers: &[], -+ }, - fragment: Some(wgpu::FragmentState { - module, - entry_point: Some(entry), - compilation_options: Default::default(), -- targets: &[Some(wgpu::ColorTargetState { format, blend, write_mask: wgpu::ColorWrites::ALL })], -+ targets: &[Some(wgpu::ColorTargetState { -+ format, -+ blend, -+ write_mask: wgpu::ColorWrites::ALL, -+ })], - }), - primitive: Default::default(), - depth_stencil: None, -@@ -286,8 +451,11 @@ fn entries(kinds: &[wgpu::BindingType]) -> Vec { - .collect() - } - --const UNIFORM: wgpu::BindingType = -- wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }; -+const UNIFORM: wgpu::BindingType = wgpu::BindingType::Buffer { -+ ty: wgpu::BufferBindingType::Uniform, -+ has_dynamic_offset: false, -+ min_binding_size: None, -+}; - const FLOAT: wgpu::BindingType = wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, -@@ -306,7 +474,12 @@ const DEPTH: wgpu::BindingType = wgpu::BindingType::Texture { - const SAMPLER: wgpu::BindingType = wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering); - - impl Hd { -- pub fn new(device: &wgpu::Device, size: [u32; 2], screen: wgpu::TextureFormat) -> Self { -+ pub fn new( -+ device: &wgpu::Device, -+ size: [u32; 2], -+ screen: wgpu::TextureFormat, -+ globals_size: u64, -+ ) -> Self { - let light_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("HD lighting"), - entries: &entries(&[ -@@ -321,6 +494,15 @@ impl Hd { - has_dynamic_offset: false, - min_binding_size: None, - }, -+ UNFILTERED, -+ wgpu::BindingType::Texture { -+ sample_type: wgpu::TextureSampleType::Depth, -+ view_dimension: wgpu::TextureViewDimension::D2Array, -+ multisampled: false, -+ }, -+ wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), -+ FLOAT, -+ SAMPLER, - ]), - }); - let bloom_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -@@ -335,14 +517,58 @@ impl Hd { - let bloom = module(device, "HD bloom", BLOOM); - let tone = module(device, "HD tone mapping", TONEMAP); - let add = wgpu::BlendState { -- color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add }, -+ color: wgpu::BlendComponent { -+ src_factor: wgpu::BlendFactor::One, -+ dst_factor: wgpu::BlendFactor::One, -+ operation: wgpu::BlendOperation::Add, -+ }, - alpha: wgpu::BlendComponent::REPLACE, - }; -- let light_pipeline = fullscreen(device, "HD lighting", &light_layout, &light, "fs_light", HDR, None); -- let prefilter = fullscreen(device, "HD bloom prefilter", &bloom_layout, &bloom, "fs_prefilter", HDR, None); -- let down = fullscreen(device, "HD bloom down", &bloom_layout, &bloom, "fs_down", HDR, None); -- let up = fullscreen(device, "HD bloom up", &bloom_layout, &bloom, "fs_up", HDR, Some(add)); -- let tone = fullscreen(device, "HD tone mapping", &tone_layout, &tone, "fs_tonemap", screen, None); -+ let light_pipeline = fullscreen( -+ device, -+ "HD lighting", -+ &light_layout, -+ &light, -+ "fs_light", -+ HDR, -+ None, -+ ); -+ let prefilter = fullscreen( -+ device, -+ "HD bloom prefilter", -+ &bloom_layout, -+ &bloom, -+ "fs_prefilter", -+ HDR, -+ None, -+ ); -+ let down = fullscreen( -+ device, -+ "HD bloom down", -+ &bloom_layout, -+ &bloom, -+ "fs_down", -+ HDR, -+ None, -+ ); -+ let up = fullscreen( -+ device, -+ "HD bloom up", -+ &bloom_layout, -+ &bloom, -+ "fs_up", -+ HDR, -+ Some(add), -+ ); -+ let tone = fullscreen( -+ device, -+ "HD tone mapping", -+ &tone_layout, -+ &tone, -+ "fs_tonemap", -+ screen, -+ None, -+ ); - let frame = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("HD frame"), - size: std::mem::size_of::() as u64, -@@ -363,11 +589,70 @@ impl Hd { - address_mode_v: wgpu::AddressMode::ClampToEdge, - ..Default::default() - }); -+ let shadow_texture = device.create_texture(&wgpu::TextureDescriptor { -+ label: Some("HD shadow cascades"), -+ size: wgpu::Extent3d { -+ width: SHADOW_SIZE, -+ height: SHADOW_SIZE, -+ depth_or_array_layers: 2, -+ }, -+ mip_level_count: 1, -+ sample_count: 1, -+ dimension: wgpu::TextureDimension::D2, -+ format: wgpu::TextureFormat::Depth32Float, -+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, -+ view_formats: &[], -+ }); -+ let shadow_array = shadow_texture.create_view(&wgpu::TextureViewDescriptor { -+ dimension: Some(wgpu::TextureViewDimension::D2Array), -+ ..Default::default() -+ }); -+ let shadow_layers = (0..2) -+ .map(|l| { -+ shadow_texture.create_view(&wgpu::TextureViewDescriptor { -+ dimension: Some(wgpu::TextureViewDimension::D2), -+ base_array_layer: l, -+ array_layer_count: Some(1), -+ ..Default::default() -+ }) -+ }) -+ .collect(); -+ let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor { -+ label: Some("HD shadows"), -+ mag_filter: wgpu::FilterMode::Linear, -+ min_filter: wgpu::FilterMode::Linear, -+ compare: Some(wgpu::CompareFunction::LessEqual), -+ ..Default::default() -+ }); -+ let globals = |label| { -+ device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some(label), -+ size: globals_size, -+ usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }) -+ }; - let mut hd = Hd { - size: [0, 0], - albedo: target(device, "HD albedo", [1, 1], ALBEDO).view, - normal: target(device, "HD normal", [1, 1], NORMAL).view, -- depth: target(device, "HD depth", [1, 1], wgpu::TextureFormat::Depth32Float).view, -+ levels: target(device, "HD light levels", [1, 1], LIGHT_LEVELS).view, -+ shadow_array, -+ shadow_layers, -+ shadow_sampler, -+ shadow_globals: [ -+ globals("HD shadow globals 0"), -+ globals("HD shadow globals 1"), -+ ], -+ main_globals: globals("HD camera globals"), -+ frames: 0, -+ depth: target( -+ device, -+ "HD depth", -+ [1, 1], -+ wgpu::TextureFormat::Depth32Float, -+ ) -+ .view, - lit: target(device, "HD lit", [1, 1], HDR).view, - fx: target(device, "HD post", [1, 1], HDR).view, - bloom: Vec::new(), -@@ -395,6 +680,7 @@ impl Hd { - self.size = size; - self.albedo = target(device, "HD albedo", size, ALBEDO).view; - self.normal = target(device, "HD normal", size, NORMAL).view; -+ self.levels = target(device, "HD light levels", size, LIGHT_LEVELS).view; - self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; - self.lit = target(device, "HD lit", size, HDR).view; - self.fx = target(device, "HD post", size, HDR).view; -@@ -406,20 +692,77 @@ impl Hd { - .collect(); - } - -- /// This frame's camera, lights and settings. -- pub fn write(&mut self, queue: &wgpu::Queue, view_proj: Mat4, lights: &[HdLight], settings: HdSettings) { -+ /// This frame's camera, sun, fog, lights and settings. -+ pub fn write( -+ &mut self, -+ queue: &wgpu::Queue, -+ scene: &HdScene, -+ lights: &[HdLight], -+ settings: HdSettings, -+ ) { - let n = lights.len().min(MAX_LIGHTS); - if n > 0 { - queue.write_buffer(&self.lights, 0, bytemuck::cast_slice(&lights[..n])); - } -+ self.frames = self.frames.wrapping_add(1); -+ // The sun by day, the moon (opposite) by night, faded out round the horizon. -+ let (dir, strength, color) = light_of(scene); -+ let cascades = cascades(scene.camera, dir); -+ let night = scene.dark.clamp(0.0, 1.0); - let frame = HdFrame { -- inverse_view_proj: view_proj.inverse().to_cols_array_2d(), -- resolution: [self.size[0] as f32, self.size[1] as f32, 1.0 / self.size[0] as f32, 1.0 / self.size[1] as f32], -- params: [n as f32, settings.exposure, settings.bloom, 0.0], -+ inverse_view_proj: scene.view_proj.inverse().to_cols_array_2d(), -+ resolution: [ -+ self.size[0] as f32, -+ self.size[1] as f32, -+ 1.0 / self.size[0] as f32, -+ 1.0 / self.size[1] as f32, -+ ], -+ params: [ -+ n as f32, -+ settings.exposure, -+ settings.bloom, -+ (self.frames % 64) as f32, -+ ], -+ cascades: cascades.map(|m| m.to_cols_array_2d()), -+ sun: [dir.x, dir.y, dir.z, strength], -+ sun_color: [color.x, color.y, color.z, night], -+ fog_color: scene.fog_color, -+ fog: scene.fog, -+ // In the shade by day a surface keeps half its sky light; at night almost all. -+ ambient: [0.5 + 0.45 * night, 1.0, 0.0018 + scene.rain * 0.006, 0.0], -+ shadow: [ -+ if strength > 0.0 { 1.0 } else { 0.0 }, -+ CASCADES[0], -+ CASCADES[1], -+ 0.0, -+ ], - }; - queue.write_buffer(&self.frame, 0, bytemuck::bytes_of(&frame)); - } - -+ /// The shadow pass of cascade `c`: its depth layer cleared, nothing else. -+ pub fn shadow_pass<'e>( -+ &self, -+ encoder: &'e mut wgpu::CommandEncoder, -+ c: usize, -+ ) -> wgpu::RenderPass<'e> { -+ encoder -+ .begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("HD shadow cascade"), -+ color_attachments: &[], -+ depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { -+ view: &self.shadow_layers[c], -+ depth_ops: Some(wgpu::Operations { -+ load: wgpu::LoadOp::Clear(1.0), -+ store: wgpu::StoreOp::Store, -+ }), -+ stencil_ops: None, -+ }), -+ ..Default::default() -+ }) -+ .forget_lifetime() -+ } -+ - /// The G-buffer pass's attachments: albedo and normal cleared, its own depth. - pub fn gbuffer_pass<'e>(&self, encoder: &'e mut wgpu::CommandEncoder) -> wgpu::RenderPass<'e> { - let clear = |view| { -@@ -427,23 +770,41 @@ impl Hd { - view, - resolve_target: None, - depth_slice: None, -- ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store }, -+ ops: wgpu::Operations { -+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), -+ store: wgpu::StoreOp::Store, -+ }, - }) - }; -- encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -- label: Some("HD G-buffer"), -- color_attachments: &[clear(&self.albedo), clear(&self.normal)], -- depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { -- view: &self.depth, -- depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(0.0), store: wgpu::StoreOp::Store }), -- stencil_ops: None, -- }), -- ..Default::default() -- }) -- .forget_lifetime() -+ encoder -+ .begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("HD G-buffer"), -+ color_attachments: &[ -+ clear(&self.albedo), -+ clear(&self.normal), -+ clear(&self.levels), -+ ], -+ depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { -+ view: &self.depth, -+ depth_ops: Some(wgpu::Operations { -+ load: wgpu::LoadOp::Clear(0.0), -+ store: wgpu::StoreOp::Store, -+ }), -+ stencil_ops: None, -+ }), -+ ..Default::default() -+ }) -+ .forget_lifetime() - } - -- fn draw(encoder: &mut wgpu::CommandEncoder, label: &str, to: &wgpu::TextureView, load: bool, pipeline: &wgpu::RenderPipeline, group: &wgpu::BindGroup) { -+ fn draw( -+ encoder: &mut wgpu::CommandEncoder, -+ label: &str, -+ to: &wgpu::TextureView, -+ load: bool, -+ pipeline: &wgpu::RenderPipeline, -+ group: &wgpu::BindGroup, -+ ) { - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some(label), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { -@@ -451,7 +812,11 @@ impl Hd { - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { -- load: if load { wgpu::LoadOp::Load } else { wgpu::LoadOp::Clear(wgpu::Color::BLACK) }, -+ load: if load { -+ wgpu::LoadOp::Load -+ } else { -+ wgpu::LoadOp::Clear(wgpu::Color::BLACK) -+ }, - store: wgpu::StoreOp::Store, - }, - })], -@@ -463,21 +828,75 @@ impl Hd { - } - - /// Lights the world (its colour and depth) into `lit`. -- pub fn light(&self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, world: (&wgpu::TextureView, &wgpu::TextureView)) { -+ pub fn light( -+ &self, -+ device: &wgpu::Device, -+ encoder: &mut wgpu::CommandEncoder, -+ world: (&wgpu::TextureView, &wgpu::TextureView), -+ lightmap: (&wgpu::TextureView, &wgpu::Sampler), -+ ) { - let group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("HD lighting"), - layout: &self.light_layout, - entries: &[ -- wgpu::BindGroupEntry { binding: 0, resource: self.frame.as_entire_binding() }, -- wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(world.0) }, -- wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(world.1) }, -- wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&self.albedo) }, -- wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(&self.normal) }, -- wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(&self.depth) }, -- wgpu::BindGroupEntry { binding: 6, resource: self.lights.as_entire_binding() }, -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: self.frame.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::TextureView(world.0), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 2, -+ resource: wgpu::BindingResource::TextureView(world.1), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 3, -+ resource: wgpu::BindingResource::TextureView(&self.albedo), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 4, -+ resource: wgpu::BindingResource::TextureView(&self.normal), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 5, -+ resource: wgpu::BindingResource::TextureView(&self.depth), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 6, -+ resource: self.lights.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 7, -+ resource: wgpu::BindingResource::TextureView(&self.levels), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 8, -+ resource: wgpu::BindingResource::TextureView(&self.shadow_array), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 9, -+ resource: wgpu::BindingResource::Sampler(&self.shadow_sampler), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 10, -+ resource: wgpu::BindingResource::TextureView(lightmap.0), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 11, -+ resource: wgpu::BindingResource::Sampler(lightmap.1), -+ }, - ], - }); -- Self::draw(encoder, "HD lighting", &self.lit, false, &self.light_pipeline, &group); -+ Self::draw( -+ encoder, -+ "HD lighting", -+ &self.lit, -+ false, -+ &self.light_pipeline, -+ &group, -+ ); - } - - fn bloom_group(&self, device: &wgpu::Device, source: &wgpu::TextureView) -> wgpu::BindGroup { -@@ -485,36 +904,140 @@ impl Hd { - label: Some("HD bloom"), - layout: &self.bloom_layout, - entries: &[ -- wgpu::BindGroupEntry { binding: 0, resource: self.frame.as_entire_binding() }, -- wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(source) }, -- wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.sampler) }, -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: self.frame.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::TextureView(source), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 2, -+ resource: wgpu::BindingResource::Sampler(&self.sampler), -+ }, - ], - }) - } - - /// Bloom from `source` (the lit world or the post pass's output), then the tone curve onto `screen`. -- pub fn finish(&self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, source: &wgpu::TextureView, screen: &wgpu::TextureView) { -+ pub fn finish( -+ &self, -+ device: &wgpu::Device, -+ encoder: &mut wgpu::CommandEncoder, -+ source: &wgpu::TextureView, -+ screen: &wgpu::TextureView, -+ ) { - // Down: the prefilter into the first level, then each level from the one above. -- Self::draw(encoder, "HD bloom prefilter", &self.bloom[0].view, false, &self.prefilter, &self.bloom_group(device, source)); -+ Self::draw( -+ encoder, -+ "HD bloom prefilter", -+ &self.bloom[0].view, -+ false, -+ &self.prefilter, -+ &self.bloom_group(device, source), -+ ); - for k in 1..self.bloom.len() { - let g = self.bloom_group(device, &self.bloom[k - 1].view); -- Self::draw(encoder, "HD bloom down", &self.bloom[k].view, false, &self.down, &g); -+ Self::draw( -+ encoder, -+ "HD bloom down", -+ &self.bloom[k].view, -+ false, -+ &self.down, -+ &g, -+ ); - } - // Up: each level added onto the bigger one. - for k in (1..self.bloom.len()).rev() { - let g = self.bloom_group(device, &self.bloom[k].view); -- Self::draw(encoder, "HD bloom up", &self.bloom[k - 1].view, true, &self.up, &g); -+ Self::draw( -+ encoder, -+ "HD bloom up", -+ &self.bloom[k - 1].view, -+ true, -+ &self.up, -+ &g, -+ ); - } - let group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("HD tone mapping"), - layout: &self.tone_layout, - entries: &[ -- wgpu::BindGroupEntry { binding: 0, resource: self.frame.as_entire_binding() }, -- wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(source) }, -- wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.bloom[0].view) }, -- wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self.sampler) }, -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: self.frame.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::TextureView(source), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 2, -+ resource: wgpu::BindingResource::TextureView(&self.bloom[0].view), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 3, -+ resource: wgpu::BindingResource::Sampler(&self.sampler), -+ }, - ], - }); -- Self::draw(encoder, "HD tone mapping", screen, false, &self.tone, &group); -+ Self::draw( -+ encoder, -+ "HD tone mapping", -+ screen, -+ false, -+ &self.tone, -+ &group, -+ ); -+ } -+} -+ -+/// The light that casts the shadows: towards the sun by day, the moon by night; how strong -+/// (0 below the horizon or without a sky) and its colour (warm low, white high, cold moon). -+pub fn light_of(scene: &HdScene) -> (glam::Vec3, f32, glam::Vec3) { -+ if !scene.sky { -+ return (glam::Vec3::Y, 0.0, glam::Vec3::ZERO); -+ } -+ let sun = scene.sun.normalize_or(glam::Vec3::Y); -+ let clear = 1.0 - scene.rain * 0.85; -+ if sun.y > -0.02 { -+ let up = smooth(-0.02, 0.18, sun.y); -+ let warm = smooth(0.0, 0.45, sun.y); -+ let color = glam::Vec3::new(1.0, 0.62, 0.38).lerp(glam::Vec3::new(1.0, 0.95, 0.86), warm); -+ (sun, 0.6 * up * clear, color) -+ } else { -+ let moon = -sun; -+ let up = smooth(-0.02, 0.18, moon.y); -+ (moon, 0.15 * up * clear, glam::Vec3::new(0.55, 0.66, 1.0)) - } - } -+ -+fn smooth(a: f32, b: f32, x: f32) -> f32 { -+ let t = ((x - a) / (b - a)).clamp(0.0, 1.0); -+ t * t * (3.0 - 2.0 * t) -+} -+ -+/// The cascades' light view-projections (camera-relative), each square snapped to its texels in -+/// the world so the shadows don't crawl as the camera moves. -+pub fn cascades(camera: glam::DVec3, light: glam::Vec3) -> [Mat4; 2] { -+ let l = light.normalize_or(glam::Vec3::Y).as_dvec3(); -+ let forward = -l; -+ let reference = if l.y.abs() > 0.99 { -+ glam::DVec3::Z -+ } else { -+ glam::DVec3::Y -+ }; -+ let right = forward.cross(reference).normalize(); -+ let up = right.cross(forward); -+ CASCADES.map(|r| { -+ let texel = f64::from(r) * 2.0 / f64::from(SHADOW_SIZE); -+ let snap = |v: f64| (v / texel).floor() * texel - v; -+ let centre = right * snap(camera.dot(right)) + up * snap(camera.dot(up)); -+ let eye = centre + l * 400.0; -+ let view = glam::DMat4::look_at_rh(eye, centre, up); -+ let r = f64::from(r); -+ let projection = glam::DMat4::orthographic_rh(-r, r, -r, r, 1.0, 800.0); -+ (projection * view).as_mat4() -+ }) -+} -diff --git a/crates/horizon-client/src/gfx/mod_post.rs b/crates/horizon-client/src/gfx/mod_post.rs -index 497cff854..d1f54a4bb 100644 ---- a/crates/horizon-client/src/gfx/mod_post.rs -+++ b/crates/horizon-client/src/gfx/mod_post.rs -@@ -121,7 +121,11 @@ fn compile(source: &str) -> Result { - let text = e.emit_to_string(&full); - match e.location(&full) { - Some(l) if l.line_number as usize > prelude_lines => { -- format!("line {}: {}", l.line_number as usize - prelude_lines, e.message()) -+ format!( -+ "line {}: {}", -+ l.line_number as usize - prelude_lines, -+ e.message() -+ ) - } - _ => text, - } -@@ -136,7 +140,11 @@ fn compile(source: &str) -> Result { - } - - impl ModPost { -- pub fn new(device: &wgpu::Device, source: &str, format: wgpu::TextureFormat) -> Result { -+ pub fn new( -+ device: &wgpu::Device, -+ source: &str, -+ format: wgpu::TextureFormat, -+ ) -> Result { - let module = compile(source)?; - let texture = |binding, sample_type| wgpu::BindGroupLayoutEntry { - binding, -@@ -232,11 +240,19 @@ impl ModPost { - } - - /// This frame's camera and the mod's `params`, for a world of `size`. -- pub fn write(&self, queue: &wgpu::Queue, frame: &Frame, size: [u32; 2], params: &[f32], partial: f32) { -+ pub fn write( -+ &self, -+ queue: &wgpu::Queue, -+ frame: &Frame, -+ size: [u32; 2], -+ params: &[f32], -+ partial: f32, -+ ) { - let aspect = size[0] as f32 / size[1].max(1) as f32; - let view = crate::camera::view_matrix(frame.yaw, frame.pitch); -- let projection = -- super::world_projection(frame.fov_degrees, aspect, frame.depth_far) * frame.bob * frame.spin; -+ let projection = super::world_projection(frame.fov_degrees, aspect, frame.depth_far) -+ * frame.bob -+ * frame.spin; - let a = frame.sky.sun_angle.to_radians(); - let sun = [-a.sin(), a.cos(), 0.0, 0.0]; - let mut p = [[0.0f32; 4]; PARAMS / 4]; -@@ -248,14 +264,24 @@ impl ModPost { - view_proj: m(projection * view), - view_inverse: m(view.inverse()), - projection_inverse: m(projection.inverse()), -- camera: [frame.camera.x as f32, frame.camera.y as f32, frame.camera.z as f32, 0.0], -+ camera: [ -+ frame.camera.x as f32, -+ frame.camera.y as f32, -+ frame.camera.z as f32, -+ 0.0, -+ ], - resolution: [ - size[0] as f32, - size[1] as f32, - 1.0 / size[0].max(1) as f32, - 1.0 / size[1].max(1) as f32, - ], -- time: [self.start.elapsed().as_secs_f32() % 3600.0, partial, sun[1], 0.0], -+ time: [ -+ self.start.elapsed().as_secs_f32() % 3600.0, -+ partial, -+ sun[1], -+ 0.0, -+ ], - sun, - params: p, - }; -@@ -279,15 +305,29 @@ impl ModPost { - label: Some("Mod post"), - layout: &self.layout, - entries: &[ -- wgpu::BindGroupEntry { binding: 0, resource: self.buffer.as_entire_binding() }, -- wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(world.0) }, -- wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(world.1) }, -- wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&self.sampler) }, -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: self.buffer.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::TextureView(world.0), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 2, -+ resource: wgpu::BindingResource::TextureView(world.1), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 3, -+ resource: wgpu::BindingResource::Sampler(&self.sampler), -+ }, - ], - }); - self.group = Some(((world.0.clone(), world.1.clone()), group)); - } -- let Some((_, group)) = &self.group else { return }; -+ let Some((_, group)) = &self.group else { -+ return; -+ }; - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Mod post"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { -diff --git a/crates/horizon-client/src/gfx/mod_views.rs b/crates/horizon-client/src/gfx/mod_views.rs -index c0d886afd..aa2c5b971 100644 ---- a/crates/horizon-client/src/gfx/mod_views.rs -+++ b/crates/horizon-client/src/gfx/mod_views.rs -@@ -194,7 +194,8 @@ impl Views { - views: &'a [ModView], - globals_size: u64, - ) -> Vec<&'a ModView> { -- self.targets.retain(|id, _| views.iter().any(|v| v.id == *id)); -+ self.targets -+ .retain(|id, _| views.iter().any(|v| v.id == *id)); - for v in views { - if self.targets.get(&v.id).is_none_or(|t| t.size != v.size) { - let t = self.target(device, v.size, globals_size); -@@ -318,5 +319,9 @@ pub(super) fn matrices(v: &ModView, eye: DVec3, far: f32) -> (Mat4, Mat4, f32) { - let view = crate::camera::view_matrix(v.yaw, v.pitch); - let projection = super::world_projection(v.fov, aspect, far); - let offset = (eye - v.camera).as_vec3(); -- (projection * view * Mat4::from_translation(offset), view, aspect) -+ ( -+ projection * view * Mat4::from_translation(offset), -+ view, -+ aspect, -+ ) - } -diff --git a/crates/horizon-client/src/terrain.wgsl b/crates/horizon-client/src/terrain.wgsl -index 293729e33..bb5c70f63 100644 ---- a/crates/horizon-client/src/terrain.wgsl -+++ b/crates/horizon-client/src/terrain.wgsl -@@ -74,6 +74,8 @@ struct TerrainOut { - // AirDef fork, HD lighting's G-buffer: the camera-relative position and the unlit colour. - @location(6) pos: vec3, - @location(7) tint: vec4, -+ // Sky and block light, 0 to 1. -+ @location(8) lm: vec2, - } - // Horizon Extras' dynamic lights (dynamic_light.rs): the block light, in the vertex's units - // (16 per level), a vertex at camera-relative `pos` gets from the frame's light sources, one -@@ -132,6 +134,7 @@ fn terrain_vertex(position: vec3, uv: vec2, layer: u32, color: vec4 f32 { -@@ -159,6 +162,7 @@ fn apply_fog(color: vec4, spherical: f32, cylindrical: f32) -> vec4 { - struct GBufferOut { - @location(0) albedo: vec4, - @location(1) normal: vec4, -+ @location(2) light: vec4, - } - fn face_normal(pos: vec3) -> vec3 { - var n = normalize(cross(dpdx(pos), dpdy(pos))); -@@ -171,8 +175,14 @@ fn face_normal(pos: vec3) -> vec3 { - var out: GBufferOut; - out.albedo = vec4(color.rgb, f32(in.emission) / 15.0); - out.normal = vec4(face_normal(in.pos), 1.0); -+ out.light = vec4(clamp(in.lm, vec2(0.0), vec2(1.0)), 0.0, 1.0); - return out; - } -+// AirDef fork: the sun's shadow map — depth only, cut-out leaves leave holes. -+@fragment fn fs_shadow(in: TerrainOut) { -+ let a = sprite(in.uv, in.layer & 0x7fffu).a; -+ if a < 0.5 && (in.layer & 0x8000u) == 0u { discard; } -+} - @fragment fn fs_translucent(in: TerrainOut) -> @location(0) vec4 { - let color = sprite(in.uv, in.layer & 0x7fffu) * in.color; - if color.a < 0.01 { discard; } --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0013-HD-bind-groups-cached-until-a-resize-or-a-new-source.patch b/horizon-patches/0013-HD-bind-groups-cached-until-a-resize-or-a-new-source.patch deleted file mode 100644 index 905a883..0000000 --- a/horizon-patches/0013-HD-bind-groups-cached-until-a-resize-or-a-new-source.patch +++ /dev/null @@ -1,439 +0,0 @@ -From 7a5a38f68923aec62f10bfa0df50b436474ed612 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 16:19:53 +0300 -Subject: [PATCH 13/42] HD: bind groups cached until a resize or a new source, - shadow passes draw only the entities that cast shadows - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx.rs | 39 ++++- - crates/horizon-client/src/gfx/hd.rs | 247 +++++++++++++++++----------- - 2 files changed, 188 insertions(+), 98 deletions(-) - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index e351763fd..f6bbe038c 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -459,6 +459,8 @@ pub struct Renderer { - entity_buffer: wgpu::Buffer, - entity_capacity: u64, - entity_batch: EntityBatch, -+ /// AirDef fork: the runs of entity quads (first, end) that cast shadows this frame. -+ shadow_casters: Vec<(u32, u32)>, - group: wgpu::BindGroup, - globals: wgpu::Buffer, - lightmap: wgpu::Texture, -@@ -2744,6 +2746,7 @@ impl Renderer { - entity_buffer, - entity_capacity, - entity_batch: EntityBatch::default(), -+ shadow_casters: Vec::new(), - group, - globals, - lightmap, -@@ -4980,6 +4983,30 @@ impl Renderer { - } - // The sun's (or moon's) shadow cascades: the world's globals swapped for the light's. - if shadow_strength > 0.0 { -+ // The quads the shadow shader would not discard (overlays 4, 9, 10 and 40: -+ // glowing mod geometry), as runs, so those are not vertex-processed twice. -+ self.shadow_casters.clear(); -+ let mut start = None; -+ let quads = self.entity_batch.combined.as_chunks::<4>().0; -+ for (i, quad) in quads.iter().take(entity_quads as usize).enumerate() { -+ let caster = !matches!(quad[0].layer, 4 | 9 | 10 | 40); -+ match (caster, start) { -+ (true, None) => start = Some(i as u32), -+ (false, Some(first)) => { -+ self.shadow_casters.push((first, i as u32)); -+ start = None; -+ } -+ _ => {} -+ } -+ } -+ if let Some(first) = start { -+ self.shadow_casters.push((first, entity_quads)); -+ } -+ // Many tiny runs cost more in draw calls than they save: draw everything. -+ if self.shadow_casters.len() > 64 { -+ self.shadow_casters.clear(); -+ self.shadow_casters.push((0, entity_quads)); -+ } - self.queue - .write_buffer(&hd.main_globals, 0, bytemuck::bytes_of(&globals)); - for (c, cascade) in hd_cascades.iter().enumerate() { -@@ -5021,11 +5048,13 @@ impl Renderer { - let instance = hd_shadow_first + k as u32; - pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); - } -- if entity_quads > 0 { -+ if !self.shadow_casters.is_empty() { - pass.set_pipeline(&self.shadow_entity); - pass.set_bind_group(0, &self.entity_group, &[]); - pass.set_vertex_buffer(0, self.entity_buffer.slice(..)); -- pass.draw_indexed(0..entity_quads * 6, 0, 0..1); -+ for &(first, end) in &self.shadow_casters { -+ pass.draw_indexed(first * 6..end * 6, 0, 0..1); -+ } - } - } - encoder.copy_buffer_to_buffer(&hd.main_globals, 0, &self.globals, 0, globals_size); -@@ -5586,7 +5615,7 @@ impl Renderer { - ); - } else if let Some((color, depth)) = &scaled - && hd_on -- && let Some(hd) = &self.hd -+ && let Some(hd) = &mut self.hd - { - // AirDef fork: lit by the mods' lights into the HD target, the mod's post pass on - // that, then bloom and the tone curve onto the screen. -@@ -5607,9 +5636,9 @@ impl Renderer { - self.mod_params.1, - ); - post.run(&self.device, &mut encoder, (&hd.lit, depth), &hd.fx); -- hd.finish(&self.device, &mut encoder, &hd.fx, &view_target); -+ hd.finish(&self.device, &mut encoder, hd::HdSource::Fx, &view_target); - } -- _ => hd.finish(&self.device, &mut encoder, &hd.lit, &view_target), -+ _ => hd.finish(&self.device, &mut encoder, hd::HdSource::Lit, &view_target), - } - } else if let Some((color, depth)) = &scaled - && mod_post -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index c36c1795d..fe2634f2c 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -359,6 +359,21 @@ fn target( - Target { view } - } - -+/// The outside views and sampler the lighting group reads. -+type LightKey = ( -+ wgpu::TextureView, -+ wgpu::TextureView, -+ wgpu::TextureView, -+ wgpu::Sampler, -+); -+ -+/// What bloom and the tone curve read: the lit world, or the post pass's output. -+#[derive(Clone, Copy, PartialEq, Eq)] -+pub enum HdSource { -+ Lit, -+ Fx, -+} -+ - pub struct Hd { - size: [u32; 2], - pub albedo: wgpu::TextureView, -@@ -377,6 +392,15 @@ pub struct Hd { - pub lit: wgpu::TextureView, - pub fx: wgpu::TextureView, - bloom: Vec, -+ /// Bind groups, rebuilt only when their views change: `resize` drops all of them, the -+ /// lighting and the source-reading ones are keyed by the views they read. -+ light_group: Option<(LightKey, wgpu::BindGroup)>, -+ prefilter_group: Option<(wgpu::TextureView, wgpu::BindGroup)>, -+ tone_group: Option<(wgpu::TextureView, wgpu::BindGroup)>, -+ /// Reading bloom level k - 1 (for the down pass into level k, index k - 1) and level k (for -+ /// the up pass onto level k - 1, index k - 1). -+ down_groups: Vec, -+ up_groups: Vec, - frame: wgpu::Buffer, - lights: wgpu::Buffer, - sampler: wgpu::Sampler, -@@ -656,6 +680,11 @@ impl Hd { - lit: target(device, "HD lit", [1, 1], HDR).view, - fx: target(device, "HD post", [1, 1], HDR).view, - bloom: Vec::new(), -+ light_group: None, -+ prefilter_group: None, -+ tone_group: None, -+ down_groups: Vec::new(), -+ up_groups: Vec::new(), - frame, - lights, - sampler, -@@ -690,6 +719,16 @@ impl Hd { - target(device, "HD bloom", s, HDR) - }) - .collect(); -+ // Every group names a view that was just replaced. -+ self.light_group = None; -+ self.prefilter_group = None; -+ self.tone_group = None; -+ self.down_groups = (1..self.bloom.len()) -+ .map(|k| self.bloom_group(device, &self.bloom[k - 1].view)) -+ .collect(); -+ self.up_groups = (1..self.bloom.len()) -+ .map(|k| self.bloom_group(device, &self.bloom[k].view)) -+ .collect(); - } - - /// This frame's camera, sun, fog, lights and settings. -@@ -829,73 +868,85 @@ impl Hd { - - /// Lights the world (its colour and depth) into `lit`. - pub fn light( -- &self, -+ &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - world: (&wgpu::TextureView, &wgpu::TextureView), - lightmap: (&wgpu::TextureView, &wgpu::Sampler), - ) { -- let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -- label: Some("HD lighting"), -- layout: &self.light_layout, -- entries: &[ -- wgpu::BindGroupEntry { -- binding: 0, -- resource: self.frame.as_entire_binding(), -- }, -- wgpu::BindGroupEntry { -- binding: 1, -- resource: wgpu::BindingResource::TextureView(world.0), -- }, -- wgpu::BindGroupEntry { -- binding: 2, -- resource: wgpu::BindingResource::TextureView(world.1), -- }, -- wgpu::BindGroupEntry { -- binding: 3, -- resource: wgpu::BindingResource::TextureView(&self.albedo), -- }, -- wgpu::BindGroupEntry { -- binding: 4, -- resource: wgpu::BindingResource::TextureView(&self.normal), -- }, -- wgpu::BindGroupEntry { -- binding: 5, -- resource: wgpu::BindingResource::TextureView(&self.depth), -- }, -- wgpu::BindGroupEntry { -- binding: 6, -- resource: self.lights.as_entire_binding(), -- }, -- wgpu::BindGroupEntry { -- binding: 7, -- resource: wgpu::BindingResource::TextureView(&self.levels), -- }, -- wgpu::BindGroupEntry { -- binding: 8, -- resource: wgpu::BindingResource::TextureView(&self.shadow_array), -- }, -- wgpu::BindGroupEntry { -- binding: 9, -- resource: wgpu::BindingResource::Sampler(&self.shadow_sampler), -- }, -- wgpu::BindGroupEntry { -- binding: 10, -- resource: wgpu::BindingResource::TextureView(lightmap.0), -- }, -- wgpu::BindGroupEntry { -- binding: 11, -- resource: wgpu::BindingResource::Sampler(lightmap.1), -- }, -- ], -- }); -+ let key = ( -+ world.0.clone(), -+ world.1.clone(), -+ lightmap.0.clone(), -+ lightmap.1.clone(), -+ ); -+ if self.light_group.as_ref().is_none_or(|(k, _)| *k != key) { -+ let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("HD lighting"), -+ layout: &self.light_layout, -+ entries: &[ -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: self.frame.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::TextureView(world.0), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 2, -+ resource: wgpu::BindingResource::TextureView(world.1), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 3, -+ resource: wgpu::BindingResource::TextureView(&self.albedo), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 4, -+ resource: wgpu::BindingResource::TextureView(&self.normal), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 5, -+ resource: wgpu::BindingResource::TextureView(&self.depth), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 6, -+ resource: self.lights.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 7, -+ resource: wgpu::BindingResource::TextureView(&self.levels), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 8, -+ resource: wgpu::BindingResource::TextureView(&self.shadow_array), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 9, -+ resource: wgpu::BindingResource::Sampler(&self.shadow_sampler), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 10, -+ resource: wgpu::BindingResource::TextureView(lightmap.0), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 11, -+ resource: wgpu::BindingResource::Sampler(lightmap.1), -+ }, -+ ], -+ }); -+ self.light_group = Some((key, group)); -+ } -+ let Some((_, group)) = &self.light_group else { -+ return; -+ }; - Self::draw( - encoder, - "HD lighting", - &self.lit, - false, - &self.light_pipeline, -- &group, -+ group, - ); - } - -@@ -922,12 +973,53 @@ impl Hd { - - /// Bloom from `source` (the lit world or the post pass's output), then the tone curve onto `screen`. - pub fn finish( -- &self, -+ &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, -- source: &wgpu::TextureView, -+ source: HdSource, - screen: &wgpu::TextureView, - ) { -+ let source = match source { -+ HdSource::Lit => self.lit.clone(), -+ HdSource::Fx => self.fx.clone(), -+ }; -+ if self -+ .prefilter_group -+ .as_ref() -+ .is_none_or(|(v, _)| *v != source) -+ { -+ let group = self.bloom_group(device, &source); -+ self.prefilter_group = Some((source.clone(), group)); -+ } -+ if self.tone_group.as_ref().is_none_or(|(v, _)| *v != source) { -+ let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("HD tone mapping"), -+ layout: &self.tone_layout, -+ entries: &[ -+ wgpu::BindGroupEntry { -+ binding: 0, -+ resource: self.frame.as_entire_binding(), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 1, -+ resource: wgpu::BindingResource::TextureView(&source), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 2, -+ resource: wgpu::BindingResource::TextureView(&self.bloom[0].view), -+ }, -+ wgpu::BindGroupEntry { -+ binding: 3, -+ resource: wgpu::BindingResource::Sampler(&self.sampler), -+ }, -+ ], -+ }); -+ self.tone_group = Some((source, group)); -+ } -+ let (Some((_, prefilter)), Some((_, tone))) = (&self.prefilter_group, &self.tone_group) -+ else { -+ return; -+ }; - // Down: the prefilter into the first level, then each level from the one above. - Self::draw( - encoder, -@@ -935,61 +1027,30 @@ impl Hd { - &self.bloom[0].view, - false, - &self.prefilter, -- &self.bloom_group(device, source), -+ prefilter, - ); - for k in 1..self.bloom.len() { -- let g = self.bloom_group(device, &self.bloom[k - 1].view); - Self::draw( - encoder, - "HD bloom down", - &self.bloom[k].view, - false, - &self.down, -- &g, -+ &self.down_groups[k - 1], - ); - } - // Up: each level added onto the bigger one. - for k in (1..self.bloom.len()).rev() { -- let g = self.bloom_group(device, &self.bloom[k].view); - Self::draw( - encoder, - "HD bloom up", - &self.bloom[k - 1].view, - true, - &self.up, -- &g, -+ &self.up_groups[k - 1], - ); - } -- let group = device.create_bind_group(&wgpu::BindGroupDescriptor { -- label: Some("HD tone mapping"), -- layout: &self.tone_layout, -- entries: &[ -- wgpu::BindGroupEntry { -- binding: 0, -- resource: self.frame.as_entire_binding(), -- }, -- wgpu::BindGroupEntry { -- binding: 1, -- resource: wgpu::BindingResource::TextureView(source), -- }, -- wgpu::BindGroupEntry { -- binding: 2, -- resource: wgpu::BindingResource::TextureView(&self.bloom[0].view), -- }, -- wgpu::BindGroupEntry { -- binding: 3, -- resource: wgpu::BindingResource::Sampler(&self.sampler), -- }, -- ], -- }); -- Self::draw( -- encoder, -- "HD tone mapping", -- screen, -- false, -- &self.tone, -- &group, -- ); -+ Self::draw(encoder, "HD tone mapping", screen, false, &self.tone, tone); - } - } - --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0014-HD-lights-culled-per-16x16-tile-surface-and-halo-lis.patch b/horizon-patches/0014-HD-lights-culled-per-16x16-tile-surface-and-halo-lis.patch deleted file mode 100644 index 3ca0b62..0000000 --- a/horizon-patches/0014-HD-lights-culled-per-16x16-tile-surface-and-halo-lis.patch +++ /dev/null @@ -1,362 +0,0 @@ -From 8020811e57d4b5a9ea1cd72d30ce859dc5095d35 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 16:25:44 +0300 -Subject: [PATCH 14/42] HD: lights culled per 16x16 tile (surface and halo - lists built on the CPU), a naga test for the lighting shaders - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx/hd.rs | 242 +++++++++++++++++++++++++++- - 1 file changed, 236 insertions(+), 6 deletions(-) - -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index fe2634f2c..2776219fc 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -16,6 +16,12 @@ pub const LIGHT_LEVELS: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; - pub const HDR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; - /// The most lights drawn a frame. - pub const MAX_LIGHTS: usize = 512; -+/// The side (pixels) of a tile of the screen with its own short list of lights. -+const TILE: u32 = 16; -+/// The indices all the tile lists may hold together; a tile past it loops over every light. -+const TILE_INDICES: usize = 1 << 19; -+/// A list length that means "every light": the list overflowed. -+const EVERY_LIGHT: u32 = u32::MAX; - /// Bloom's mip levels. - const BLOOM_LEVELS: usize = 6; - -@@ -55,7 +61,7 @@ struct HdFrame { - fog: [f32; 4], - /// x: sky light kept in the shade; y: blocks' light; z: the air's density (rays, halos). - ambient: [f32; 4], -- /// x: shadows on; y, z: the cascades' half sizes. -+ /// x: shadows on; y, z: the cascades' half sizes; w: the light tiles per row. - shadow: [f32; 4], - } - -@@ -123,6 +129,10 @@ struct Light { - @group(0) @binding(9) var shadow_sampler: sampler_comparison; - @group(0) @binding(10) var lightmap: texture_2d; - @group(0) @binding(11) var linear: sampler; -+// Per tile of 16x16 pixels: where its surface list and its halo list start in this array and -+// how long they are (4 words), then the lists of light indices. A length of 0xffffffff means -+// every light. -+@group(0) @binding(12) var tiles: array; - - // Interleaved gradient noise: a different offset each pixel and frame, for the ray march. - fn ign(px: vec2) -> f32 { -@@ -181,6 +191,13 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let sun = frame.sun.xyz; - let sun_on = frame.sun.w; - let count = u32(frame.params.x); -+ let tile = ((u32(px.y) >> 4u) * u32(frame.shadow.w) + (u32(px.x) >> 4u)) * 4u; -+ let surface_start = tiles[tile]; -+ let surface_all = tiles[tile + 1u] == 0xffffffffu; -+ let surface_count = select(tiles[tile + 1u], count, surface_all); -+ let halo_start = tiles[tile + 2u]; -+ let halo_all = tiles[tile + 3u] == 0xffffffffu; -+ let halo_count = select(tiles[tile + 3u], count, halo_all); - var c = scene; - let surface = d > 0.0 && abs(d - g) <= max(d, g) * 0.002; - if surface { -@@ -199,8 +216,8 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let direct = frame.sun_color.rgb * sun_on * max(ndl, 0.0) * s * sky_reach; - var light = max(base * frame.ambient.x, block * frame.ambient.y) + direct; - // The mods' lights. -- for (var i = 0u; i < count; i++) { -- let l = lights[i]; -+ for (var k = 0u; k < surface_count; k++) { -+ let l = lights[select(tiles[surface_start + k], k, surface_all)]; - let to = l.pos.xyz - p; - let dist = length(to); - if dist >= l.pos.w { continue; } -@@ -233,8 +250,8 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let phase = (1.0 - gk * gk) / pow(1.0 + gk * gk - 2.0 * gk * cos_t, 1.5) * 0.08; - c += frame.sun_color.rgb * sun_on * (acc / f32(steps)) * phase * (1.0 - exp(-far * density)); - } -- for (var i = 0u; i < count; i++) { -- let l = lights[i]; -+ for (var k = 0u; k < halo_count; k++) { -+ let l = lights[select(tiles[halo_start + k], k, halo_all)]; - // Nearest point of the view ray to the light, and the glow along the ray in front of it. - let t0 = dot(l.pos.xyz, view); - let hh = length(l.pos.xyz - view * t0) + 0.5; -@@ -403,6 +420,11 @@ pub struct Hd { - up_groups: Vec, - frame: wgpu::Buffer, - lights: wgpu::Buffer, -+ /// The tiles' light lists (see `tile_lists`), sized for the screen, and the tiles per row. -+ tiles: wgpu::Buffer, -+ tiles_x: u32, -+ tile_scratch: Vec>, -+ tile_words: Vec, - sampler: wgpu::Sampler, - light_layout: wgpu::BindGroupLayout, - light_pipeline: wgpu::RenderPipeline, -@@ -414,6 +436,137 @@ pub struct Hd { - tone: wgpu::RenderPipeline, - } - -+/// The tiles' header (4 words each) and index lists for a screen of `size`. -+fn tiles_buffer(device: &wgpu::Device, size: [u32; 2]) -> wgpu::Buffer { -+ let tiles = (size[0].div_ceil(TILE).max(1) * size[1].div_ceil(TILE).max(1)) as usize; -+ device.create_buffer(&wgpu::BufferDescriptor { -+ label: Some("HD light tiles"), -+ size: ((tiles * 4 + TILE_INDICES) * std::mem::size_of::()) as u64, -+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, -+ mapped_at_creation: false, -+ }) -+} -+ -+/// Where a camera-relative sphere can show on the screen. -+enum Cover { -+ /// Nowhere: it is wholly behind the camera. -+ Nothing, -+ /// Anywhere: it crosses the camera's plane, or holds the camera. -+ Everywhere, -+ /// Inside this pixel rectangle (min x, min y, max x, max y). -+ Rect([f32; 4]), -+} -+ -+/// The screen cover of the sphere at `c` of radius `r`: the projection of the 8 corners of the -+/// box round it. A box is convex and so is its projection while every corner is in front of the -+/// camera, so the rectangle holds the whole sphere. With some corners behind, the projection -+/// is not bounded and the cover is the whole screen; with all of them behind, no pixel ray -+/// (they all point forward) can meet the sphere. -+fn cover(view_proj: &Mat4, c: glam::Vec3, r: f32, size: [u32; 2]) -> Cover { -+ if !c.is_finite() || !r.is_finite() { -+ return Cover::Everywhere; -+ } -+ // A little more than the shader's radius, against rounding. -+ let r = r * 1.01 + 0.01; -+ let (mut lo, mut hi) = (glam::Vec2::splat(f32::MAX), glam::Vec2::splat(f32::MIN)); -+ let mut front = 0; -+ for corner in 0..8 { -+ let o = glam::Vec3::new( -+ if corner & 1 == 0 { -r } else { r }, -+ if corner & 2 == 0 { -r } else { r }, -+ if corner & 4 == 0 { -r } else { r }, -+ ); -+ let h = *view_proj * (c + o).extend(1.0); -+ if h.w > 0.0 { -+ front += 1; -+ let ndc = glam::Vec2::new(h.x, h.y) / h.w; -+ let px = glam::Vec2::new( -+ (ndc.x * 0.5 + 0.5) * size[0] as f32, -+ (0.5 - ndc.y * 0.5) * size[1] as f32, -+ ); -+ lo = lo.min(px); -+ hi = hi.max(px); -+ } -+ } -+ match front { -+ 0 => Cover::Nothing, -+ 8 => Cover::Rect([lo.x - 2.0, lo.y - 2.0, hi.x + 2.0, hi.y + 2.0]), -+ _ => Cover::Everywhere, -+ } -+} -+ -+/// Fills `words` with each 16x16 tile's two lists of the lights that can reach it: header -+/// (surface start, surface length, halo start, halo length) per tile, then the indices; a -+/// length of `EVERY_LIGHT` stands for all of `lights` (a list past the buffer's room). The -+/// surface list holds the lights whose sphere can touch what the tile sees. The halo list -+/// holds the lights whose halo can reach a ray of the tile: the shader adds a halo for the -+/// view *line* passing within half the radius of the light, so a ray pointing away from a light -+/// behind it is lit too, and the mirrored sphere is covered as well. Lights stay in order, so -+/// the sums are the ones of the full loop. -+fn tile_lists( -+ view_proj: &Mat4, -+ lights: &[HdLight], -+ size: [u32; 2], -+ scratch: &mut Vec>, -+ words: &mut Vec, -+) { -+ let tx = size[0].div_ceil(TILE).max(1) as usize; -+ let ty = size[1].div_ceil(TILE).max(1) as usize; -+ let tiles = tx * ty; -+ scratch.resize_with(tiles * 2, Vec::new); -+ scratch.truncate(tiles * 2); -+ scratch.iter_mut().for_each(Vec::clear); -+ let mut add = |list: usize, area: Cover, light: u16| match area { -+ Cover::Nothing => {} -+ Cover::Everywhere => { -+ for t in 0..tiles { -+ scratch[t * 2 + list].push(light); -+ } -+ } -+ Cover::Rect([x0, y0, x1, y1]) => { -+ let tile = TILE as f32; -+ if x1 < 0.0 || y1 < 0.0 || x0 >= size[0] as f32 || y0 >= size[1] as f32 { -+ return; -+ } -+ let from = |v: f32, n: usize| ((v / tile).floor().max(0.0) as usize).min(n - 1); -+ for y in from(y0, ty)..=from(y1, ty) { -+ for x in from(x0, tx)..=from(x1, tx) { -+ scratch[(y * tx + x) * 2 + list].push(light); -+ } -+ } -+ } -+ }; -+ for (i, l) in lights.iter().enumerate() { -+ let c = glam::Vec3::new(l.pos[0], l.pos[1], l.pos[2]); -+ add(0, cover(view_proj, c, l.pos[3], size), i as u16); -+ // The shader's halo reach: the line within `radius / 2 - 0.5` of the light. -+ let reach = l.pos[3] * 0.5 - 0.5; -+ if reach > 0.0 || !reach.is_finite() { -+ add(1, cover(view_proj, c, reach, size), i as u16); -+ add(1, cover(view_proj, -c, reach, size), i as u16); -+ } -+ } -+ words.clear(); -+ words.resize(tiles * 4, 0); -+ let mut room = TILE_INDICES; -+ for t in 0..tiles { -+ for list in 0..2 { -+ let ids = &mut scratch[t * 2 + list]; -+ // Both the sphere and its mirror can name a light: once is enough. -+ ids.dedup(); -+ let at = t * 4 + list * 2; -+ if ids.len() > room { -+ words[at + 1] = EVERY_LIGHT; -+ } else { -+ room -= ids.len(); -+ words[at] = words.len() as u32; -+ words[at + 1] = ids.len() as u32; -+ words.extend(ids.iter().map(|&i| u32::from(i))); -+ } -+ } -+ } -+} -+ - fn module(device: &wgpu::Device, label: &str, body: &str) -> wgpu::ShaderModule { - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some(label), -@@ -527,6 +680,11 @@ impl Hd { - wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), - FLOAT, - SAMPLER, -+ wgpu::BindingType::Buffer { -+ ty: wgpu::BufferBindingType::Storage { read_only: true }, -+ has_dynamic_offset: false, -+ min_binding_size: None, -+ }, - ]), - }); - let bloom_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -@@ -687,6 +845,10 @@ impl Hd { - up_groups: Vec::new(), - frame, - lights, -+ tiles: tiles_buffer(device, [1, 1]), -+ tiles_x: 1, -+ tile_scratch: Vec::new(), -+ tile_words: Vec::new(), - sampler, - light_layout, - light_pipeline, -@@ -713,6 +875,8 @@ impl Hd { - self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; - self.lit = target(device, "HD lit", size, HDR).view; - self.fx = target(device, "HD post", size, HDR).view; -+ self.tiles = tiles_buffer(device, size); -+ self.tiles_x = size[0].div_ceil(TILE).max(1); - self.bloom = (0..BLOOM_LEVELS) - .map(|k| { - let s = [(size[0] >> (k + 1)).max(1), (size[1] >> (k + 1)).max(1)]; -@@ -743,6 +907,15 @@ impl Hd { - if n > 0 { - queue.write_buffer(&self.lights, 0, bytemuck::cast_slice(&lights[..n])); - } -+ // Written every frame, empty too: the shader reads the counts, not `params.x`. -+ tile_lists( -+ &scene.view_proj, -+ &lights[..n], -+ self.size, -+ &mut self.tile_scratch, -+ &mut self.tile_words, -+ ); -+ queue.write_buffer(&self.tiles, 0, bytemuck::cast_slice(&self.tile_words)); - self.frames = self.frames.wrapping_add(1); - // The sun by day, the moon (opposite) by night, faded out round the horizon. - let (dir, strength, color) = light_of(scene); -@@ -773,7 +946,7 @@ impl Hd { - if strength > 0.0 { 1.0 } else { 0.0 }, - CASCADES[0], - CASCADES[1], -- 0.0, -+ self.tiles_x as f32, - ], - }; - queue.write_buffer(&self.frame, 0, bytemuck::bytes_of(&frame)); -@@ -933,6 +1106,10 @@ impl Hd { - binding: 11, - resource: wgpu::BindingResource::Sampler(lightmap.1), - }, -+ wgpu::BindGroupEntry { -+ binding: 12, -+ resource: self.tiles.as_entire_binding(), -+ }, - ], - }); - self.light_group = Some((key, group)); -@@ -1102,3 +1279,56 @@ pub fn cascades(camera: glam::DVec3, light: glam::Vec3) -> [Mat4; 2] { - (projection * view).as_mat4() - }) - } -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ #[test] -+ fn lighting_shader_parses() { -+ for (name, body) in [("light", LIGHT), ("bloom", BLOOM)] { -+ let source = format!("{COMMON}{body}"); -+ let module = naga::front::wgsl::parse_str(&source).unwrap_or_else(|e| { -+ panic!("{name}: {}", e.emit_to_string(&source)); -+ }); -+ naga::valid::Validator::new( -+ naga::valid::ValidationFlags::all(), -+ naga::valid::Capabilities::all(), -+ ) -+ .validate(&module) -+ .unwrap_or_else(|e| panic!("{name}: {e:?}")); -+ } -+ } -+ -+ #[test] -+ fn tile_lists_keep_lights_that_reach_a_tile() { -+ let size = [320, 160]; -+ let proj = Mat4::perspective_rh(1.0, 2.0, 0.05, 1000.0); -+ let light = |x: f32, y: f32, z: f32, r: f32| HdLight { -+ pos: [x, y, z, r], -+ color: [1.0; 4], -+ }; -+ let lights = [ -+ // Dead ahead and small; far to the side; behind the camera; holding the camera. -+ light(0.0, 0.0, -20.0, 4.0), -+ light(30.0, 0.0, -5.0, 4.0), -+ light(0.0, 0.0, 10.0, 8.0), -+ light(1.0, 0.0, 0.0, 6.0), -+ ]; -+ let (mut scratch, mut words) = (Vec::new(), Vec::new()); -+ tile_lists(&proj, &lights, size, &mut scratch, &mut words); -+ let list = |tile: usize, which: usize| -> Vec { -+ let (start, len) = (words[tile * 4 + which * 2], words[tile * 4 + which * 2 + 1]); -+ words[start as usize..(start + len) as usize].to_vec() -+ }; -+ // The centre tile sees the first light and the one at the camera, in both lists. -+ let centre = 5 * 20 + 10; -+ assert_eq!(list(centre, 0), vec![0, 3]); -+ assert!(list(centre, 1).contains(&0) && list(centre, 1).contains(&3)); -+ // A far corner sees only the one at the camera. -+ assert_eq!(list(0, 0), vec![3]); -+ // The light behind the camera shows in the halo list where the view points away. -+ assert!(list(centre, 1).contains(&2)); -+ assert!(!list(0, 0).contains(&2)); -+ } -+} --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0015-HD-GPU-time-per-pass-in-the-F3-timing-list-half-reso.patch b/horizon-patches/0015-HD-GPU-time-per-pass-in-the-F3-timing-list-half-reso.patch deleted file mode 100644 index 3d31fc0..0000000 --- a/horizon-patches/0015-HD-GPU-time-per-pass-in-the-F3-timing-list-half-reso.patch +++ /dev/null @@ -1,712 +0,0 @@ -From 72d3fddfea329e578162656151350f0aa2efd323 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 16:43:36 +0300 -Subject: [PATCH 15/42] HD: GPU time per pass in the F3 timing list, - half-resolution GTAO with a depth-aware blur - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx.rs | 23 +- - crates/horizon-client/src/gfx/hd.rs | 430 +++++++++++++++++++++++++++- - 2 files changed, 443 insertions(+), 10 deletions(-) - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index f6bbe038c..e14429b03 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -3725,9 +3725,20 @@ impl Renderer { - (_, Some(native)) => native.timing.as_ref(), - _ => None, - }; -- timer -+ let mut times = timer - .map(pack_timing::PassTiming::medians) -- .unwrap_or_default() -+ .unwrap_or_default(); -+ // AirDef fork: the HD lighting's passes (shadows, G-buffer, occlusion, lighting, bloom, -+ // tone curve) while it is on. -+ if let Some(hd) = &self.hd { -+ times.extend(hd.pass_times()); -+ } -+ times -+ } -+ -+ /// AirDef fork: bind groups the HD lighting made in the last frame (0 in a steady state). -+ pub fn hd_bind_groups_last_frame(&self) -> Option { -+ self.hd.as_ref().map(|hd| hd.groups_last_frame) - } - /// The dimension the shader pack was last set for. - pub fn shader_pack_dimension(&self) -> Option { -@@ -4981,6 +4992,8 @@ impl Renderer { - if let Some(settings) = frame.hd { - hd.write(&self.queue, &hd_scene, frame.hd_lights, settings); - } -+ hd.set_timing(device, &self.queue, self.pass_timing); -+ hd.timing_begin(device, &self.queue); - // The sun's (or moon's) shadow cascades: the world's globals swapped for the light's. - if shadow_strength > 0.0 { - // The quads the shadow shader would not discard (overlays 4, 9, 10 and 40: -@@ -5090,6 +5103,9 @@ impl Renderer { - pass.set_vertex_buffer(0, self.entity_buffer.slice(..)); - pass.draw_indexed(0..entity_quads * 6, 0, 0..1); - } -+ drop(pass); -+ // Contact shadows from the G-buffer's depth and normals, for the lighting. -+ hd.ao(&mut encoder); - } else if !hd_on { - self.hd = None; - } -@@ -5831,6 +5847,9 @@ impl Renderer { - if let Some(Some(timer)) = Self::pass_timer(&mut self.pack_post, &mut self.native) { - timer.submitted(&self.queue); - } -+ if hd_on && let Some(hd) = &mut self.hd { -+ hd.timing_submitted(&self.queue); -+ } - if let Some((path, buffer)) = readback { - self.screenshot_result = Some( - self.write_screenshot(&path, &buffer) -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index 2776219fc..d3944e97b 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -5,7 +5,9 @@ - //! into a high-dynamic-range target; the mod's post pass runs on that; then - //! bright parts bloom and a tone curve brings it to the screen — vanilla's - //! range (up to about 0.8) left as it is, only what is brighter rolled off. -+use super::pack_timing::PassTiming; - use glam::Mat4; -+use std::sync::atomic::{AtomicU64, Ordering}; - - /// The G-buffer's formats. - pub const ALBEDO: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; -@@ -24,6 +26,25 @@ const TILE_INDICES: usize = 1 << 19; - const EVERY_LIGHT: u32 = u32::MAX; - /// Bloom's mip levels. - const BLOOM_LEVELS: usize = 6; -+/// The ambient occlusion's targets (half resolution): occlusion and the distance it was taken at. -+pub const AO_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg16Float; -+/// The occlusion's default strength (`Hd::ao_strength`; 0 turns it off). -+pub const AO_STRENGTH: f32 = 1.0; -+/// The timed passes of bloom, by level (each is timed on its own; the report sums them). -+const BLOOM_NAMES: [&str; 2 * BLOOM_LEVELS] = [ -+ "hd bloom#0", -+ "hd bloom#1", -+ "hd bloom#2", -+ "hd bloom#3", -+ "hd bloom#4", -+ "hd bloom#5", -+ "hd bloom#6", -+ "hd bloom#7", -+ "hd bloom#8", -+ "hd bloom#9", -+ "hd bloom#10", -+ "hd bloom#11", -+]; - - /// A light for this frame, relative to the camera. - #[repr(C)] -@@ -59,7 +80,8 @@ struct HdFrame { - fog_color: [f32; 4], - /// Environmental start, end; render-distance start, end. - fog: [f32; 4], -- /// x: sky light kept in the shade; y: blocks' light; z: the air's density (rays, halos). -+ /// x: sky light kept in the shade; y: blocks' light; z: the air's density (rays, halos); -+ /// w: the ambient occlusion's strength (0 off). - ambient: [f32; 4], - /// x: shadows on; y, z: the cascades' half sizes; w: the light tiles per row. - shadow: [f32; 4], -@@ -133,6 +155,34 @@ struct Light { - // how long they are (4 words), then the lists of light indices. A length of 0xffffffff means - // every light. - @group(0) @binding(12) var tiles: array; -+// Ambient occlusion at half resolution: x the occlusion (1 open), y the distance it was taken at. -+@group(0) @binding(13) var ao_tex: texture_2d; -+ -+// The occlusion at a full-resolution pixel `dist` away: the four nearest half-resolution texels -+// weighted by closeness and by how near their distance is, so it doesn't bleed across edges. -+fn ao_at(px: vec2, dist: f32) -> f32 { -+ let size = vec2(textureDimensions(ao_tex)); -+ let f = vec2(px) * 0.5; -+ let base = vec2(floor(f)); -+ let t = f - floor(f); -+ var sum = 0.0; -+ var total = 0.0; -+ for (var j = 0; j < 2; j++) { -+ for (var i = 0; i < 2; i++) { -+ let q = clamp(base + vec2(i, j), vec2(0), size - 1); -+ let s = textureLoad(ao_tex, q, 0).xy; -+ let bw = select(1.0 - t.x, t.x, i == 1) * select(1.0 - t.y, t.y, j == 1); -+ let dz = (s.y - dist) / (0.03 * dist + 0.1); -+ let w = select(0.0, bw / (1.0 + dz * dz * dz * dz), s.y > 0.0); -+ sum += s.x * w; -+ total += w; -+ } -+ } -+ if total < 0.0001 { -+ return textureLoad(ao_tex, clamp(px / 2, vec2(0), size - 1), 0).x; -+ } -+ return sum / total; -+} - - // Interleaved gradient noise: a different offset each pixel and frame, for the ray march. - fn ign(px: vec2) -> f32 { -@@ -214,7 +264,13 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - // Shadowed sky light (ambient) keeps vanilla's look, blocks' light stays as is; the sun adds - // direct light by the face's angle. - let direct = frame.sun_color.rgb * sun_on * max(ndl, 0.0) * s * sky_reach; -- var light = max(base * frame.ambient.x, block * frame.ambient.y) + direct; -+ // Contact shadows from the ambient occlusion: on the ambient and blocks' light fully, on -+ // the mods' lights by half, not on the sun or on emission. -+ var occlusion = 1.0; -+ if frame.ambient.w > 0.0 { occlusion = ao_at(px, length(p)); } -+ let ao_ambient = mix(1.0, occlusion, frame.ambient.w); -+ let ao_lights = mix(1.0, occlusion, frame.ambient.w * 0.5); -+ var light = max(base * frame.ambient.x, block * frame.ambient.y) * ao_ambient + direct; - // The mods' lights. - for (var k = 0u; k < surface_count; k++) { - let l = lights[select(tiles[surface_start + k], k, surface_all)]; -@@ -224,7 +280,7 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let window = pow(clamp(1.0 - pow(dist / l.pos.w, 4.0), 0.0, 1.0), 2.0); - let falloff = l.color.w / (dist * dist + 1.0) * window; - let k = max(dot(n, to / max(dist, 0.001)) * 0.85 + 0.15, 0.0); -- light += l.color.rgb * falloff * k; -+ light += l.color.rgb * falloff * k * ao_lights; - } - c = albedo.rgb * light + albedo.rgb * albedo.a * 1.5; - // Vanilla's fog over it again. -@@ -264,6 +320,131 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - } - "#; - -+const AO: &str = r#" -+const AO_RADIUS: f32 = 1.8; -+const AO_SLICES: i32 = 3; -+const AO_STEPS: i32 = 5; -+const PI: f32 = 3.14159265; -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var depth_tex: texture_depth_2d; -+@group(0) @binding(2) var normal_tex: texture_2d; -+ -+// Interleaved gradient noise, shifted per frame (params.w) and per use. -+fn noise(px: vec2, shift: vec2) -> f32 { -+ let p = px + shift + vec2(5.588238 * frame.params.w); -+ return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); -+} -+ -+// The camera-relative position of a (continuous) full-resolution pixel at depth z. -+fn position_at(px: vec2, z: f32) -> vec3 { -+ let uv = px * frame.resolution.zw; -+ let h = frame.inverse_view_proj * vec4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, z, 1.0); -+ return h.xyz / h.w; -+} -+ -+// Ground-truth ambient occlusion (horizon based): per slice through the view ray, the highest -+// horizon on either side, integrated against the normal's cosine lobe. Half resolution: one -+// pixel of each 2x2. -+@fragment -+fn fs_ao(in: FullOut) -> @location(0) vec2 { -+ let size = vec2(textureDimensions(depth_tex)); -+ let ic = min(vec2(in.position.xy) * 2, size - 1); -+ let z = textureLoad(depth_tex, ic, 0); -+ if z <= 0.0 { return vec2(1.0, 0.0); } -+ let nrm = textureLoad(normal_tex, ic, 0).xyz; -+ let c = vec2(ic) + 0.5; -+ let p = position_at(c, z); -+ let dist = length(p); -+ if dot(nrm, nrm) < 0.01 { return vec2(1.0, dist); } -+ let n = normalize(nrm); -+ let v = -p / dist; -+ // Blocks per pixel here (the plane of equal depth is square to the view), so how far in -+ // pixels the radius reaches. -+ let per_px = max(length(position_at(c + vec2(4.0, 0.0), z) - p) / 4.0, 0.00001); -+ let reach = min(AO_RADIUS / per_px, 56.0); -+ if reach < 2.0 { return vec2(1.0, dist); } -+ let radius = reach * per_px; -+ let rot = noise(in.position.xy, vec2(0.0)); -+ let jitter = noise(in.position.xy, vec2(37.0, 17.0)); -+ var visibility = 0.0; -+ for (var s = 0; s < AO_SLICES; s++) { -+ let phi = (f32(s) + rot) / f32(AO_SLICES) * PI; -+ let d2 = vec2(cos(phi), sin(phi)); -+ // The slice's direction in space, square to the view ray, and its plane's axis. -+ let q = position_at(c + d2 * 4.0, z) - p; -+ let dir = normalize(q - v * dot(q, v)); -+ let axis = cross(dir, v); -+ let projected = n - axis * dot(n, axis); -+ let plen = length(projected); -+ if plen < 0.0001 { visibility += 1.0; continue; } -+ let cos_n = clamp(dot(projected, v) / plen, -1.0, 1.0); -+ let nang = select(-1.0, 1.0, dot(projected, dir) >= 0.0) * acos(cos_n); -+ var c_neg = -1.0; -+ var c_pos = -1.0; -+ for (var i = 0; i < AO_STEPS; i++) { -+ let t = (f32(i) + jitter) / f32(AO_STEPS); -+ let r = 1.5 + (reach - 1.5) * t * t; -+ for (var side = 0; side < 2; side++) { -+ let sg = select(-1.0, 1.0, side == 1); -+ let ip = vec2(floor(c + d2 * (r * sg))); -+ if any(ip < vec2(0)) || any(ip >= size) { continue; } -+ let sz = textureLoad(depth_tex, ip, 0); -+ if sz <= 0.0 { continue; } -+ let to = position_at(vec2(ip) + 0.5, sz) - p; -+ let len = length(to); -+ // Points on (or under) the surface's own plane don't occlude it. -+ if dot(to, n) < 0.1 * len { continue; } -+ // Far points count less, to nothing at the radius. -+ let fall = clamp((len - radius * 0.6) / (radius * 0.4), 0.0, 1.0); -+ let ch = mix(dot(to / len, v), -1.0, fall); -+ if side == 1 { c_pos = max(c_pos, ch); } else { c_neg = max(c_neg, ch); } -+ } -+ } -+ var h_neg = -acos(clamp(c_neg, -1.0, 1.0)); -+ var h_pos = acos(clamp(c_pos, -1.0, 1.0)); -+ h_neg = nang + max(h_neg - nang, -0.5 * PI); -+ h_pos = nang + min(h_pos - nang, 0.5 * PI); -+ let sn = sin(nang); -+ let arc_neg = cos_n + 2.0 * h_neg * sn - cos(2.0 * h_neg - nang); -+ let arc_pos = cos_n + 2.0 * h_pos * sn - cos(2.0 * h_pos - nang); -+ visibility += plen * 0.25 * (arc_neg + arc_pos); -+ } -+ let ao = clamp(visibility / f32(AO_SLICES), 0.0, 1.0); -+ // Far away it is too coarse to show: fade out. -+ return vec2(mix(1.0, ao, 1.0 - smoothstep(40.0, 96.0, dist)), dist); -+} -+"#; -+ -+const AO_BLUR: &str = r#" -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var raw_ao: texture_2d; -+ -+// A 5x5 blur that only takes texels at about the same distance (so not across an edge). -+@fragment -+fn fs_ao_blur(in: FullOut) -> @location(0) vec2 { -+ let size = vec2(textureDimensions(raw_ao)); -+ let ip = vec2(in.position.xy); -+ let centre = textureLoad(raw_ao, ip, 0).xy; -+ if centre.y <= 0.0 { return centre; } -+ let tolerance = 0.03 * centre.y + 0.1; -+ var sum = 0.0; -+ var total = 0.0; -+ for (var y = -2; y <= 2; y++) { -+ for (var x = -2; x <= 2; x++) { -+ let q = ip + vec2(x, y); -+ if any(q < vec2(0)) || any(q >= size) { continue; } -+ let s = textureLoad(raw_ao, q, 0).xy; -+ if s.y <= 0.0 { continue; } -+ let dz = (s.y - centre.y) / tolerance; -+ let w = 1.0 / (1.0 + dz * dz * dz * dz); -+ sum += s.x * w; -+ total += w; -+ } -+ } -+ return vec2(sum / max(total, 0.0001), centre.y); -+} -+"#; -+ - const BLOOM: &str = r#" - @group(0) @binding(0) var frame: HdFrame; - @group(0) @binding(1) var source: texture_2d; -@@ -434,6 +615,23 @@ pub struct Hd { - up: wgpu::RenderPipeline, - tone_layout: wgpu::BindGroupLayout, - tone: wgpu::RenderPipeline, -+ /// The ambient occlusion: its strength in the lighting, the half-resolution targets (raw, then -+ /// blurred), their passes and bind groups (made at `resize`). -+ pub ao_strength: f32, -+ ao_raw: wgpu::TextureView, -+ ao_blurred: wgpu::TextureView, -+ ao_layout: wgpu::BindGroupLayout, -+ ao_blur_layout: wgpu::BindGroupLayout, -+ ao_pipeline: wgpu::RenderPipeline, -+ ao_blur_pipeline: wgpu::RenderPipeline, -+ ao_group: Option, -+ ao_blur_group: Option, -+ /// GPU time of the HD passes while diagnostics ask for it (`set_timing`). -+ timing: Option, -+ /// Bind groups made so far, and in the last frame (a steady state makes none). -+ groups_made: AtomicU64, -+ groups_seen: u64, -+ pub groups_last_frame: u64, - } - - /// The tiles' header (4 words each) and index lists for a screen of `size`. -@@ -567,6 +765,22 @@ fn tile_lists( - } - } - -+/// The timestamps of the pass `name` while HD passes are timed. -+fn stamp<'a>( -+ timing: &'a mut Option, -+ name: &str, -+) -> Option> { -+ timing.as_mut()?.writes(name) -+} -+ -+/// `HORIZON_HD_AO=0` turns the ambient occlusion off, another number sets its strength. -+fn ao_strength_default() -> f32 { -+ std::env::var("HORIZON_HD_AO") -+ .ok() -+ .and_then(|v| v.parse().ok()) -+ .unwrap_or(AO_STRENGTH) -+} -+ - fn module(device: &wgpu::Device, label: &str, body: &str) -> wgpu::ShaderModule { - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some(label), -@@ -685,8 +899,17 @@ impl Hd { - has_dynamic_offset: false, - min_binding_size: None, - }, -+ UNFILTERED, - ]), - }); -+ let ao_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD ambient occlusion"), -+ entries: &entries(&[UNIFORM, DEPTH, UNFILTERED]), -+ }); -+ let ao_blur_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD ambient occlusion blur"), -+ entries: &entries(&[UNIFORM, UNFILTERED]), -+ }); - let bloom_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("HD bloom"), - entries: &entries(&[UNIFORM, FLOAT, SAMPLER]), -@@ -698,6 +921,26 @@ impl Hd { - let light = module(device, "HD lighting", LIGHT); - let bloom = module(device, "HD bloom", BLOOM); - let tone = module(device, "HD tone mapping", TONEMAP); -+ let ao = module(device, "HD ambient occlusion", AO); -+ let ao_blur = module(device, "HD ambient occlusion blur", AO_BLUR); -+ let ao_pipeline = fullscreen( -+ device, -+ "HD ambient occlusion", -+ &ao_layout, -+ &ao, -+ "fs_ao", -+ AO_FORMAT, -+ None, -+ ); -+ let ao_blur_pipeline = fullscreen( -+ device, -+ "HD ambient occlusion blur", -+ &ao_blur_layout, -+ &ao_blur, -+ "fs_ao_blur", -+ AO_FORMAT, -+ None, -+ ); - let add = wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, -@@ -858,6 +1101,19 @@ impl Hd { - up, - tone_layout, - tone, -+ ao_strength: ao_strength_default(), -+ ao_raw: target(device, "HD AO", [1, 1], AO_FORMAT).view, -+ ao_blurred: target(device, "HD AO blurred", [1, 1], AO_FORMAT).view, -+ ao_layout, -+ ao_blur_layout, -+ ao_pipeline, -+ ao_blur_pipeline, -+ ao_group: None, -+ ao_blur_group: None, -+ timing: None, -+ groups_made: AtomicU64::new(0), -+ groups_seen: 0, -+ groups_last_frame: 0, - }; - hd.resize(device, size); - hd -@@ -875,6 +1131,28 @@ impl Hd { - self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; - self.lit = target(device, "HD lit", size, HDR).view; - self.fx = target(device, "HD post", size, HDR).view; -+ let half = [size[0].div_ceil(2), size[1].div_ceil(2)]; -+ self.ao_raw = target(device, "HD AO", half, AO_FORMAT).view; -+ self.ao_blurred = target(device, "HD AO blurred", half, AO_FORMAT).view; -+ self.ao_group = Some(self.make_group( -+ device, -+ "HD ambient occlusion", -+ &self.ao_layout, -+ &[ -+ wgpu::BindingResource::Buffer(self.frame.as_entire_buffer_binding()), -+ wgpu::BindingResource::TextureView(&self.depth), -+ wgpu::BindingResource::TextureView(&self.normal), -+ ], -+ )); -+ self.ao_blur_group = Some(self.make_group( -+ device, -+ "HD ambient occlusion blur", -+ &self.ao_blur_layout, -+ &[ -+ wgpu::BindingResource::Buffer(self.frame.as_entire_buffer_binding()), -+ wgpu::BindingResource::TextureView(&self.ao_raw), -+ ], -+ )); - self.tiles = tiles_buffer(device, size); - self.tiles_x = size[0].div_ceil(TILE).max(1); - self.bloom = (0..BLOOM_LEVELS) -@@ -917,6 +1195,15 @@ impl Hd { - ); - queue.write_buffer(&self.tiles, 0, bytemuck::cast_slice(&self.tile_words)); - self.frames = self.frames.wrapping_add(1); -+ let made = self.groups_made.load(Ordering::Relaxed); -+ self.groups_last_frame = made - self.groups_seen; -+ self.groups_seen = made; -+ if PassTiming::logging() && self.frames.is_multiple_of(240) { -+ log::info!( -+ "HD bind groups made in the last frame: {} ({made} in all)", -+ self.groups_last_frame -+ ); -+ } - // The sun by day, the moon (opposite) by night, faded out round the horizon. - let (dir, strength, color) = light_of(scene); - let cascades = cascades(scene.camera, dir); -@@ -941,7 +1228,12 @@ impl Hd { - fog_color: scene.fog_color, - fog: scene.fog, - // In the shade by day a surface keeps half its sky light; at night almost all. -- ambient: [0.5 + 0.45 * night, 1.0, 0.0018 + scene.rain * 0.006, 0.0], -+ ambient: [ -+ 0.5 + 0.45 * night, -+ 1.0, -+ 0.0018 + scene.rain * 0.006, -+ self.ao_strength.max(0.0), -+ ], - shadow: [ - if strength > 0.0 { 1.0 } else { 0.0 }, - CASCADES[0], -@@ -954,13 +1246,14 @@ impl Hd { - - /// The shadow pass of cascade `c`: its depth layer cleared, nothing else. - pub fn shadow_pass<'e>( -- &self, -+ &mut self, - encoder: &'e mut wgpu::CommandEncoder, - c: usize, - ) -> wgpu::RenderPass<'e> { - encoder - .begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("HD shadow cascade"), -+ timestamp_writes: stamp(&mut self.timing, ["hd shadow 0", "hd shadow 1"][c.min(1)]), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &self.shadow_layers[c], -@@ -976,7 +1269,10 @@ impl Hd { - } - - /// The G-buffer pass's attachments: albedo and normal cleared, its own depth. -- pub fn gbuffer_pass<'e>(&self, encoder: &'e mut wgpu::CommandEncoder) -> wgpu::RenderPass<'e> { -+ pub fn gbuffer_pass<'e>( -+ &mut self, -+ encoder: &'e mut wgpu::CommandEncoder, -+ ) -> wgpu::RenderPass<'e> { - let clear = |view| { - Some(wgpu::RenderPassColorAttachment { - view, -@@ -991,6 +1287,7 @@ impl Hd { - encoder - .begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("HD G-buffer"), -+ timestamp_writes: stamp(&mut self.timing, "hd g-buffer"), - color_attachments: &[ - clear(&self.albedo), - clear(&self.normal), -@@ -1016,9 +1313,11 @@ impl Hd { - load: bool, - pipeline: &wgpu::RenderPipeline, - group: &wgpu::BindGroup, -+ timestamp_writes: Option>, - ) { - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some(label), -+ timestamp_writes, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: to, - resolve_target: None, -@@ -1054,6 +1353,7 @@ impl Hd { - lightmap.1.clone(), - ); - if self.light_group.as_ref().is_none_or(|(k, _)| *k != key) { -+ self.groups_made.fetch_add(1, Ordering::Relaxed); - let group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("HD lighting"), - layout: &self.light_layout, -@@ -1110,6 +1410,10 @@ impl Hd { - binding: 12, - resource: self.tiles.as_entire_binding(), - }, -+ wgpu::BindGroupEntry { -+ binding: 13, -+ resource: wgpu::BindingResource::TextureView(&self.ao_blurred), -+ }, - ], - }); - self.light_group = Some((key, group)); -@@ -1124,10 +1428,36 @@ impl Hd { - false, - &self.light_pipeline, - group, -+ stamp(&mut self.timing, "hd lighting"), - ); - } - -+ /// A bind group of consecutive bindings 0.., counted (`groups_last_frame`). -+ fn make_group( -+ &self, -+ device: &wgpu::Device, -+ label: &str, -+ layout: &wgpu::BindGroupLayout, -+ resources: &[wgpu::BindingResource<'_>], -+ ) -> wgpu::BindGroup { -+ self.groups_made.fetch_add(1, Ordering::Relaxed); -+ let entries: Vec<_> = resources -+ .iter() -+ .enumerate() -+ .map(|(i, r)| wgpu::BindGroupEntry { -+ binding: i as u32, -+ resource: r.clone(), -+ }) -+ .collect(); -+ device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some(label), -+ layout, -+ entries: &entries, -+ }) -+ } -+ - fn bloom_group(&self, device: &wgpu::Device, source: &wgpu::TextureView) -> wgpu::BindGroup { -+ self.groups_made.fetch_add(1, Ordering::Relaxed); - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("HD bloom"), - layout: &self.bloom_layout, -@@ -1169,6 +1499,7 @@ impl Hd { - self.prefilter_group = Some((source.clone(), group)); - } - if self.tone_group.as_ref().is_none_or(|(v, _)| *v != source) { -+ self.groups_made.fetch_add(1, Ordering::Relaxed); - let group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("HD tone mapping"), - layout: &self.tone_layout, -@@ -1205,6 +1536,7 @@ impl Hd { - false, - &self.prefilter, - prefilter, -+ stamp(&mut self.timing, BLOOM_NAMES[0]), - ); - for k in 1..self.bloom.len() { - Self::draw( -@@ -1214,6 +1546,7 @@ impl Hd { - false, - &self.down, - &self.down_groups[k - 1], -+ stamp(&mut self.timing, BLOOM_NAMES[k]), - ); - } - // Up: each level added onto the bigger one. -@@ -1225,9 +1558,84 @@ impl Hd { - true, - &self.up, - &self.up_groups[k - 1], -+ stamp(&mut self.timing, BLOOM_NAMES[BLOOM_LEVELS + k - 1]), - ); - } -- Self::draw(encoder, "HD tone mapping", screen, false, &self.tone, tone); -+ Self::draw( -+ encoder, -+ "HD tone mapping", -+ screen, -+ false, -+ &self.tone, -+ tone, -+ stamp(&mut self.timing, "hd tonemap"), -+ ); -+ } -+ -+ /// The ambient occlusion from the G-buffer's depth and normals: the half-resolution pass, then -+ /// its blur. Run after the G-buffer pass; nothing when the strength is 0. -+ pub fn ao(&mut self, encoder: &mut wgpu::CommandEncoder) { -+ if self.ao_strength <= 0.0 { -+ return; -+ } -+ let (Some(raw), Some(blur)) = (&self.ao_group, &self.ao_blur_group) else { -+ return; -+ }; -+ Self::draw( -+ encoder, -+ "HD ambient occlusion", -+ &self.ao_raw, -+ false, -+ &self.ao_pipeline, -+ raw, -+ stamp(&mut self.timing, "hd ao"), -+ ); -+ Self::draw( -+ encoder, -+ "HD ambient occlusion blur", -+ &self.ao_blurred, -+ false, -+ &self.ao_blur_pipeline, -+ blur, -+ stamp(&mut self.timing, "hd ao blur"), -+ ); -+ } -+ -+ /// Times the HD passes on the GPU while `on` (and the device can), else not. -+ pub fn set_timing(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, on: bool) { -+ if on != self.timing.is_some() { -+ self.timing = on.then(|| PassTiming::new(device, queue)).flatten(); -+ } -+ } -+ -+ /// At the start of the frame's passes, before the first of them. -+ pub fn timing_begin(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { -+ if let Some(t) = &mut self.timing { -+ t.begin(device, queue); -+ } -+ } -+ -+ /// After the frame is submitted. -+ pub fn timing_submitted(&mut self, queue: &wgpu::Queue) { -+ if let Some(t) = &mut self.timing { -+ t.submitted(queue); -+ } -+ } -+ -+ /// Each HD pass's median GPU time in milliseconds (bloom's passes summed into one). -+ pub fn pass_times(&self) -> Vec<(String, f32)> { -+ let Some(t) = &self.timing else { -+ return Vec::new(); -+ }; -+ let mut out: Vec<(String, f32)> = Vec::new(); -+ for (name, ms) in t.medians() { -+ let name = name.split('#').next().unwrap_or(&name).to_owned(); -+ match out.iter_mut().find(|o| o.0 == name) { -+ Some(o) => o.1 += ms, -+ None => out.push((name, ms)), -+ } -+ } -+ out - } - } - -@@ -1286,7 +1694,13 @@ mod tests { - - #[test] - fn lighting_shader_parses() { -- for (name, body) in [("light", LIGHT), ("bloom", BLOOM)] { -+ for (name, body) in [ -+ ("light", LIGHT), -+ ("bloom", BLOOM), -+ ("tonemap", TONEMAP), -+ ("ao", AO), -+ ("ao blur", AO_BLUR), -+ ] { - let source = format!("{COMMON}{body}"); - let module = naga::front::wgsl::parse_str(&source).unwrap_or_else(|e| { - panic!("{name}: {}", e.emit_to_string(&source)); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0016-HD-shadows-draw-only-sun-facing-quads-and-skip-burie.patch b/horizon-patches/0016-HD-shadows-draw-only-sun-facing-quads-and-skip-burie.patch deleted file mode 100644 index 071b86e..0000000 --- a/horizon-patches/0016-HD-shadows-draw-only-sun-facing-quads-and-skip-burie.patch +++ /dev/null @@ -1,678 +0,0 @@ -From 1fd83a821bf270879d8ece5aa583f200ed920d89 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 17:02:02 +0300 -Subject: [PATCH 16/42] HD: shadows draw only sun-facing quads and skip buried - sections, the far cascade every second frame; TAA (Halton jitter, - reprojection by depth, YCoCg variance clipping) before bloom - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx.rs | 126 +++++++++- - crates/horizon-client/src/gfx/hd.rs | 347 +++++++++++++++++++++++++++- - 2 files changed, 457 insertions(+), 16 deletions(-) - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index e14429b03..eb152667c 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -569,6 +569,11 @@ pub struct Renderer { - shadow_terrain: wgpu::RenderPipeline, - shadow_entity: wgpu::RenderPipeline, - hd_shadow_visible: Vec, -+ /// The far cascade as last drawn (matrix, camera, light) and the frames since. -+ hd_far: Option<(Mat4, glam::DVec3, Vec3, u32)>, -+ /// HD's TAA: frames jittered so far, and the last one's unjittered view-projection and camera. -+ taa_frame: u32, -+ taa_prev: Option<(Mat4, glam::DVec3)>, - hd: Option, - mod_params: (Vec, f32), - /// Time the shader pack's passes on the GPU (F3, or `HORIZON_SHADER_PACK_TIMING`). -@@ -2815,6 +2820,9 @@ impl Renderer { - shadow_terrain, - shadow_entity, - hd_shadow_visible: Vec::new(), -+ hd_far: None, -+ taa_frame: 0, -+ taa_prev: None, - hd: None, - mod_params: (Vec::new(), 0.0), - pass_timing: pack_timing::PassTiming::logging(), -@@ -3954,7 +3962,29 @@ impl Renderer { - frame.block_flicker, - frame.boss_darken, - ); -- let view_proj = self.view_projection(frame); -+ let hd_wanted = !frame.title -+ && frame.hd.is_some() -+ && !self.pack_post.as_ref().is_some_and(|p| p.1.is_some()) -+ && !self.native.as_ref().is_some_and(|n| n.post.is_some()); -+ // AirDef fork: HD's temporal anti-aliasing shifts the whole projection by a sub-pixel -+ // offset each frame; `unjittered` is what the next frame reprojects with. -+ let unjittered = self.view_projection(frame); -+ let taa = hd_wanted && hd::taa_enabled(); -+ let view_proj = if taa { -+ let size = self -+ .world_post -+ .size() -+ .unwrap_or([self.config.width, self.config.height]); -+ let j = hd::jitter(self.taa_frame, size); -+ self.taa_frame = self.taa_frame.wrapping_add(1); -+ Mat4::from_translation(Vec3::new(j[0], j[1], 0.0)) * unjittered -+ } else { -+ unjittered -+ }; -+ let taa_prev = std::mem::replace( -+ &mut self.taa_prev, -+ taa.then_some((unjittered, frame.camera)), -+ ); - if frame.title { - self.panorama.prepare( - &self.queue, -@@ -4158,13 +4188,12 @@ impl Renderer { - } - self.drawn_sections = visible.len() + visible_lods.len(); - // AirDef fork: the HD sun shadows draw every section in the farther cascade's box. -- let hd_wanted = !frame.title -- && frame.hd.is_some() -- && !self.pack_post.as_ref().is_some_and(|p| p.1.is_some()) -- && !self.native.as_ref().is_some_and(|n| n.post.is_some()); -- let hd_scene = hd::HdScene { -+ let mut hd_scene = hd::HdScene { -+ cascades: [Mat4::IDENTITY; 2], - view_proj, -- camera, -+ taa: taa && taa_prev.is_some(), -+ prev_view_proj: taa_prev.map_or(unjittered, |p| p.0), -+ camera_delta: taa_prev.map_or(Vec3::ZERO, |p| (frame.camera - p.1).as_vec3()), - // The world's sun: vanilla turns the sky -90° about Y, so it crosses from east to west. - sun: Vec3::new(-sun_angle.sin(), sun_angle.cos(), 0.0), - dark: 1.0 - frame.sky.sky_light_factor, -@@ -4174,10 +4203,34 @@ impl Renderer { - sky: frame.skybox && !frame.end_sky && !frame.nether_lighting && !frame.underwater, - }; - let (shadow_dir, shadow_strength, _) = hd::light_of(&hd_scene); -- let hd_cascades = hd::cascades(camera, shadow_dir); -+ let mut hd_cascades = hd::cascades(camera, shadow_dir); -+ // The far cascade is redrawn every second frame (it is snapped to texels and the sun is -+ // slow); between, the lighting pass gets the matrix its map was drawn with. -+ let hd_far_fresh = if hd_wanted && shadow_strength > 0.0 { -+ let fresh = match self.hd_far { -+ Some((_, at, dir, age)) if hd_shadow_fast() => { -+ age >= 1 -+ || (camera - at).length_squared() > 48.0 * 48.0 -+ || dir.dot(shadow_dir) < 0.99995 -+ || self.hd.is_none() -+ } -+ _ => true, -+ }; -+ if fresh { -+ self.hd_far = Some((hd_cascades[1], camera, shadow_dir, 0)); -+ } else if let Some((held, _, _, age)) = &mut self.hd_far { -+ hd_cascades[1] = *held; -+ *age += 1; -+ } -+ fresh -+ } else { -+ self.hd_far = None; -+ true -+ }; -+ hd_scene.cascades = hd_cascades; - let mut hd_shadow_visible = std::mem::take(&mut self.hd_shadow_visible); - hd_shadow_visible.clear(); -- if hd_wanted && shadow_strength > 0.0 { -+ if hd_wanted && shadow_strength > 0.0 && hd_far_fresh { - let radius = - ((frame.detail_distance / 16.0) as i32).min((hd::CASCADES[1] / 16.0) as i32 + 2); - let light = Frustum { -@@ -4191,6 +4244,11 @@ impl Renderer { - |_, _| false, - &mut hd_shadow_visible, - ); -+ if hd_shadow_fast() { -+ drop_buried(&mut hd_shadow_visible, |slot| { -+ self.sections.get(slot).solid > 0 -+ }); -+ } - } - // Mods' cameras: their sections round them, all of them (no occlusion), after the shadow's. - let globals_size = std::mem::size_of::() as u64; -@@ -5022,7 +5080,12 @@ impl Renderer { - } - self.queue - .write_buffer(&hd.main_globals, 0, bytemuck::bytes_of(&globals)); -+ // Facing the light only: the nearest surface to it is always one that does. -+ let toward = (hd_shadow_fast() && self.sides_on).then(|| shadow_dir * 1.0e5); - for (c, cascade) in hd_cascades.iter().enumerate() { -+ if c == 1 && !hd_far_fresh { -+ continue; -+ } - let light = Globals { - view_proj: cascade.to_cols_array_2d(), - ..globals -@@ -5059,7 +5122,13 @@ impl Renderer { - page = Some(s.page); - } - let instance = hd_shadow_first + k as u32; -- pass.draw_indexed(0..s.solid * 6, s.start as i32, instance..instance + 1); -+ facing_runs(&s.sides, toward, v.origin, |first, quads| { -+ pass.draw_indexed( -+ 0..quads * 6, -+ s.start as i32 + first as i32 * 4, -+ instance..instance + 1, -+ ); -+ }); - } - if !self.shadow_casters.is_empty() { - pass.set_pipeline(&self.shadow_entity); -@@ -5652,9 +5721,13 @@ impl Renderer { - self.mod_params.1, - ); - post.run(&self.device, &mut encoder, (&hd.lit, depth), &hd.fx); -- hd.finish(&self.device, &mut encoder, hd::HdSource::Fx, &view_target); -+ let source = hd.taa(&self.device, &mut encoder, hd::HdSource::Fx, depth); -+ hd.finish(&self.device, &mut encoder, source, &view_target); -+ } -+ _ => { -+ let source = hd.taa(&self.device, &mut encoder, hd::HdSource::Lit, depth); -+ hd.finish(&self.device, &mut encoder, source, &view_target); - } -- _ => hd.finish(&self.device, &mut encoder, hd::HdSource::Lit, &view_target), - } - } else if let Some((color, depth)) = &scaled - && mod_post -@@ -6055,6 +6128,35 @@ fn facing_runs(sides: &Sides, eye: Option, origin: [f32; 3], mut draw: imp - None => {} - } - } -+/// `HORIZON_HD_SHADOW_FAST=0` draws the HD shadow cascades the slow way (every section, both -+/// faces, the far one every frame), for comparison. -+fn hd_shadow_fast() -> bool { -+ static FAST: std::sync::OnceLock = std::sync::OnceLock::new(); -+ *FAST.get_or_init(|| !std::env::var("HORIZON_HD_SHADOW_FAST").is_ok_and(|v| v == "0")) -+} -+/// Drops the sections that sit well under the ground of their own and all eight neighbouring -+/// columns: shut in by rock, what they hold neither lights nor shades anything the sun reaches. -+fn drop_buried(list: &mut Vec, has_solid: impl Fn(u32) -> bool) { -+ let mut top: std::collections::HashMap<(i32, i32), i32> = std::collections::HashMap::new(); -+ for v in list.iter() { -+ if has_solid(v.slot) { -+ let e = top.entry((v.key.0, v.key.2)).or_insert(i32::MIN); -+ *e = (*e).max(v.key.1); -+ } -+ } -+ list.retain(|v| { -+ let mut lowest = i32::MAX; -+ for dx in -1..=1 { -+ for dz in -1..=1 { -+ match top.get(&(v.key.0 + dx, v.key.2 + dz)) { -+ Some(&t) => lowest = lowest.min(t), -+ None => return true, -+ } -+ } -+ } -+ v.key.1 > lowest - 3 -+ }); -+} - fn frustum(m: Mat4) -> [Vec4; 6] { - let r = |i| m.row(i); - let planes = [ -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index d3944e97b..16610cb02 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -85,14 +85,26 @@ struct HdFrame { - ambient: [f32; 4], - /// x: shadows on; y, z: the cascades' half sizes; w: the light tiles per row. - shadow: [f32; 4], -+ /// The previous frame's camera-relative view-projection without jitter (for TAA). -+ prev_view_proj: [[f32; 4]; 4], -+ /// xyz: how far the camera moved since that frame; w: 1 when the history is usable. -+ taa: [f32; 4], -+ /// x: the history's weight; y: the sharpening (0 without TAA). -+ taa_params: [f32; 4], - } - - /// What the world around looks like this frame (from the renderer's globals). - #[derive(Clone, Copy, Debug)] - pub struct HdScene { -+ /// The shadow cascades' matrices (`cascades`; the far one may be a frame or two old). -+ pub cascades: [Mat4; 2], -+ /// The (jittered) projection the world was drawn with. - pub view_proj: Mat4, -- /// The camera in the world, for steady shadows. -- pub camera: glam::DVec3, -+ /// Last frame's view-projection without jitter, and the camera's move since (TAA). -+ pub prev_view_proj: Mat4, -+ pub camera_delta: glam::Vec3, -+ /// This frame is anti-aliased over time (the projection is jittered). -+ pub taa: bool, - /// Towards the sun, and the sky's darkening (0 day, 1 night). - pub sun: glam::Vec3, - pub dark: f32, -@@ -119,6 +131,9 @@ struct HdFrame { - fog: vec4, - ambient: vec4, - shadow: vec4, -+ prev_view_proj: mat4x4, -+ taa: vec4, -+ taa_params: vec4, - } - struct FullOut { - @builtin(position) position: vec4, -@@ -507,7 +522,21 @@ const TONEMAP: &str = r#" - - @fragment - fn fs_tonemap(in: FullOut) -> @location(0) vec4 { -- var c = textureSampleLevel(hdr, linear, in.uv, 0.0).rgb * frame.params.y; -+ var c = textureSampleLevel(hdr, linear, in.uv, 0.0).rgb; -+ // A slight sharpen after TAA (its history resampling softens): the centre against its four -+ // neighbours, kept within their range so it can't ring. -+ let k = frame.taa_params.y; -+ if k > 0.0 { -+ let t = 1.0 / vec2(textureDimensions(hdr)); -+ let n0 = textureSampleLevel(hdr, linear, in.uv + vec2(t.x, 0.0), 0.0).rgb; -+ let n1 = textureSampleLevel(hdr, linear, in.uv - vec2(t.x, 0.0), 0.0).rgb; -+ let n2 = textureSampleLevel(hdr, linear, in.uv + vec2(0.0, t.y), 0.0).rgb; -+ let n3 = textureSampleLevel(hdr, linear, in.uv - vec2(0.0, t.y), 0.0).rgb; -+ let lo = min(min(n0, n1), min(min(n2, n3), c)); -+ let hi = max(max(n0, n1), max(max(n2, n3), c)); -+ c = clamp(c * (1.0 + 4.0 * k) - (n0 + n1 + n2 + n3) * k, lo, hi); -+ } -+ c = c * frame.params.y; - c += textureSampleLevel(bloom, linear, in.uv, 0.0).rgb * frame.params.z; - // Up to 0.8 as it is (vanilla's look); above it, rolled off towards white by the - // brightest channel, so hues hold instead of clipping. -@@ -528,6 +557,130 @@ fn fs_tonemap(in: FullOut) -> @location(0) vec4 { - } - "#; - -+const TAA: &str = r#" -+struct TaaOut { -+ @location(0) color: vec4, -+ @location(1) history: vec4, -+} -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var source: texture_2d; -+@group(0) @binding(2) var depth_tex: texture_depth_2d; -+@group(0) @binding(3) var history: texture_2d; -+@group(0) @binding(4) var linear: sampler; -+ -+fn to_ycocg(c: vec3) -> vec3 { -+ return vec3(c.r * 0.25 + c.g * 0.5 + c.b * 0.25, c.r * 0.5 - c.b * 0.5, c.g * 0.5 - c.r * 0.25 - c.b * 0.25); -+} -+fn from_ycocg(c: vec3) -> vec3 { -+ let t = c.x - c.z; -+ return vec3(t + c.y, c.x + c.z, t - c.y); -+} -+// Compress the range so a very bright pixel does not dominate the blend or the clamp. -+fn squash(c: vec3) -> vec3 { -+ return c / (1.0 + max(c.r, max(c.g, c.b))); -+} -+fn unsquash(c: vec3) -> vec3 { -+ return c / max(1.0 - max(c.r, max(c.g, c.b)), 0.001); -+} -+ -+// The history at a uv, filtered with a Catmull-Rom spline (five bilinear taps) so that -+// resampling it every frame doesn't blur. -+fn history_color(uv: vec2) -> vec3 { -+ let size = vec2(textureDimensions(history)); -+ let pos = uv * size; -+ let c1 = floor(pos - 0.5) + 0.5; -+ let f = pos - c1; -+ let w0 = f * (-0.5 + f * (1.0 - 0.5 * f)); -+ let w1 = 1.0 + f * f * (-2.5 + 1.5 * f); -+ let w2 = f * (0.5 + f * (2.0 - 1.5 * f)); -+ let w3 = f * f * (-0.5 + 0.5 * f); -+ let w12 = w1 + w2; -+ let t0 = (c1 - 1.0) / size; -+ let t3 = (c1 + 2.0) / size; -+ let t12 = (c1 + w2 / w12) / size; -+ var sum = textureSampleLevel(history, linear, vec2(t12.x, t0.y), 0.0).rgb * (w12.x * w0.y); -+ var total = w12.x * w0.y; -+ sum += textureSampleLevel(history, linear, vec2(t0.x, t12.y), 0.0).rgb * (w0.x * w12.y); -+ total += w0.x * w12.y; -+ sum += textureSampleLevel(history, linear, t12, 0.0).rgb * (w12.x * w12.y); -+ total += w12.x * w12.y; -+ sum += textureSampleLevel(history, linear, vec2(t3.x, t12.y), 0.0).rgb * (w3.x * w12.y); -+ total += w3.x * w12.y; -+ sum += textureSampleLevel(history, linear, vec2(t12.x, t3.y), 0.0).rgb * (w12.x * w3.y); -+ total += w12.x * w3.y; -+ return max(sum / total, vec3(0.0)); -+} -+ -+// Pulls `p` towards the box's centre until it is inside. -+fn clip_to_box(lo: vec3, hi: vec3, p: vec3) -> vec3 { -+ let centre = (lo + hi) * 0.5; -+ let ext = max((hi - lo) * 0.5, vec3(0.00001)); -+ let off = (p - centre) / ext; -+ let m = max(abs(off.x), max(abs(off.y), abs(off.z))); -+ return select(p, centre + (p - centre) / m, m > 1.0); -+} -+ -+// Temporal anti-aliasing: the history, reprojected by depth and the camera's move, clamped to -+// the colours round this pixel (so what moved or was uncovered can't ghost), blended in. -+@fragment -+fn fs_taa(in: FullOut) -> TaaOut { -+ let size = vec2(textureDimensions(source)); -+ let px = vec2(in.position.xy); -+ let d = textureLoad(depth_tex, px, 0); -+ var sum = vec3(0.0); -+ var sum2 = vec3(0.0); -+ var lo = vec3(1.0e9); -+ var hi = vec3(-1.0e9); -+ var centre = vec3(0.0); -+ for (var y = -1; y <= 1; y++) { -+ for (var x = -1; x <= 1; x++) { -+ let q = clamp(px + vec2(x, y), vec2(0), size - 1); -+ let s = to_ycocg(squash(textureLoad(source, q, 0).rgb)); -+ sum += s; -+ sum2 += s * s; -+ lo = min(lo, s); -+ hi = max(hi, s); -+ if x == 0 && y == 0 { centre = s; } -+ } -+ } -+ let sky = d <= 0.0; -+ let ndc = vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, max(d, 0.000001), 1.0); -+ let h = frame.inverse_view_proj * ndc; -+ let p = h.xyz / h.w; -+ let dist = select(length(p), 0.0, sky); -+ var resolved = centre; -+ if frame.taa.w > 0.5 { -+ let prev = frame.prev_view_proj * vec4(select(p + frame.taa.xyz, p, sky), 1.0); -+ if prev.w > 0.0 { -+ let pn = prev.xy / prev.w; -+ let uv = vec2(pn.x * 0.5 + 0.5, 0.5 - pn.y * 0.5); -+ if all(uv > vec2(0.0)) && all(uv < vec2(1.0)) { -+ let hp = clamp(vec2(uv * vec2(size)), vec2(0), size - 1); -+ let seen = textureLoad(history, hp, 0).a; -+ let expected = select(length(p + frame.taa.xyz), 0.0, sky); -+ // The same surface as last frame (not uncovered), by its distance. -+ if abs(seen - expected) <= expected * 0.03 + 0.25 { -+ let mean = sum / 9.0; -+ let sigma = sqrt(max(sum2 / 9.0 - mean * mean, vec3(0.0))); -+ let box_lo = min(max(mean - sigma * 1.25, lo), centre); -+ let box_hi = max(min(mean + sigma * 1.25, hi), centre); -+ let past = clip_to_box(box_lo, box_hi, to_ycocg(squash(history_color(uv)))); -+ // Fast motion trusts the history less. -+ let moved = length((uv - in.uv) * vec2(size)); -+ let keep = mix(frame.taa_params.x, frame.taa_params.x * 0.75, clamp(moved / 4.0, 0.0, 1.0)); -+ resolved = mix(centre, past, keep); -+ } -+ } -+ } -+ } -+ let color = unsquash(from_ycocg(resolved)); -+ var out: TaaOut; -+ out.color = vec4(color, 1.0); -+ out.history = vec4(color, dist); -+ return out; -+} -+"#; -+ - struct Target { - view: wgpu::TextureView, - } -@@ -570,6 +723,35 @@ type LightKey = ( - pub enum HdSource { - Lit, - Fx, -+ /// The anti-aliased result of `taa`. -+ Taa, -+} -+ -+/// TAA is on unless `HORIZON_HD_TAA=0`. -+pub fn taa_enabled() -> bool { -+ static ON: std::sync::OnceLock = std::sync::OnceLock::new(); -+ *ON.get_or_init(|| std::env::var("HORIZON_HD_TAA").map_or(true, |v| v != "0")) -+} -+ -+/// The Halton sequence's `index`-th number in `base`, in (0, 1). -+fn halton(mut index: u32, base: u32) -> f32 { -+ let (mut f, mut r) = (1.0, 0.0); -+ while index > 0 { -+ f /= base as f32; -+ r += f * (index % base) as f32; -+ index /= base; -+ } -+ r -+} -+ -+/// Frame `frame`'s sub-pixel offset of the projection in clip space (a fraction of the -+/// screen's size in x and y), from the Halton(2, 3) sequence over 16 frames. -+pub fn jitter(frame: u32, size: [u32; 2]) -> [f32; 2] { -+ let i = frame % 16 + 1; -+ [ -+ (halton(i, 2) - 0.5) * 2.0 / size[0].max(1) as f32, -+ (halton(i, 3) - 0.5) * 2.0 / size[1].max(1) as f32, -+ ] - } - - pub struct Hd { -@@ -589,6 +771,15 @@ pub struct Hd { - /// The lit world, and the post pass's output. - pub lit: wgpu::TextureView, - pub fx: wgpu::TextureView, -+ /// TAA: the resolved frame, and the two histories (one read, one written, alternating). -+ taa: wgpu::TextureView, -+ history: [wgpu::TextureView; 2], -+ history_ready: bool, -+ history_flip: usize, -+ taa_active: bool, -+ taa_layout: wgpu::BindGroupLayout, -+ taa_pipeline: wgpu::RenderPipeline, -+ taa_groups: Option<((wgpu::TextureView, wgpu::TextureView), [wgpu::BindGroup; 2])>, - bloom: Vec, - /// Bind groups, rebuilt only when their views change: `resize` drops all of them, the - /// lighting and the source-reading ones are keyed by the views they read. -@@ -902,6 +1093,44 @@ impl Hd { - UNFILTERED, - ]), - }); -+ let taa_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD TAA"), -+ entries: &entries(&[UNIFORM, UNFILTERED, DEPTH, FLOAT, SAMPLER]), -+ }); -+ let taa_module = module(device, "HD TAA", TAA); -+ let taa_pipeline = { -+ let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { -+ label: Some("HD TAA"), -+ bind_group_layouts: &[Some(&taa_layout)], -+ ..Default::default() -+ }); -+ let target = Some(wgpu::ColorTargetState { -+ format: HDR, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ }); -+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some("HD TAA"), -+ layout: Some(&layout), -+ vertex: wgpu::VertexState { -+ module: &taa_module, -+ entry_point: Some("vs_full"), -+ compilation_options: Default::default(), -+ buffers: &[], -+ }, -+ fragment: Some(wgpu::FragmentState { -+ module: &taa_module, -+ entry_point: Some("fs_taa"), -+ compilation_options: Default::default(), -+ targets: &[target.clone(), target], -+ }), -+ primitive: Default::default(), -+ depth_stencil: None, -+ multisample: Default::default(), -+ multiview_mask: None, -+ cache: None, -+ }) -+ }; - let ao_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("HD ambient occlusion"), - entries: &entries(&[UNIFORM, DEPTH, UNFILTERED]), -@@ -1080,6 +1309,17 @@ impl Hd { - .view, - lit: target(device, "HD lit", [1, 1], HDR).view, - fx: target(device, "HD post", [1, 1], HDR).view, -+ taa: target(device, "HD TAA", [1, 1], HDR).view, -+ history: [ -+ target(device, "HD TAA history", [1, 1], HDR).view, -+ target(device, "HD TAA history", [1, 1], HDR).view, -+ ], -+ history_ready: false, -+ history_flip: 0, -+ taa_active: false, -+ taa_layout, -+ taa_pipeline, -+ taa_groups: None, - bloom: Vec::new(), - light_group: None, - prefilter_group: None, -@@ -1131,6 +1371,13 @@ impl Hd { - self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; - self.lit = target(device, "HD lit", size, HDR).view; - self.fx = target(device, "HD post", size, HDR).view; -+ self.taa = target(device, "HD TAA", size, HDR).view; -+ self.history = [ -+ target(device, "HD TAA history", size, HDR).view, -+ target(device, "HD TAA history", size, HDR).view, -+ ]; -+ self.history_ready = false; -+ self.taa_groups = None; - let half = [size[0].div_ceil(2), size[1].div_ceil(2)]; - self.ao_raw = target(device, "HD AO", half, AO_FORMAT).view; - self.ao_blurred = target(device, "HD AO blurred", half, AO_FORMAT).view; -@@ -1206,9 +1453,22 @@ impl Hd { - } - // The sun by day, the moon (opposite) by night, faded out round the horizon. - let (dir, strength, color) = light_of(scene); -- let cascades = cascades(scene.camera, dir); -+ let cascades = scene.cascades; - let night = scene.dark.clamp(0.0, 1.0); -+ self.taa_active = scene.taa && taa_enabled(); -+ // A long jump of the camera (a teleport) starts the history over. -+ if !self.taa_active || scene.camera_delta.length() > 64.0 { -+ self.history_ready = false; -+ } - let frame = HdFrame { -+ prev_view_proj: scene.prev_view_proj.to_cols_array_2d(), -+ taa: [ -+ scene.camera_delta.x, -+ scene.camera_delta.y, -+ scene.camera_delta.z, -+ if self.history_ready { 1.0 } else { 0.0 }, -+ ], -+ taa_params: [0.9, if self.taa_active { 0.12 } else { 0.0 }, 0.0, 0.0], - inverse_view_proj: scene.view_proj.inverse().to_cols_array_2d(), - resolution: [ - self.size[0] as f32, -@@ -1489,6 +1749,7 @@ impl Hd { - let source = match source { - HdSource::Lit => self.lit.clone(), - HdSource::Fx => self.fx.clone(), -+ HdSource::Taa => self.taa.clone(), - }; - if self - .prefilter_group -@@ -1572,6 +1833,70 @@ impl Hd { - ); - } - -+ /// Temporal anti-aliasing of `source` (with the world's `depth`): the source it left, to hand -+ /// to `finish` — the same one when TAA is off this frame. -+ pub fn taa( -+ &mut self, -+ device: &wgpu::Device, -+ encoder: &mut wgpu::CommandEncoder, -+ source: HdSource, -+ depth: &wgpu::TextureView, -+ ) -> HdSource { -+ if !self.taa_active { -+ return source; -+ } -+ let input = match source { -+ HdSource::Lit => self.lit.clone(), -+ HdSource::Fx => self.fx.clone(), -+ HdSource::Taa => return source, -+ }; -+ let key = (input, depth.clone()); -+ if self.taa_groups.as_ref().is_none_or(|(k, _)| *k != key) { -+ let groups = [0, 1].map(|read| { -+ self.make_group( -+ device, -+ "HD TAA", -+ &self.taa_layout, -+ &[ -+ wgpu::BindingResource::Buffer(self.frame.as_entire_buffer_binding()), -+ wgpu::BindingResource::TextureView(&key.0), -+ wgpu::BindingResource::TextureView(depth), -+ wgpu::BindingResource::TextureView(&self.history[read]), -+ wgpu::BindingResource::Sampler(&self.sampler), -+ ], -+ ) -+ }); -+ self.taa_groups = Some((key, groups)); -+ } -+ let Some((_, groups)) = &self.taa_groups else { -+ return source; -+ }; -+ let (read, write) = (self.history_flip, 1 - self.history_flip); -+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("HD TAA"), -+ timestamp_writes: stamp(&mut self.timing, "hd taa"), -+ color_attachments: &[&self.taa, &self.history[write]].map(|view| { -+ Some(wgpu::RenderPassColorAttachment { -+ view, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { -+ load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), -+ store: wgpu::StoreOp::Store, -+ }, -+ }) -+ }), -+ ..Default::default() -+ }); -+ pass.set_pipeline(&self.taa_pipeline); -+ pass.set_bind_group(0, &groups[read], &[]); -+ pass.draw(0..3, 0..1); -+ drop(pass); -+ self.history_flip = write; -+ self.history_ready = true; -+ HdSource::Taa -+ } -+ - /// The ambient occlusion from the G-buffer's depth and normals: the half-resolution pass, then - /// its blur. Run after the G-buffer pass; nothing when the strength is 0. - pub fn ao(&mut self, encoder: &mut wgpu::CommandEncoder) { -@@ -1698,6 +2023,7 @@ mod tests { - ("light", LIGHT), - ("bloom", BLOOM), - ("tonemap", TONEMAP), -+ ("taa", TAA), - ("ao", AO), - ("ao blur", AO_BLUR), - ] { -@@ -1714,6 +2040,19 @@ mod tests { - } - } - -+ #[test] -+ fn jitter_stays_within_a_pixel() { -+ let size = [1920u32, 1080]; -+ let mut seen = std::collections::HashSet::new(); -+ for f in 0..32 { -+ let j = jitter(f, size); -+ assert!(j[0].abs() <= 1.0 / size[0] as f32 + 1e-9); -+ assert!(j[1].abs() <= 1.0 / size[1] as f32 + 1e-9); -+ seen.insert((j[0].to_bits(), j[1].to_bits())); -+ } -+ assert_eq!(seen.len(), 16); -+ } -+ - #[test] - fn tile_lists_keep_lights_that_reach_a_tile() { - let size = [320, 160]; --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0017-Mods-model-handles-render.model-id-model-generation-.patch b/horizon-patches/0017-Mods-model-handles-render.model-id-model-generation-.patch deleted file mode 100644 index e9523c7..0000000 --- a/horizon-patches/0017-Mods-model-handles-render.model-id-model-generation-.patch +++ /dev/null @@ -1,643 +0,0 @@ -From 1502d359d599228fe0f44ce6b99a3fded2ac5ea0 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 17:02:02 +0300 -Subject: [PATCH 17/42] Mods: model handles (render.model-id, model-generation, - set-models-by-id; API level 11) so mods send no model names each tick; - clear-post and set-lighting documented - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game/mod_models.rs | 21 +- - crates/horizon-client/src/game/mods.rs | 10 + - crates/horizon-mod-sdk/wit/horizon.wit | 31 +++ - crates/horizon-mods/src/api_level.rs | 7 +- - crates/horizon-mods/src/runtime.rs | 212 +++++++++++++++---- - docs/api/mods-reference.md | 113 ++++++++++ - 6 files changed, 351 insertions(+), 43 deletions(-) - -diff --git a/crates/horizon-client/src/game/mod_models.rs b/crates/horizon-client/src/game/mod_models.rs -index 2d1b4780a..f2329a123 100644 ---- a/crates/horizon-client/src/game/mod_models.rs -+++ b/crates/horizon-client/src/game/mod_models.rs -@@ -11,7 +11,7 @@ use { - particles::{ParticleOptions, ParticleType}, - }, - horizon_core::random::RandomSource, -- horizon_mods::{CameraView, ParticleSpawn, PointLight}, -+ horizon_mods::{CameraView, ModelRef, ParticleSpawn, PointLight}, - horizon_world::registry::BlockStateId, - std::collections::HashMap, - }; -@@ -84,10 +84,21 @@ impl Game<'_> { - let registry = &self.pack.assets.registry; - let models = &mut self.mods.models; - for (owner, m) in self.mods.mods.models() { -- let state = *models -- .states -- .entry(m.model.clone()) -- .or_insert_with(|| super::render_control::parse_state(registry, &m.model).ok()); -+ let state = match &m.model { -+ ModelRef::Handle(h) => { -+ let id = BlockStateId(*h); -+ registry.state(id).map(|_| id) -+ } -+ ModelRef::Name(name) => { -+ if let Some(s) = models.states.get(name.as_str()) { -+ *s -+ } else { -+ let s = super::render_control::parse_state(registry, name).ok(); -+ models.states.insert(name.clone(), s); -+ s -+ } -+ } -+ }; - let Some(state) = state else { - continue; - }; -diff --git a/crates/horizon-client/src/game/mods.rs b/crates/horizon-client/src/game/mods.rs -index a760c7763..db468c7cb 100644 ---- a/crates/horizon-client/src/game/mods.rs -+++ b/crates/horizon-client/src/game/mods.rs -@@ -615,6 +615,16 @@ impl Game<'_> { - } - let snapshot = self.mod_snapshot(); - self.mods.mods.set_snapshot(snapshot); -+ let registry = self.pack.assets.registry; -+ self.mods -+ .mods -+ .set_model_resolver(registry as *const _ as usize, || { -+ Box::new(move |name| { -+ super::render_control::parse_state(registry, name) -+ .ok() -+ .map(|s| s.0) -+ }) -+ }); - self.mods.mods.tick(self.ticks); - self.update_mod_models(); - self.show_mod_output(); -diff --git a/crates/horizon-mod-sdk/wit/horizon.wit b/crates/horizon-mod-sdk/wit/horizon.wit -index 6696fa134..8019aab95 100644 ---- a/crates/horizon-mod-sdk/wit/horizon.wit -+++ b/crates/horizon-mod-sdk/wit/horizon.wit -@@ -467,6 +467,34 @@ interface render { - /// each id between its last two positions and rotations every frame. - /// An empty list removes them. - set-models: func(models: list); -+ /// A handle for a block state name, for `set-models-by-id` (AirDef -+ /// fork, API level 11): the name crosses once, not every tick. None for -+ /// a name that is no block state of the game's registry (nothing is -+ /// remembered for it). Handles are valid until `model-generation` -+ /// changes (the registry changed: a resource reload, another world's -+ /// data); ask again for every name then. -+ model-id: func(model: string) -> option; -+ /// Moves on whenever the handles `model-id` gave stop being valid. A -+ /// mod reads it before `set-models-by-id` and re-asks when it changed. -+ model-generation: func() -> u32; -+ /// `model-instance` with the model as a handle. -+ record model-instance-id { -+ id: u32, -+ /// From `model-id`. -+ model: u32, -+ x: f64, -+ y: f64, -+ z: f64, -+ rotation: tuple, -+ scale: f32, -+ glow: bool, -+ } -+ /// `set-models` with handles instead of names: the same models, limits -+ /// and interpolation, fewer bytes across the boundary. `generation` is -+ /// the `model-generation` the handles were asked under; if it is not -+ /// the current one the call is ignored and returns false (re-ask the -+ /// handles and call again). Handles the game doesn't know draw nothing. -+ set-models-by-id: func(generation: u32, models: list) -> bool; - /// Particles to spawn on this client (AirDef fork). - record particle-spawn { - /// As `/particle` names the type: `minecraft:flame`, `minecraft:dust`. -@@ -508,6 +536,7 @@ interface render { - /// Compile errors come back in the chat and the log; the last good pass - /// keeps running. - set-post: func(wgsl: string); -+ /// Removes this mod's post pass: the world is shown as the game draws it. - clear-post: func(); - /// The pass's `frame.params` (up to 1024 floats, as 256 vec4), kept until - /// replaced; call it once a tick (the pass gets the partial tick in -@@ -561,6 +590,8 @@ interface render { - /// How much bright things glow (0 none, 1 strong). - bloom: f32, - } -+ /// Switches the HD lighting on or off for this mod's world, with its -+ /// exposure and bloom; off, the world is drawn as the game draws it. - set-lighting: func(lighting: lighting); - } - -diff --git a/crates/horizon-mods/src/api_level.rs b/crates/horizon-mods/src/api_level.rs -index 80ac74b6f..d71396f59 100644 ---- a/crates/horizon-mods/src/api_level.rs -+++ b/crates/horizon-mods/src/api_level.rs -@@ -7,13 +7,13 @@ - //! version's level so the launcher can tell before it installs. - - /// The newest level this Horizon serves. --pub const LEVEL: u32 = 10; -+pub const LEVEL: u32 = 11; - - /// Every function the game offers mods, as `interface.function`, with its - /// level. Level 0 is the contract of Horizon 0.2.67 to 0.2.69. A new - /// function takes the next level and raises [`LEVEL`] (the `api_reference` - /// test fails until it is listed here). --pub const FUNCTIONS: [(&str, u32); 58] = [ -+pub const FUNCTIONS: [(&str, u32); 61] = [ - ("log.write", 0), - ("game.get-player", 0), - ("game.get-clock", 0), -@@ -73,6 +73,9 @@ pub const FUNCTIONS: [(&str, u32); 58] = [ - ("game.world-id", 9), - ("render.set-lights", 10), - ("render.set-lighting", 10), -+ ("render.model-id", 11), -+ ("render.model-generation", 11), -+ ("render.set-models-by-id", 11), - ]; - - /// What the game's program carries so the launcher can read its level -diff --git a/crates/horizon-mods/src/runtime.rs b/crates/horizon-mods/src/runtime.rs -index 57713e228..951ff2e24 100644 ---- a/crates/horizon-mods/src/runtime.rs -+++ b/crates/horizon-mods/src/runtime.rs -@@ -79,9 +79,10 @@ use render_bindings::horizon::mods::input; - pub use render_bindings::horizon::mods::input::{Event as InputEvent, HeldItem, ItemUse}; - /// What capability `render` asks the game to draw (WIT `interface render`). - pub use render_bindings::horizon::mods::render::{ -- Corner as RenderCorner, FarTerrain as ModFarTerrain, FillShape, FrameRate, ItemShape, -- Minimap as ModMinimap, ModelInstance, ParticleSpawn, Point as RenderPoint, QuadShape, -- CameraView, Lighting as ModLighting, PointLight, Rect as RenderRect, Shape, SpriteShape, TextShape, ViewShape, Zoom as ModZoom, -+ CameraView, Corner as RenderCorner, FarTerrain as ModFarTerrain, FillShape, FrameRate, -+ ItemShape, Lighting as ModLighting, Minimap as ModMinimap, ModelInstance, ModelInstanceId, -+ ParticleSpawn, Point as RenderPoint, PointLight, QuadShape, Rect as RenderRect, Shape, -+ SpriteShape, TextShape, ViewShape, Zoom as ModZoom, - }; - use render_bindings::{RenderHost, horizon::mods::render}; - /// Capability `recipes`'s imports (docs/mods.md): the game's recipes and items. -@@ -278,6 +279,42 @@ pub trait WorldView: std::any::Any + Send { - /// The view lent for the current calls, shared by every mod's store. - type WorldSlot = Arc>>>; - -+/// How the game turns a block state name into a handle (`render.model-id`): -+/// None for a name that is no state of its registry. -+pub type ModelResolver = Box Option + Send>; -+ -+/// The handles of `render.model-id`: the game's registry decides which names -+/// have one, so nothing grows with what mods ask; `generation` moves on -+/// when the registry changes and the handles with it. -+#[derive(Default)] -+pub struct ModelTable { -+ key: usize, -+ generation: u32, -+ resolve: Option, -+} -+ -+type ModelSlot = Arc>; -+ -+/// A model as a mod set it: by name, or by a handle of the [`ModelTable`]. -+#[derive(Clone, Debug)] -+pub enum ModelRef { -+ Name(String), -+ Handle(u32), -+} -+ -+/// One world model (`render.set-models` or `set-models-by-id`). -+#[derive(Clone, Debug)] -+pub struct HostModel { -+ pub id: u32, -+ pub model: ModelRef, -+ pub x: f64, -+ pub y: f64, -+ pub z: f64, -+ pub rotation: (f32, f32, f32, f32), -+ pub scale: f32, -+ pub glow: bool, -+} -+ - /// A mod's text panel, drawn every frame until it changes. - #[derive(Clone, Debug)] - pub struct Panel { -@@ -429,7 +466,9 @@ struct State { - /// Capability `render`'s far terrain, when the mod draws one. - far_terrain: Option, - /// Capability `render`'s world models (AirDef fork), drawn until replaced. -- models: Vec, -+ models: Vec, -+ /// The game's block state names -> handles, shared by every mod's store. -+ model_table: ModelSlot, - /// `render.query-ground`'s columns, answered before the next tick. - ground_queries: Vec<(i32, i32)>, - /// The game's answers to them. -@@ -464,6 +503,7 @@ pub struct Mods { - snapshot: Arc, - out: Vec, - world: WorldSlot, -+ model_table: ModelSlot, - /// Moves on whenever a mod starts or stops: the game resends the server - /// mods' commands to players when it changes. - generation: u64, -@@ -492,6 +532,26 @@ impl Mods { - *slot = Some(view); - } - } -+ /// The game's registry for `render.model-id`. `key` names the registry -+ /// (its address): the same key keeps the handles, another one makes -+ /// `model-generation` move on and drops every mod's models. `make` runs -+ /// only then. -+ pub fn set_model_resolver(&mut self, key: usize, make: impl FnOnce() -> ModelResolver) { -+ let Ok(mut t) = self.model_table.lock() else { -+ return; -+ }; -+ if t.key != key || t.resolve.is_none() { -+ t.key = key; -+ t.generation = t.generation.wrapping_add(1); -+ t.resolve = Some(make()); -+ drop(t); -+ for e in &mut self.entries { -+ if let Some(r) = e.run.as_mut() { -+ r.store.data_mut().models.clear(); -+ } -+ } -+ } -+ } - /// The view [`Mods::lend_world`] lent, back to the game. - pub fn take_world(&mut self) -> Option> { - self.world.lock().ok()?.take() -@@ -613,6 +673,7 @@ impl Mods { - .collect(); - let snapshot = self.snapshot.clone(); - let world = self.world.clone(); -+ let model_table = self.model_table.clone(); - let (engine, linker) = self.engine()?; - let component = - Component::new(engine, wasm).map_err(|e| format!("not a component: {e}"))?; -@@ -646,6 +707,7 @@ impl Mods { - minimap: None, - far_terrain: None, - models: Vec::new(), -+ model_table: model_table.clone(), - ground_queries: Vec::new(), - ground: Vec::new(), - post: None, -@@ -1209,7 +1271,7 @@ impl Mods { - } - /// Every running mod's world models (AirDef fork), with the mod's index - /// so ids of different mods don't meet. -- pub fn models(&self) -> impl Iterator { -+ pub fn models(&self) -> impl Iterator { - self.entries.iter().enumerate().flat_map(|(i, e)| { - e.run - .iter() -@@ -1234,7 +1296,9 @@ impl Mods { - } - /// The first running mod's lighting, if it set one. - pub fn lighting(&self) -> Option { -- self.entries.iter().find_map(|e| e.run.as_ref()?.store.data().lighting) -+ self.entries -+ .iter() -+ .find_map(|e| e.run.as_ref()?.store.data().lighting) - } - /// The columns each running mod asked the ground height of, taken. - pub fn take_ground_queries(&mut self) -> Vec<(usize, Vec<(i32, i32)>)> { -@@ -1435,6 +1499,29 @@ fn reason(e: &wasmtime::Error) -> String { - } - } - -+/// The models a mod set, limited: finite, normalised, at most [`MODELS`]. -+fn clean_models(models: impl Iterator) -> Vec { -+ let finite = |v: f64| v.is_finite() && v.abs() < 30_000_000.0; -+ models -+ .filter(|m| finite(m.x) && finite(m.y) && finite(m.z)) -+ .take(MODELS) -+ .map(|mut m| { -+ let (x, y, z, w) = m.rotation; -+ let len = (x * x + y * y + z * z + w * w).sqrt(); -+ m.rotation = if len.is_finite() && len > 1e-6 { -+ (x / len, y / len, z / len, w / len) -+ } else { -+ (0.0, 0.0, 0.0, 1.0) -+ }; -+ m.scale = if m.scale.is_finite() { -+ m.scale.clamp(0.0, MODEL_SCALE) -+ } else { -+ 1.0 -+ }; -+ m -+ }) -+ .collect() -+} - fn clip(text: String) -> String { - clip_to(text, LINE_CHARS) - } -@@ -1949,28 +2036,45 @@ impl render::Host for State { - } - } - fn set_models(&mut self, models: Vec) { -- let finite = |v: f64| v.is_finite() && v.abs() < 30_000_000.0; -- self.models = models -- .into_iter() -- .filter(|m| finite(m.x) && finite(m.y) && finite(m.z)) -- .take(MODELS) -- .map(|mut m| { -- let (x, y, z, w) = m.rotation; -- let len = (x * x + y * y + z * z + w * w).sqrt(); -- m.rotation = if len.is_finite() && len > 1e-6 { -- (x / len, y / len, z / len, w / len) -- } else { -- (0.0, 0.0, 0.0, 1.0) -- }; -- m.scale = if m.scale.is_finite() { -- m.scale.clamp(0.0, MODEL_SCALE) -- } else { -- 1.0 -- }; -- m.model = clip(m.model); -- m -- }) -- .collect(); -+ let models = models.into_iter().map(|m| HostModel { -+ id: m.id, -+ model: ModelRef::Name(clip(m.model)), -+ x: m.x, -+ y: m.y, -+ z: m.z, -+ rotation: m.rotation, -+ scale: m.scale, -+ glow: m.glow, -+ }); -+ self.models = clean_models(models); -+ } -+ fn model_id(&mut self, model: String) -> Option { -+ let t = self.model_table.lock().ok()?; -+ (t.resolve.as_ref()?)(&model) -+ } -+ fn model_generation(&mut self) -> u32 { -+ self.model_table.lock().map_or(0, |t| t.generation) -+ } -+ fn set_models_by_id(&mut self, generation: u32, models: Vec) -> bool { -+ if self -+ .model_table -+ .lock() -+ .map_or(true, |t| t.generation != generation) -+ { -+ return false; -+ } -+ let models = models.into_iter().map(|m| HostModel { -+ id: m.id, -+ model: ModelRef::Handle(m.model), -+ x: m.x, -+ y: m.y, -+ z: m.z, -+ rotation: m.rotation, -+ scale: m.scale, -+ glow: m.glow, -+ }); -+ self.models = clean_models(models); -+ true - } - fn spawn_particles(&mut self, particles: Vec) { - let particles: Vec = particles -@@ -2012,7 +2116,11 @@ impl render::Host for State { - .map(|mut v| { - v.yaw = angle(v.yaw); - v.pitch = angle(v.pitch).clamp(-90.0, 90.0); -- v.fov = if v.fov.is_finite() { v.fov.clamp(1.0, 110.0) } else { 70.0 }; -+ v.fov = if v.fov.is_finite() { -+ v.fov.clamp(1.0, 110.0) -+ } else { -+ 70.0 -+ }; - v.width = v.width.clamp(16, 1920); - v.height = v.height.clamp(16, 1080); - v -@@ -2021,22 +2129,44 @@ impl render::Host for State { - } - fn set_lights(&mut self, lights: Vec) { - let finite = |v: f64| v.is_finite() && v.abs() < 30_000_000.0; -- let unit = |v: f32| if v.is_finite() { v.clamp(0.0, 1.0) } else { 0.0 }; -+ let unit = |v: f32| { -+ if v.is_finite() { -+ v.clamp(0.0, 1.0) -+ } else { -+ 0.0 -+ } -+ }; - self.lights = lights - .into_iter() - .filter(|l| finite(l.x) && finite(l.y) && finite(l.z)) - .take(512) - .map(|mut l| { - l.color = (unit(l.color.0), unit(l.color.1), unit(l.color.2)); -- l.intensity = if l.intensity.is_finite() { l.intensity.clamp(0.0, 100_000.0) } else { 0.0 }; -- l.radius = if l.radius.is_finite() { l.radius.clamp(1.0, 256.0) } else { 1.0 }; -+ l.intensity = if l.intensity.is_finite() { -+ l.intensity.clamp(0.0, 100_000.0) -+ } else { -+ 0.0 -+ }; -+ l.radius = if l.radius.is_finite() { -+ l.radius.clamp(1.0, 256.0) -+ } else { -+ 1.0 -+ }; - l - }) - .collect(); - } - fn set_lighting(&mut self, mut lighting: ModLighting) { -- lighting.exposure = if lighting.exposure.is_finite() { lighting.exposure.clamp(0.05, 8.0) } else { 1.0 }; -- lighting.bloom = if lighting.bloom.is_finite() { lighting.bloom.clamp(0.0, 1.0) } else { 0.0 }; -+ lighting.exposure = if lighting.exposure.is_finite() { -+ lighting.exposure.clamp(0.05, 8.0) -+ } else { -+ 1.0 -+ }; -+ lighting.bloom = if lighting.bloom.is_finite() { -+ lighting.bloom.clamp(0.0, 1.0) -+ } else { -+ 0.0 -+ }; - self.lighting = Some(lighting); - } - fn query_ground(&mut self, columns: Vec<(i32, i32)>) { -@@ -2050,8 +2180,18 @@ impl render::Host for State { - if !(x.is_finite() && y.is_finite() && z.is_finite()) { - return; - } -- let ok = |v: f32, max: f32| if v.is_finite() { v.clamp(0.0, max) } else { 1.0 }; -- let sounds = self.out.iter().filter(|o| matches!(o, Output::Sound { .. })).count(); -+ let ok = |v: f32, max: f32| { -+ if v.is_finite() { -+ v.clamp(0.0, max) -+ } else { -+ 1.0 -+ } -+ }; -+ let sounds = self -+ .out -+ .iter() -+ .filter(|o| matches!(o, Output::Sound { .. })) -+ .count(); - if sounds < 64 { - self.out.push(Output::Sound { - event: clip(event), -diff --git a/docs/api/mods-reference.md b/docs/api/mods-reference.md -index b25ef5b9e..150453ed5 100644 ---- a/docs/api/mods-reference.md -+++ b/docs/api/mods-reference.md -@@ -125,6 +125,7 @@ A block along the player's view. - | --- | --- | --- | - | `get-player` | `func() -> option` | None on the title screen. | - | `get-clock` | `func() -> option` | None on the title screen. | -+| `world-id` | `func() -> option` | Which world is open (AirDef fork): `world:` for a saved world, `server:
` on a server; none on the title screen. Mods keep their state apart by it. | - | `raycast` | `func(distance: f32) -> option` | The first block along the player's view (fluids pass), up to `distance` blocks (at most 1024; past the loaded chunks, across the distant terrain when LOD Distance is on), as of this tick. None when nothing is hit, and on the title screen. | - - ## `chat` -@@ -399,6 +400,16 @@ Any four-cornered shape (a wheel's segment, an arrow): the corners in order roun - | `centered` | `bool` | Centred on `at.x` instead of starting there. | - | `shadow` | `bool` | | - -+### record `view-shape` -+ -+A picture of one of the mod's `set-views` cameras (AirDef fork). -+ -+| Field | Type | | -+| --- | --- | --- | -+| `view` | `u32` | The camera's id; an unknown one draws nothing. | -+| `rect` | `rect` | | -+| `mode` | `u32` | 0 as seen, 1 thermal (white hot), 2 night vision (green). | -+ - ### variant `shape` - - | Case | Payload | | -@@ -408,6 +419,7 @@ Any four-cornered shape (a wheel's segment, an arrow): the corners in order roun - | `sprite` | `sprite-shape` | | - | `item` | `item-shape` | | - | `text` | `text-shape` | | -+| `view` | `view-shape` | | - - ### record `frame-rate` - -@@ -442,6 +454,93 @@ Any four-cornered shape (a wheel's segment, an arrow): the corners in order roun - | `distance` | `u32` | How far the distant terrain reaches, in chunks: 64 to 1024. On integrated graphics other than Apple's the game caps it lower. | - | `reduced` | `bool` | Coarser far tiles, as the Far Detail setting's Reduced: 2 to 4 ms less GPU time per frame on integrated graphics. | - -+### record `model-instance` -+ -+A block model drawn in the world (AirDef fork). -+ -+| Field | Type | | -+| --- | --- | --- | -+| `id` | `u32` | Stays the same while the object lives: the game moves an id smoothly from where the last `set-models` put it. | -+| `model` | `string` | A block state as commands write it (`airdef:shahed`, `minecraft:stone`, `minecraft:furnace[facing=east]`): its block model is drawn. An unknown one draws nothing. | -+| `x` | `f64` | Where the model's centre is, in blocks. | -+| `y` | `f64` | | -+| `z` | `f64` | | -+| `rotation` | `tuple` | Rotation about the model's centre: a quaternion (x, y, z, w). | -+| `scale` | `f32` | Uniform scale, 1.0 a block (0 to 64). | -+| `glow` | `bool` | Drawn at full brightness, whatever the light where it is. | -+ -+### record `model-instance-id` -+ -+`model-instance` with the model as a handle. -+ -+| Field | Type | | -+| --- | --- | --- | -+| `id` | `u32` | | -+| `model` | `u32` | From `model-id`. | -+| `x` | `f64` | | -+| `y` | `f64` | | -+| `z` | `f64` | | -+| `rotation` | `tuple` | | -+| `scale` | `f32` | | -+| `glow` | `bool` | | -+ -+### record `particle-spawn` -+ -+Particles to spawn on this client (AirDef fork). -+ -+| Field | Type | | -+| --- | --- | --- | -+| `particle` | `string` | As `/particle` names the type: `minecraft:flame`, `minecraft:dust`. | -+| `x` | `f64` | | -+| `y` | `f64` | | -+| `z` | `f64` | | -+| `spread` | `tuple` | Gaussian spread round the point, per axis, in blocks. | -+| `velocity` | `tuple` | Every particle's starting velocity, blocks a tick. | -+| `speed` | `f32` | Gaussian speed added to it per axis. | -+| `count` | `u32` | 1 to 256. | -+| `color` | `u32` | 0xRRGGBB for dust and coloured types (0: white). | -+| `scale` | `f32` | Dust size (0.01 to 4). | -+ -+### record `camera-view` -+ -+A camera the game renders the world from besides the player's eye (AirDef fork): the land, the sky and the entities near the player. -+ -+| Field | Type | | -+| --- | --- | --- | -+| `id` | `u32` | Stays the same while the camera lives: the game turns it smoothly between the last two `set-views`. | -+| `x` | `f64` | | -+| `y` | `f64` | | -+| `z` | `f64` | | -+| `yaw` | `f32` | Degrees, as the player's: yaw 0 looks south (+z), pitch 90 down. | -+| `pitch` | `f32` | | -+| `fov` | `f32` | Vertical field of view, degrees (1 to 110). | -+| `width` | `u32` | The picture's size in pixels (16 to 1920 × 16 to 1080). | -+| `height` | `u32` | | -+ -+### record `point-light` -+ -+A light the mod's things give off (AirDef fork): rocket flames, flares, blasts. Drawn by the HD lighting (`set-lighting`). -+ -+| Field | Type | | -+| --- | --- | --- | -+| `id` | `u32` | Stays the same while the light lives: the game moves it smoothly. | -+| `x` | `f64` | | -+| `y` | `f64` | | -+| `z` | `f64` | | -+| `color` | `tuple` | Linear RGB, 0 to 1. | -+| `intensity` | `f32` | Brightness: about what it lights one block away (0 to 100000). | -+| `radius` | `f32` | Nothing past this many blocks (1 to 256). | -+ -+### record `lighting` -+ -+The HD lighting (AirDef fork): the world lit per pixel by the mods' lights, in high dynamic range, with bloom and tone mapping. -+ -+| Field | Type | | -+| --- | --- | --- | -+| `hd` | `bool` | | -+| `exposure` | `f32` | 1 as is; more brightens. | -+| `bloom` | `f32` | How much bright things glow (0 none, 1 strong). | -+ - | Function | Signature | What it does | - | --- | --- | --- | - | `set-hud` | `func(shapes: list)` | Replaces the mod's HUD shapes: up to 1024, drawn every frame in order over the HUD (after the hotbar, under chat and screens), hidden with the HUD (F1). An empty list removes them. | -@@ -454,6 +553,20 @@ Any four-cornered shape (a wheel's segment, an arrow): the corners in order roun - | `set-zoom` | `func(zoom: zoom)` | Zooms the camera until the next call (`factor` 1 ends it), only while playing: a screen opening or the spyglass's own view (which wins) leave the camera vanilla's. With several mods, the strongest zoom counts. | - | `set-minimap` | `func(minimap: option)` | Shows a map of the world around the player in `corner`, stacked with the game's overlays and the mods' panels, until the next call (none hides it). It looks like a map item: vanilla's map colours with their height shading and water depth, and the player's arrow. Underground, under a roof and in the Nether it maps the floor around the player's height. The game keeps the map's tiles and refreshes them within its own frame budget; hidden, they are dropped. One map shows at a time: the first loaded mod's. | - | `set-far-terrain` | `func(far-terrain: option)` | Draws the world beyond the render distance as distant terrain, out to `distance` chunks, until the next call (none ends it). The game builds it on its own workers, from the chunks already loaded or saved and, where nobody has been, from the world's generator; the simulation distance doesn't change. Only worlds whose generator the game has (single player and a LAN world you host) show it. The player's own LOD Distance setting, when on, wins. One far terrain shows at a time: the first loaded mod's. | -+| `set-models` | `func(models: list)` | Replaces the mod's models: up to 4096, drawn with the world's entities until replaced. Call it once a tick: the game interpolates each id between its last two positions and rotations every frame. An empty list removes them. | -+| `model-id` | `func(model: string) -> option` | A handle for a block state name, for `set-models-by-id` (AirDef fork, API level 11): the name crosses once, not every tick. None for a name that is no block state of the game's registry (nothing is remembered for it). Handles are valid until `model-generation` changes (the registry changed: a resource reload, another world's data); ask again for every name then. | -+| `model-generation` | `func() -> u32` | Moves on whenever the handles `model-id` gave stop being valid. A mod reads it before `set-models-by-id` and re-asks when it changed. | -+| `set-models-by-id` | `func(generation: u32, models: list) -> bool` | `set-models` with handles instead of names: the same models, limits and interpolation, fewer bytes across the boundary. `generation` is the `model-generation` the handles were asked under; if it is not the current one the call is ignored and returns false (re-ask the handles and call again). Handles the game doesn't know draw nothing. | -+| `spawn-particles` | `func(particles: list)` | Spawns particles on this client only: no command, no server. At most 512 spawns a call. | -+| `play-sound` | `func(event: string, x: f64, y: f64, z: f64, volume: f32, pitch: f32)` | Plays a sound event (`minecraft:entity.generic.explode`, `:`) at a point on this client. | -+| `query-ground` | `func(columns: list>)` | Asks for the ground height of these columns (x, z): at most 512 a tick, answered by `ground` from the next tick on. | -+| `ground` | `func() -> list>` | The answers to the last tick's `query-ground`: (x, z, y), y one above the highest block that blocks motion or holds a fluid, as the motion blocking heightmap; -64 where the chunk isn't loaded. | -+| `set-post` | `func(wgsl: string)` | A full-screen post pass (AirDef fork): WGSL defining `fn post(uv: vec2, pixel: vec2) -> vec4`, after a prelude with `frame` (camera matrices, time, `params`), the world's colour and depth and helpers (`scene`, `depth-at`, `view-dir`, `scene-distance`, `project`). It runs after the world, before the hand and the HUD. Compile errors come back in the chat and the log; the last good pass keeps running. | -+| `clear-post` | `func()` | Removes this mod's post pass: the world is shown as the game draws it. | -+| `set-params` | `func(params: list)` | The pass's `frame.params` (up to 1024 floats, as 256 vec4), kept until replaced; call it once a tick (the pass gets the partial tick in `frame.time.y`). | -+| `set-views` | `func(views: list)` | Replaces the mod's cameras: up to 8. A `view` shape shows one; the game renders up to three a frame, in turn. | -+| `set-lights` | `func(lights: list)` | Replaces the mod's lights: up to 512, interpolated per id between the last two calls. | -+| `set-lighting` | `func(lighting: lighting)` | Switches the HD lighting on or off for this mod's world, with its exposure and bloom; off, the world is drawn as the game draws it. | - - ## `events` - --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0018-HD-temporal-upscaling-HORIZON_HD_SCALE-world-G-buffe.patch b/horizon-patches/0018-HD-temporal-upscaling-HORIZON_HD_SCALE-world-G-buffe.patch deleted file mode 100644 index 31ea9b5..0000000 --- a/horizon-patches/0018-HD-temporal-upscaling-HORIZON_HD_SCALE-world-G-buffe.patch +++ /dev/null @@ -1,527 +0,0 @@ -From 0f9d99b8ad3e4b72e621d84c2396cf1d3d2b7a33 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 17:19:57 +0300 -Subject: [PATCH 18/42] HD: temporal upscaling (HORIZON_HD_SCALE: world, - G-buffer and lighting at a lower size, rebuilt to the window in the TAA - pass); fix flickering shadows near the player (the near cascade lost its - sections on frames the far one was held, the held far matrix ignored the - camera's step, plants' normals turned with the camera); TAA keeps more - history when still - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx.rs | 33 ++- - crates/horizon-client/src/gfx/hd.rs | 211 ++++++++++++++++++-- - crates/horizon-client/src/gfx/world_post.rs | 8 +- - crates/horizon-client/src/terrain.wgsl | 4 + - 4 files changed, 223 insertions(+), 33 deletions(-) - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index eb152667c..b85c6e269 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -3970,13 +3970,15 @@ impl Renderer { - // offset each frame; `unjittered` is what the next frame reprojects with. - let unjittered = self.view_projection(frame); - let taa = hd_wanted && hd::taa_enabled(); -+ let mut jitter_px = [0.0; 2]; - let view_proj = if taa { -- let size = self -- .world_post -- .size() -- .unwrap_or([self.config.width, self.config.height]); -- let j = hd::jitter(self.taa_frame, size); -+ let window = [self.config.width, self.config.height]; -+ let size = self.world_post.size().unwrap_or(window); -+ // Upscaling (the world smaller than the window) cycles through more positions. -+ let ratio = window[0] as f32 / size[0].max(1) as f32; -+ let j = hd::jitter(self.taa_frame, size, hd::jitter_count(ratio)); - self.taa_frame = self.taa_frame.wrapping_add(1); -+ jitter_px = [j[0] * size[0] as f32 * 0.5, -j[1] * size[1] as f32 * 0.5]; - Mat4::from_translation(Vec3::new(j[0], j[1], 0.0)) * unjittered - } else { - unjittered -@@ -4192,6 +4194,7 @@ impl Renderer { - cascades: [Mat4::IDENTITY; 2], - view_proj, - taa: taa && taa_prev.is_some(), -+ jitter_px, - prev_view_proj: taa_prev.map_or(unjittered, |p| p.0), - camera_delta: taa_prev.map_or(Vec3::ZERO, |p| (frame.camera - p.1).as_vec3()), - // The world's sun: vanilla turns the sky -90° about Y, so it crosses from east to west. -@@ -4218,8 +4221,11 @@ impl Renderer { - }; - if fresh { - self.hd_far = Some((hd_cascades[1], camera, shadow_dir, 0)); -- } else if let Some((held, _, _, age)) = &mut self.hd_far { -- hd_cascades[1] = *held; -+ } else if let Some((held, at, _, age)) = &mut self.hd_far { -+ // The held matrix is relative to the camera it was drawn from: shifted by the -+ // camera's step since, so the map stays put in the world as the player walks. -+ let step = (camera - *at).as_vec3(); -+ hd_cascades[1] = *held * Mat4::from_translation(step); - *age += 1; - } - fresh -@@ -4230,11 +4236,14 @@ impl Renderer { - hd_scene.cascades = hd_cascades; - let mut hd_shadow_visible = std::mem::take(&mut self.hd_shadow_visible); - hd_shadow_visible.clear(); -- if hd_wanted && shadow_strength > 0.0 && hd_far_fresh { -+ if hd_wanted && shadow_strength > 0.0 { -+ // The near cascade is drawn every frame: between the far one's redraws the list holds -+ // only the sections in the near box. -+ let c = if hd_far_fresh { 1 } else { 0 }; - let radius = -- ((frame.detail_distance / 16.0) as i32).min((hd::CASCADES[1] / 16.0) as i32 + 2); -+ ((frame.detail_distance / 16.0) as i32).min((hd::CASCADES[c] / 16.0) as i32 + 2); - let light = Frustum { -- planes: frustum(hd_cascades[1]), -+ planes: frustum(hd_cascades[c]), - }; - self.sections.visible( - camera, -@@ -5028,6 +5037,8 @@ impl Renderer { - .as_ref() - .and_then(|p| p.1.as_ref()) - .is_some_and(|p| p.has_weather()); -+ // AirDef fork: HD renders the world at its own scale and upscales it in the TAA pass. -+ self.world_post.hd_scale = hd_on.then(hd::scale_override).flatten(); - let scaled = self.world_post.prepare( - &self.device, - [self.config.width, self.config.height], -@@ -5046,7 +5057,7 @@ impl Renderer { - let hd = self - .hd - .get_or_insert_with(|| hd::Hd::new(device, size, format, globals_size)); -- hd.resize(device, size); -+ hd.resize(device, size, [self.config.width, self.config.height]); - if let Some(settings) = frame.hd { - hd.write(&self.queue, &hd_scene, frame.hd_lights, settings); - } -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index 16610cb02..c54b7b961 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -89,7 +89,7 @@ struct HdFrame { - prev_view_proj: [[f32; 4]; 4], - /// xyz: how far the camera moved since that frame; w: 1 when the history is usable. - taa: [f32; 4], -- /// x: the history's weight; y: the sharpening (0 without TAA). -+ /// x: the history's weight; y: the sharpening (0 without TAA); zw: the jitter in world pixels. - taa_params: [f32; 4], - } - -@@ -105,6 +105,9 @@ pub struct HdScene { - pub camera_delta: glam::Vec3, - /// This frame is anti-aliased over time (the projection is jittered). - pub taa: bool, -+ /// The jitter in world-target pixels, screen orientation (x right, y down): the world's sample -+ /// of a pixel lies this far from its centre (the upscaler weighs samples by it). -+ pub jitter_px: [f32; 2], - /// Towards the sun, and the sky's darkening (0 day, 1 night). - pub sun: glam::Vec3, - pub dark: f32, -@@ -662,8 +665,8 @@ fn fs_taa(in: FullOut) -> TaaOut { - if abs(seen - expected) <= expected * 0.03 + 0.25 { - let mean = sum / 9.0; - let sigma = sqrt(max(sum2 / 9.0 - mean * mean, vec3(0.0))); -- let box_lo = min(max(mean - sigma * 1.25, lo), centre); -- let box_hi = max(min(mean + sigma * 1.25, hi), centre); -+ let box_lo = min(max(mean - sigma * 1.5, lo), centre); -+ let box_hi = max(min(mean + sigma * 1.5, hi), centre); - let past = clip_to_box(box_lo, box_hi, to_ycocg(squash(history_color(uv)))); - // Fast motion trusts the history less. - let moved = length((uv - in.uv) * vec2(size)); -@@ -679,6 +682,99 @@ fn fs_taa(in: FullOut) -> TaaOut { - out.history = vec4(color, dist); - return out; - } -+ -+// The jittered samples' reconstruction filter (a polynomial fit of Lanczos-2, radius 2 in -+// world-target pixels): 1 at 0, 0 at 1 and 2, a little negative between. -+fn lanczos2(x: f32) -> f32 { -+ let x2 = min(x * x, 4.0); -+ let a = 0.4 * x2 - 1.0; -+ let b = 0.25 * x2 - 1.0; -+ return (1.5625 * a * a - 0.5625) * b * b; -+} -+ -+// Temporal upscaling: the world (and its depth) is smaller than the output. Each output pixel -+// gathers the 3x3 world samples round it, each weighed by its distance from the pixel's centre -+// (the samples lie off the pixel centres by the frame's jitter), clamps the reprojected full-size -+// history to their colour box and blends: more history where the nearest sample is far away. -+@fragment -+fn fs_taau(in: FullOut) -> TaaOut { -+ let low = vec2(textureDimensions(source)); -+ let lowi = vec2(low); -+ let high = vec2(textureDimensions(history)); -+ let jit = frame.taa_params.zw; -+ // The output pixel's place among the world's samples: sample i lies at index i - jit, and -+ // the nearest to this pixel is `base`. -+ let t = in.uv * low - 0.5 + jit; -+ let base = vec2(floor(t + 0.5)); -+ var sum = vec3(0.0); -+ var sum2 = vec3(0.0); -+ var lo = vec3(1.0e9); -+ var hi = vec3(-1.0e9); -+ var weighted = vec3(0.0); -+ var total = 0.0; -+ for (var y = -1; y <= 1; y++) { -+ for (var x = -1; x <= 1; x++) { -+ let q = clamp(base + vec2(x, y), vec2(0), lowi - 1); -+ let s = to_ycocg(squash(textureLoad(source, q, 0).rgb)); -+ sum += s; -+ sum2 += s * s; -+ lo = min(lo, s); -+ hi = max(hi, s); -+ let off = vec2(base + vec2(x, y)) - t; -+ let w = lanczos2(off.x) * lanczos2(off.y); -+ weighted += s * w; -+ total += w; -+ } -+ } -+ let current = weighted / max(total, 0.001); -+ // Depth: the nearest of the 2x2 samples round the pixel (steady between frames at an edge). -+ let corner = vec2(floor(in.uv * low - 0.5 + jit)); -+ var d = 0.0; -+ for (var y = 0; y <= 1; y++) { -+ for (var x = 0; x <= 1; x++) { -+ d = max(d, textureLoad(depth_tex, clamp(corner + vec2(x, y), vec2(0), lowi - 1), 0)); -+ } -+ } -+ let sky = d <= 0.0; -+ // The world was drawn shifted by the jitter in clip space; this pixel's unshifted point. -+ let jc = vec2(jit.x * 2.0 / low.x, -jit.y * 2.0 / low.y); -+ let ndc = vec4(in.uv.x * 2.0 - 1.0 + jc.x, 1.0 - in.uv.y * 2.0 + jc.y, max(d, 0.000001), 1.0); -+ let h = frame.inverse_view_proj * ndc; -+ let p = h.xyz / h.w; -+ let dist = select(length(p), 0.0, sky); -+ var resolved = current; -+ if frame.taa.w > 0.5 { -+ let prev = frame.prev_view_proj * vec4(select(p + frame.taa.xyz, p, sky), 1.0); -+ if prev.w > 0.0 { -+ let pn = prev.xy / prev.w; -+ let uv = vec2(pn.x * 0.5 + 0.5, 0.5 - pn.y * 0.5); -+ if all(uv > vec2(0.0)) && all(uv < vec2(1.0)) { -+ let hp = clamp(vec2(uv * high), vec2(0), vec2(high) - 1); -+ let seen = textureLoad(history, hp, 0).a; -+ let expected = select(length(p + frame.taa.xyz), 0.0, sky); -+ if abs(seen - expected) <= expected * 0.03 + 0.25 { -+ let mean = sum / 9.0; -+ let sigma = sqrt(max(sum2 / 9.0 - mean * mean, vec3(0.0))); -+ let box_lo = min(max(mean - sigma * 1.25, lo), current); -+ let box_hi = max(min(mean + sigma * 1.25, hi), current); -+ let past = clip_to_box(box_lo, box_hi, to_ycocg(squash(history_color(uv)))); -+ // How far the nearest sample is from the pixel's centre (0 on it, 1 at the -+ // corner of the world's pixel): the farther, the less the frame says. -+ let near = clamp(length(t - floor(t + 0.5)) / 0.7071, 0.0, 1.0); -+ let fresh = (1.0 - frame.taa_params.x) * mix(1.8, 0.5, near); -+ let moved = length((uv - in.uv) * high); -+ let keep = (1.0 - fresh) * mix(1.0, 0.8, clamp(moved / 4.0, 0.0, 1.0)); -+ resolved = mix(current, past, clamp(keep, 0.0, 0.98)); -+ } -+ } -+ } -+ } -+ let color = unsquash(from_ycocg(resolved)); -+ var out: TaaOut; -+ out.color = vec4(color, 1.0); -+ out.history = vec4(color, dist); -+ return out; -+} - "#; - - struct Target { -@@ -744,10 +840,35 @@ fn halton(mut index: u32, base: u32) -> f32 { - r - } - -+/// The history's weight when upscaling (the frames each pixel gathers are sparse, so more of them -+/// are kept than without; `fs_taau` lowers it where the frame's sample is near). -+const UPSCALE_HISTORY: f32 = 0.92; -+ -+/// The world's size relative to the output (`HORIZON_HD_SCALE`, per axis, 0.25..=1) while HD is on, if -+/// set: it replaces the resolution scale of Horizon's settings for HD. 1 renders at full size. -+pub fn scale_override() -> Option { -+ static SCALE: std::sync::OnceLock> = std::sync::OnceLock::new(); -+ *SCALE.get_or_init(|| { -+ let v: f32 = std::env::var("HORIZON_HD_SCALE").ok()?.parse().ok()?; -+ v.is_finite().then(|| v.clamp(0.25, 1.0)) -+ }) -+} -+ -+/// How many jitter positions the sequence cycles through for an output `ratio` times the world's -+/// size per axis: 16 without upscaling, else 8 per output pixel's share of a world pixel -+/// (ratio squared), so every output pixel gets samples all over the world pixel it lies in. -+pub fn jitter_count(ratio: f32) -> u32 { -+ if ratio <= 1.001 { -+ 16 -+ } else { -+ ((8.0 * ratio * ratio).ceil() as u32).clamp(16, 64) -+ } -+} -+ - /// Frame `frame`'s sub-pixel offset of the projection in clip space (a fraction of the --/// screen's size in x and y), from the Halton(2, 3) sequence over 16 frames. --pub fn jitter(frame: u32, size: [u32; 2]) -> [f32; 2] { -- let i = frame % 16 + 1; -+/// screen's size in x and y), from the Halton(2, 3) sequence over `count` frames. -+pub fn jitter(frame: u32, size: [u32; 2], count: u32) -> [f32; 2] { -+ let i = frame % count.max(1) + 1; - [ - (halton(i, 2) - 0.5) * 2.0 / size[0].max(1) as f32, - (halton(i, 3) - 0.5) * 2.0 / size[1].max(1) as f32, -@@ -755,7 +876,10 @@ pub fn jitter(frame: u32, size: [u32; 2]) -> [f32; 2] { - } - - pub struct Hd { -+ /// The world's size (the G-buffer, lighting, AO, post pass), and the output's (TAA, bloom, -+ /// tone curve): bigger when the world is upscaled. - size: [u32; 2], -+ out: [u32; 2], - pub albedo: wgpu::TextureView, - pub normal: wgpu::TextureView, - pub levels: wgpu::TextureView, -@@ -779,6 +903,7 @@ pub struct Hd { - taa_active: bool, - taa_layout: wgpu::BindGroupLayout, - taa_pipeline: wgpu::RenderPipeline, -+ taau_pipeline: wgpu::RenderPipeline, - taa_groups: Option<((wgpu::TextureView, wgpu::TextureView), [wgpu::BindGroup; 2])>, - bloom: Vec, - /// Bind groups, rebuilt only when their views change: `resize` drops all of them, the -@@ -1098,7 +1223,7 @@ impl Hd { - entries: &entries(&[UNIFORM, UNFILTERED, DEPTH, FLOAT, SAMPLER]), - }); - let taa_module = module(device, "HD TAA", TAA); -- let taa_pipeline = { -+ let taa_pipeline = |entry: &str| { - let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("HD TAA"), - bind_group_layouts: &[Some(&taa_layout)], -@@ -1120,7 +1245,7 @@ impl Hd { - }, - fragment: Some(wgpu::FragmentState { - module: &taa_module, -- entry_point: Some("fs_taa"), -+ entry_point: Some(entry), - compilation_options: Default::default(), - targets: &[target.clone(), target], - }), -@@ -1131,6 +1256,7 @@ impl Hd { - cache: None, - }) - }; -+ let (taa_pipeline, taau_pipeline) = (taa_pipeline("fs_taa"), taa_pipeline("fs_taau")); - let ao_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("HD ambient occlusion"), - entries: &entries(&[UNIFORM, DEPTH, UNFILTERED]), -@@ -1288,6 +1414,7 @@ impl Hd { - }; - let mut hd = Hd { - size: [0, 0], -+ out: [0, 0], - albedo: target(device, "HD albedo", [1, 1], ALBEDO).view, - normal: target(device, "HD normal", [1, 1], NORMAL).view, - levels: target(device, "HD light levels", [1, 1], LIGHT_LEVELS).view, -@@ -1319,6 +1446,7 @@ impl Hd { - taa_active: false, - taa_layout, - taa_pipeline, -+ taau_pipeline, - taa_groups: None, - bloom: Vec::new(), - light_group: None, -@@ -1355,26 +1483,29 @@ impl Hd { - groups_seen: 0, - groups_last_frame: 0, - }; -- hd.resize(device, size); -+ hd.resize(device, size, size); - hd - } - -- /// The targets, at the world target's size. -- pub fn resize(&mut self, device: &wgpu::Device, size: [u32; 2]) { -- if self.size == size { -+ /// The targets: the world's at `size` (the world target's), the output's (TAA history, bloom) -+ /// at `out`, which is bigger when the world is upscaled. -+ pub fn resize(&mut self, device: &wgpu::Device, size: [u32; 2], out: [u32; 2]) { -+ let out = [out[0].max(size[0]), out[1].max(size[1])]; -+ if self.size == size && self.out == out { - return; - } - self.size = size; -+ self.out = out; - self.albedo = target(device, "HD albedo", size, ALBEDO).view; - self.normal = target(device, "HD normal", size, NORMAL).view; - self.levels = target(device, "HD light levels", size, LIGHT_LEVELS).view; - self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; - self.lit = target(device, "HD lit", size, HDR).view; - self.fx = target(device, "HD post", size, HDR).view; -- self.taa = target(device, "HD TAA", size, HDR).view; -+ self.taa = target(device, "HD TAA", out, HDR).view; - self.history = [ -- target(device, "HD TAA history", size, HDR).view, -- target(device, "HD TAA history", size, HDR).view, -+ target(device, "HD TAA history", out, HDR).view, -+ target(device, "HD TAA history", out, HDR).view, - ]; - self.history_ready = false; - self.taa_groups = None; -@@ -1404,7 +1535,7 @@ impl Hd { - self.tiles_x = size[0].div_ceil(TILE).max(1); - self.bloom = (0..BLOOM_LEVELS) - .map(|k| { -- let s = [(size[0] >> (k + 1)).max(1), (size[1] >> (k + 1)).max(1)]; -+ let s = [(out[0] >> (k + 1)).max(1), (out[1] >> (k + 1)).max(1)]; - target(device, "HD bloom", s, HDR) - }) - .collect(); -@@ -1420,6 +1551,11 @@ impl Hd { - .collect(); - } - -+ /// The world is smaller than the output, so TAA upsamples it. -+ fn upscaling(&self) -> bool { -+ self.out != self.size -+ } -+ - /// This frame's camera, sun, fog, lights and settings. - pub fn write( - &mut self, -@@ -1468,7 +1604,18 @@ impl Hd { - scene.camera_delta.z, - if self.history_ready { 1.0 } else { 0.0 }, - ], -- taa_params: [0.9, if self.taa_active { 0.12 } else { 0.0 }, 0.0, 0.0], -+ taa_params: [ -+ if self.upscaling() { -+ UPSCALE_HISTORY -+ } else { -+ // A still picture keeps 94% of the past: the jitter's shimmer on pixel-art -+ // textures averages out instead of flickering. -+ 0.94 -+ }, -+ if self.taa_active { 0.12 } else { 0.0 }, -+ scene.jitter_px[0], -+ scene.jitter_px[1], -+ ], - inverse_view_proj: scene.view_proj.inverse().to_cols_array_2d(), - resolution: [ - self.size[0] as f32, -@@ -1872,9 +2019,13 @@ impl Hd { - return source; - }; - let (read, write) = (self.history_flip, 1 - self.history_flip); -+ let upscaling = self.upscaling(); - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("HD TAA"), -- timestamp_writes: stamp(&mut self.timing, "hd taa"), -+ timestamp_writes: stamp( -+ &mut self.timing, -+ if upscaling { "hd taau" } else { "hd taa" }, -+ ), - color_attachments: &[&self.taa, &self.history[write]].map(|view| { - Some(wgpu::RenderPassColorAttachment { - view, -@@ -1888,7 +2039,11 @@ impl Hd { - }), - ..Default::default() - }); -- pass.set_pipeline(&self.taa_pipeline); -+ pass.set_pipeline(if upscaling { -+ &self.taau_pipeline -+ } else { -+ &self.taa_pipeline -+ }); - pass.set_bind_group(0, &groups[read], &[]); - pass.draw(0..3, 0..1); - drop(pass); -@@ -2045,7 +2200,7 @@ mod tests { - let size = [1920u32, 1080]; - let mut seen = std::collections::HashSet::new(); - for f in 0..32 { -- let j = jitter(f, size); -+ let j = jitter(f, size, 16); - assert!(j[0].abs() <= 1.0 / size[0] as f32 + 1e-9); - assert!(j[1].abs() <= 1.0 / size[1] as f32 + 1e-9); - seen.insert((j[0].to_bits(), j[1].to_bits())); -@@ -2053,6 +2208,22 @@ mod tests { - assert_eq!(seen.len(), 16); - } - -+ #[test] -+ fn jitter_covers_the_upscale_ratio() { -+ assert_eq!(jitter_count(1.0), 16); -+ assert_eq!(jitter_count(1.0 / 0.67), 18); -+ assert_eq!(jitter_count(2.0), 32); -+ let size = [1280u32, 720]; -+ let count = jitter_count(2.0); -+ let seen: std::collections::HashSet<_> = (0..count * 2) -+ .map(|f| { -+ let j = jitter(f, size, count); -+ (j[0].to_bits(), j[1].to_bits()) -+ }) -+ .collect(); -+ assert_eq!(seen.len(), count as usize); -+ } -+ - #[test] - fn tile_lists_keep_lights_that_reach_a_tile() { - let size = [320, 160]; -diff --git a/crates/horizon-client/src/gfx/world_post.rs b/crates/horizon-client/src/gfx/world_post.rs -index 17609d27d..a32bdc964 100644 ---- a/crates/horizon-client/src/gfx/world_post.rs -+++ b/crates/horizon-client/src/gfx/world_post.rs -@@ -18,6 +18,8 @@ pub struct Post { - format: wgpu::TextureFormat, - /// World resolution relative to the window (0.5..=2.0). - pub scale: f32, -+ /// HD's own scale for this frame (`HORIZON_HD_SCALE`), instead of `scale`. -+ pub hd_scale: Option, - pub antialias: bool, - target: Option, - } -@@ -94,6 +96,7 @@ impl Post { - sampler, - format, - scale: 1.0, -+ hd_scale: None, - antialias: false, - target: None, - } -@@ -108,11 +111,12 @@ impl Post { - window: [u32; 2], - always: bool, - ) -> Option<(wgpu::TextureView, wgpu::TextureView)> { -- if self.scale == 1.0 && !self.antialias && !always { -+ let scale = self.hd_scale.unwrap_or(self.scale); -+ if scale == 1.0 && !self.antialias && !always { - self.target = None; - return None; - } -- let size = window.map(|v| ((v as f32 * self.scale).round() as u32).clamp(1, 8192)); -+ let size = window.map(|v| ((v as f32 * scale).round() as u32).clamp(1, 8192)); - if self.target.as_ref().is_none_or(|t| t.size != size) { - let extent = wgpu::Extent3d { - width: size[0], -diff --git a/crates/horizon-client/src/terrain.wgsl b/crates/horizon-client/src/terrain.wgsl -index bb5c70f63..c44449cc7 100644 ---- a/crates/horizon-client/src/terrain.wgsl -+++ b/crates/horizon-client/src/terrain.wgsl -@@ -166,6 +166,10 @@ struct GBufferOut { - } - fn face_normal(pos: vec3) -> vec3 { - var n = normalize(cross(dpdx(pos), dpdy(pos))); -+ // Block faces are axis-aligned; only plants' crossed quads lean. Those have two -+ // sides, so a normal turned to the camera would flip as the player walks by and -+ // the sun and the shadow lookup with it: plants take the up normal instead. -+ if max(abs(n.x), max(abs(n.y), abs(n.z))) < 0.9 { return vec3(0.0, 1.0, 0.0); } - if dot(n, pos) > 0.0 { n = -n; } - return n; - } --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0019-HD-water-its-own-pass-colour-normal-depth-the-floor-.patch b/horizon-patches/0019-HD-water-its-own-pass-colour-normal-depth-the-floor-.patch deleted file mode 100644 index efb492d..0000000 --- a/horizon-patches/0019-HD-water-its-own-pass-colour-normal-depth-the-floor-.patch +++ /dev/null @@ -1,982 +0,0 @@ -From 38250c4392e27d5700842ca6ee47291dd1831b72 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 17:47:52 +0300 -Subject: [PATCH 19/42] HD water: its own pass (colour, normal, depth), the - floor under it lit, a composite with world-space waves, refraction with depth - absorption in the biome colour, screen-space reflections, Fresnel and a - shadowed sun glint (HORIZON_HD_WATER=0 to switch off) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/game.rs | 9 + - crates/horizon-client/src/gfx.rs | 109 +++++- - crates/horizon-client/src/gfx/flat.rs | 4 + - crates/horizon-client/src/gfx/hd.rs | 497 ++++++++++++++++++++++++- - crates/horizon-client/src/terrain.wgsl | 19 + - 5 files changed, 627 insertions(+), 11 deletions(-) - -diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs -index 3a11ee283..5a173977f 100644 ---- a/crates/horizon-client/src/game.rs -+++ b/crates/horizon-client/src/game.rs -@@ -14658,6 +14658,14 @@ fn build_renderer( - prepared, - previous, - api, -+ // AirDef fork, HD water: the layers of the water sprites. -+ ["water_still", "water_flow"].map(|name| { -+ assets -+ .sprite_layers -+ .get(&format!("minecraft:block/{name}")) -+ .copied() -+ .unwrap_or(0xffff) -+ }), - ))?; - renderer.hud.sprite_animations = atlas - .sprite_animations -@@ -14717,6 +14725,7 @@ fn loading_renderer( - None, - previous, - api, -+ [0xffff; 2], - ))?; - if let Some(atlas) = atlas { - renderer.hud.sprite_animations = atlas -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index b85c6e269..f9dab8abf 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -564,6 +564,8 @@ pub struct Renderer { - mod_views: Option, - /// AirDef fork: the HD lighting's G-buffer pipelines (terrain, entities) and its targets. - gbuffer_terrain: wgpu::RenderPipeline, -+ /// The water quads alone into the HD water targets (section draws; `flat.water` for flat ones). -+ gbuffer_water: wgpu::RenderPipeline, - gbuffer_entity: wgpu::RenderPipeline, - /// The HD sun shadows: depth only, from the light, and the sections in its farther cascade. - shadow_terrain: wgpu::RenderPipeline, -@@ -1083,6 +1085,7 @@ impl Renderer { - prepared: Option, - previous: Option<&Self>, - api: GraphicsApi, -+ water_layers: [u32; 2], - ) -> Result { - let split = sprite_split(); - if layers > split * SPRITE_ARRAYS { -@@ -1205,6 +1208,13 @@ impl Renderer { - ("SPRITE_ARRAYS", f64::from(sprite_arrays)), - ("SPRITE_SPLIT", f64::from(split)), - ]; -+ // AirDef fork, HD water: the sprite layers terrain.wgsl's fs_water takes for water. -+ let water_constants = [ -+ ("SPRITE_ARRAYS", f64::from(sprite_arrays)), -+ ("SPRITE_SPLIT", f64::from(split)), -+ ("WATER_STILL", f64::from(water_layers[0])), -+ ("WATER_FLOW", f64::from(water_layers[1])), -+ ]; - let bindings = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Horizon terrain bindings"), - entries: &[ -@@ -1420,6 +1430,55 @@ impl Renderer { - multiview_mask: None, - cache: None, - }); -+ // AirDef fork, HD water: only the water quads (`fs_water`), into the colour and normal -+ // targets and the water's own depth; `vertex` is the section or the flat vertex stage. -+ let water_targets = [ -+ Some(wgpu::ColorTargetState { -+ format: hd::WATER_TINT, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ }), -+ Some(wgpu::ColorTargetState { -+ format: hd::WATER_NORMAL, -+ blend: None, -+ write_mask: wgpu::ColorWrites::ALL, -+ }), -+ ]; -+ let water_pipeline = -+ |layout: &wgpu::PipelineLayout, -+ label: &str, -+ vertex: &str, -+ buffers: &[Option>]| { -+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -+ label: Some(label), -+ layout: Some(layout), -+ vertex: wgpu::VertexState { -+ module: &shader, -+ entry_point: Some(vertex), -+ compilation_options: Default::default(), -+ buffers, -+ }, -+ primitive: wgpu::PrimitiveState { -+ cull_mode: Some(wgpu::Face::Back), -+ ..Default::default() -+ }, -+ depth_stencil: gbuffer_depth.clone(), -+ multisample: Default::default(), -+ fragment: Some(wgpu::FragmentState { -+ module: &shader, -+ entry_point: Some("fs_water"), -+ compilation_options: wgpu::PipelineCompilationOptions { -+ constants: &water_constants, -+ ..Default::default() -+ }, -+ targets: &water_targets, -+ }), -+ multiview_mask: None, -+ cache: None, -+ }) -+ }; -+ let gbuffer_water = -+ water_pipeline(&layout, "HD water terrain", "vs_terrain", &terrain_buffers); - let shadow_terrain = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("HD shadows terrain"), - layout: Some(&layout), -@@ -2597,6 +2656,7 @@ impl Renderer { - &device, - &bindings, - arena.page_vertices(), -+ |layout, label| water_pipeline(layout, label, "vs_terrain_flat", &buffers), - |layout, label, fragment, blend| { - pipeline_in( - layout, -@@ -2816,6 +2876,7 @@ impl Renderer { - mod_post_hdr: false, - mod_views: None, - gbuffer_terrain, -+ gbuffer_water, - gbuffer_entity, - shadow_terrain, - shadow_entity, -@@ -4204,6 +4265,16 @@ impl Renderer { - fog_color, - fog, - sky: frame.skybox && !frame.end_sky && !frame.nether_lighting && !frame.underwater, -+ sky_color: [ -+ frame.sky.sky_color[0], -+ frame.sky.sky_color[1], -+ frame.sky.sky_color[2], -+ ], -+ camera_mod: [ -+ camera.x.rem_euclid(256.0) as f32, -+ camera.z.rem_euclid(256.0) as f32, -+ ], -+ water: !frame.underwater, - }; - let (shadow_dir, shadow_strength, _) = hd::light_of(&hd_scene); - let mut hd_cascades = hd::cascades(camera, shadow_dir); -@@ -5058,6 +5129,11 @@ impl Renderer { - .hd - .get_or_insert_with(|| hd::Hd::new(device, size, format, globals_size)); - hd.resize(device, size, [self.config.width, self.config.height]); -+ // Translucent terrain on screen: the water pass has something to draw. -+ hd.water_geometry = match self.flat.as_ref().filter(|_| flat_frame) { -+ Some(flat) => !flat.translucent_calls().is_empty(), -+ None => translucent_draws > 0, -+ }; - if let Some(settings) = frame.hd { - hd.write(&self.queue, &hd_scene, frame.hd_lights, settings); - } -@@ -5186,6 +5262,32 @@ impl Renderer { - drop(pass); - // Contact shadows from the G-buffer's depth and normals, for the lighting. - hd.ao(&mut encoder); -+ // The water quads alone, with their own depth: the lighting finds the water by it and -+ // the composite after the lighting lays the surface over the lit floor. -+ if hd.water_active() { -+ let mut pass = hd.water_pass(&mut encoder); -+ pass.set_bind_group(0, &self.group, &[]); -+ match self.flat.as_ref().filter(|_| flat_frame) { -+ Some(flat) => { -+ pass.set_pipeline(&flat.water); -+ flat.draw(&mut pass, flat.translucent_calls(), |p| { -+ self.arena.buffer(p) -+ }); -+ } -+ None => { -+ pass.set_pipeline(&self.gbuffer_water); -+ pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32); -+ pass.set_vertex_buffer(1, self.instances.slice(..)); -+ self.draw_sections( -+ &mut pass, -+ &self.indirect, -+ 0, -+ solid_draws, -+ translucent_draws, -+ ); -+ } -+ } -+ } - } else if !hd_on { - self.hd = None; - } -@@ -5721,6 +5823,11 @@ impl Renderer { - (color, depth), - (&self.lightmap_view, &self.lightmap_sampler), - ); -+ hd.water_composite( -+ &self.device, -+ &mut encoder, -+ (&self.lightmap_view, &self.lightmap_sampler), -+ ); - match &mut self.mod_post { - Some(post) if mod_post => { - let size = self.world_post.size().unwrap_or([1, 1]); -@@ -5731,7 +5838,7 @@ impl Renderer { - &self.mod_params.0, - self.mod_params.1, - ); -- post.run(&self.device, &mut encoder, (&hd.lit, depth), &hd.fx); -+ post.run(&self.device, &mut encoder, (hd.lit_view(), depth), &hd.fx); - let source = hd.taa(&self.device, &mut encoder, hd::HdSource::Fx, depth); - hd.finish(&self.device, &mut encoder, source, &view_target); - } -diff --git a/crates/horizon-client/src/gfx/flat.rs b/crates/horizon-client/src/gfx/flat.rs -index 5d86888da..7e2e20cad 100644 ---- a/crates/horizon-client/src/gfx/flat.rs -+++ b/crates/horizon-client/src/gfx/flat.rs -@@ -64,6 +64,8 @@ pub struct Call { - pub struct Flat { - pub solid: wgpu::RenderPipeline, - pub translucent: wgpu::RenderPipeline, -+ /// The water quads alone, into HD's water targets. -+ pub water: wgpu::RenderPipeline, - flatten: wgpu::ComputePipeline, - compute_layout: wgpu::BindGroupLayout, - flatten_seen: wgpu::ComputePipeline, -@@ -254,6 +256,7 @@ impl Flat { - device: &wgpu::Device, - terrain_layout: &wgpu::BindGroupLayout, - page_vertices: u64, -+ water: impl Fn(&wgpu::PipelineLayout, &str) -> wgpu::RenderPipeline, - pipeline: impl Fn( - &wgpu::PipelineLayout, - &str, -@@ -320,6 +323,7 @@ impl Flat { - "fs_translucent", - Some(wgpu::BlendState::ALPHA_BLENDING), - ), -+ water: water(&layout, "HD water terrain (flat)"), - flatten, - compute_layout, - flatten_seen, -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index c54b7b961..f5caa2d0b 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -16,6 +16,9 @@ pub const NORMAL: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; - pub const LIGHT_LEVELS: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; - /// The lit world and the post pass's output. - pub const HDR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; -+/// The water pass's targets: the surface colour with the block light, and the normal with the sky light. -+pub const WATER_TINT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; -+pub const WATER_NORMAL: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; - /// The most lights drawn a frame. - pub const MAX_LIGHTS: usize = 512; - /// The side (pixels) of a tile of the screen with its own short list of lights. -@@ -91,6 +94,13 @@ struct HdFrame { - taa: [f32; 4], - /// x: the history's weight; y: the sharpening (0 without TAA); zw: the jitter in world pixels. - taa_params: [f32; 4], -+ /// The (jittered) world-to-clip matrix the world was drawn with (rays in the water pass). -+ view_proj: [[f32; 4]; 4], -+ /// x: the water effect runs this frame; y: seconds for the waves (wraps at 600); zw: the -+ /// camera's x and z modulo 256, the waves' period. -+ water: [f32; 4], -+ /// rgb: the sky's colour; w: 1 when there is a sky. -+ sky_color: [f32; 4], - } - - /// What the world around looks like this frame (from the renderer's globals). -@@ -116,6 +126,11 @@ pub struct HdScene { - pub fog: [f32; 4], - /// The sky has a sun at all (the Overworld). - pub sky: bool, -+ pub sky_color: [f32; 3], -+ /// The camera's x and z modulo 256 (the waves' period), so the waves hold still in the world. -+ pub camera_mod: [f32; 2], -+ /// The water effect may run (not underwater, not disabled). -+ pub water: bool, - } - - /// The cascades' half sizes (blocks) and shadow map size. -@@ -137,6 +152,9 @@ struct HdFrame { - prev_view_proj: mat4x4, - taa: vec4, - taa_params: vec4, -+ view_proj: mat4x4, -+ water: vec4, -+ sky_color: vec4, - } - struct FullOut { - @builtin(position) position: vec4, -@@ -175,6 +193,8 @@ struct Light { - @group(0) @binding(12) var tiles: array; - // Ambient occlusion at half resolution: x the occlusion (1 open), y the distance it was taken at. - @group(0) @binding(13) var ao_tex: texture_2d; -+// The water pass's depth: the nearest water surface (0 where there is none). -+@group(0) @binding(14) var water_depth: texture_depth_2d; - - // The occlusion at a full-resolution pixel `dist` away: the four nearest half-resolution texels - // weighted by closeness and by how near their distance is, so it doesn't bleed across edges. -@@ -216,6 +236,12 @@ fn cascade_at(p: vec3, c: i32) -> vec3 { - - // The sun's (or moon's) light reaching p: 1 lit, 0 in shadow; soft-edged. - fn shadow(p: vec3, n: vec3) -> f32 { -+ return shadow_taps(p, n, 1); -+} -+ -+// `shadow` with a (2 * r + 1) square of taps: the floor under water takes one (the water dims and -+// blurs it anyway). -+fn shadow_taps(p: vec3, n: vec3, r: i32) -> f32 { - let dist = length(p); - let c = select(1, 0, dist < frame.shadow.y * 0.92); - let texel = select(frame.shadow.z, frame.shadow.y, c == 0) * 2.0 / 2048.0; -@@ -224,13 +250,13 @@ fn shadow(p: vec3, n: vec3) -> f32 { - if any(s.xy < vec2(0.0)) || any(s.xy > vec2(1.0)) || s.z > 1.0 { return 1.0; } - var lit = 0.0; - let step = 1.0 / 2048.0; -- for (var y = -1; y <= 1; y++) { -- for (var x = -1; x <= 1; x++) { -+ for (var y = -r; y <= r; y++) { -+ for (var x = -r; x <= r; x++) { - lit += textureSampleCompareLevel(shadow_map, shadow_sampler, s.xy + vec2(f32(x), f32(y)) * step, c, s.z - 0.0005); - } - } - let fade = clamp((frame.shadow.z - dist) / 16.0, 0.0, 1.0); -- return mix(1.0, lit / 9.0, fade); -+ return mix(1.0, lit / f32((2 * r + 1) * (2 * r + 1)), fade); - } - - // One shadow sample, for the rays through the air. -@@ -252,7 +278,15 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let scene = textureLoad(scene_color, px, 0).rgb; - let d = textureLoad(scene_depth, px, 0); - let g = textureLoad(gbuffer_depth, px, 0); -- let ndc = vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, max(d, 0.000001), 1.0); -+ // Water in front of the opaque world with nothing translucent over it: here the floor is lit -+ // and the water composite lays the surface over it (it knows the pixel by the alpha 0.5). -+ var water = false; -+ if frame.water.x > 0.5 && g > 0.0 { -+ let wd = textureLoad(water_depth, px, 0); -+ water = wd > 0.0 && wd >= g && d - wd <= d * 0.002; -+ } -+ let dd = select(d, g, water); -+ let ndc = vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, max(dd, 0.000001), 1.0); - let h = frame.inverse_view_proj * ndc; - let p = h.xyz / h.w; - let view = normalize(p); -@@ -267,7 +301,7 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let halo_all = tiles[tile + 3u] == 0xffffffffu; - let halo_count = select(tiles[tile + 3u], count, halo_all); - var c = scene; -- let surface = d > 0.0 && abs(d - g) <= max(d, g) * 0.002; -+ let surface = water || (d > 0.0 && abs(d - g) <= max(d, g) * 0.002); - if surface { - let n = normalize(textureLoad(normal_tex, px, 0).xyz); - let albedo = textureLoad(albedo_tex, px, 0); -@@ -278,14 +312,15 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let sky_reach = smoothstep(0.35, 0.95, lm.x); - var s = 1.0; - let ndl = dot(n, sun); -- if sun_on > 0.0 && ndl > 0.0 && sky_reach > 0.0 { s = shadow(p, n); } else { s = 0.0; } -+ if sun_on > 0.0 && ndl > 0.0 && sky_reach > 0.0 { s = shadow_taps(p, n, select(1, 0, water)); } else { s = 0.0; } - // Shadowed sky light (ambient) keeps vanilla's look, blocks' light stays as is; the sun adds - // direct light by the face's angle. - let direct = frame.sun_color.rgb * sun_on * max(ndl, 0.0) * s * sky_reach; - // Contact shadows from the ambient occlusion: on the ambient and blocks' light fully, on - // the mods' lights by half, not on the sun or on emission. - var occlusion = 1.0; -- if frame.ambient.w > 0.0 { occlusion = ao_at(px, length(p)); } -+ // (The water dims and blurs the floor under it: no occlusion there.) -+ if frame.ambient.w > 0.0 && !water { occlusion = ao_at(px, length(p)); } - let ao_ambient = mix(1.0, occlusion, frame.ambient.w); - let ao_lights = mix(1.0, occlusion, frame.ambient.w * 0.5); - var light = max(base * frame.ambient.x, block * frame.ambient.y) * ao_ambient + direct; -@@ -334,7 +369,253 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let a1 = atan((far - t0) / hh); - c += l.color.rgb * l.color.w * (a1 - a0) / hh * 0.004 * (0.3 + density * 300.0); - } -- return vec4(c, 1.0); -+ return vec4(c, select(1.0, 0.5, water)); -+} -+"#; -+ -+const WATER: &str = r#" -+const PI: f32 = 3.14159265; -+const REFLECT_STEPS: i32 = 24; -+const REFLECT_RANGE: f32 = 96.0; -+@group(0) @binding(0) var frame: HdFrame; -+@group(0) @binding(1) var lit_tex: texture_2d; -+@group(0) @binding(2) var gbuffer_depth: texture_depth_2d; -+@group(0) @binding(3) var water_tint: texture_2d; -+@group(0) @binding(4) var water_normal: texture_2d; -+@group(0) @binding(5) var water_depth: texture_depth_2d; -+@group(0) @binding(6) var shadow_map: texture_depth_2d_array; -+@group(0) @binding(7) var shadow_sampler: sampler_comparison; -+@group(0) @binding(8) var lightmap: texture_2d; -+@group(0) @binding(9) var linear: sampler; -+ -+fn ign(px: vec2) -> f32 { -+ let p = px + vec2(5.588238 * frame.params.w); -+ return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); -+} -+ -+// The camera-relative position of the screen point `uv` at depth z. -+fn position_at(uv: vec2, z: f32) -> vec3 { -+ let h = frame.inverse_view_proj * vec4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, z, 1.0); -+ return h.xyz / h.w; -+} -+ -+// The screen point (uv, clip w) of a camera-relative position. -+fn project(p: vec3) -> vec3 { -+ let c = frame.view_proj * vec4(p, 1.0); -+ return vec3(c.x / c.w * 0.5 + 0.5, 0.5 - c.y / c.w * 0.5, c.w); -+} -+ -+fn on_screen(uv: vec2) -> bool { -+ return all(uv > vec2(0.0)) && all(uv < vec2(1.0)); -+} -+ -+fn texel(uv: vec2) -> vec2 { -+ return clamp(vec2(uv * frame.resolution.xy), vec2(0), vec2(frame.resolution.xy) - 1); -+} -+ -+// Sine waves in world space: wave vectors that are whole multiples of 2 pi / 256 and speeds that -+// are whole cycles per 600 s, so the sum repeats every 256 blocks and 10 minutes and holds still -+// in the world as the camera moves. x, y: the vector in units of 2 pi / 256; z: the slope the -+// wave adds; w: its phase. -+const WAVES = array, 10>( -+ vec4(3.0, 1.0, 0.040, 0.3), -+ vec4(-2.0, 5.0, 0.045, 2.1), -+ vec4(7.0, 2.0, 0.040, 4.4), -+ vec4(-5.0, -9.0, 0.040, 1.2), -+ vec4(11.0, -4.0, 0.035, 5.6), -+ vec4(13.0, 10.0, 0.035, 3.3), -+ vec4(-17.0, 7.0, 0.030, 0.9), -+ vec4(9.0, 23.0, 0.030, 2.7), -+ vec4(-27.0, 19.0, 0.025, 4.9), -+ vec4(31.0, 30.0, 0.025, 1.8), -+); -+ -+// The gradient of the water's height at world x, z: the waves that a pixel `footprint` blocks -+// wide can still resolve (finer ones fade out, or they would shimmer). -+fn wave_gradient(xz: vec2, footprint: f32) -> vec2 { -+ var grad = vec2(0.0); -+ for (var i = 0; i < 10; i++) { -+ let w = WAVES[i]; -+ let k = w.xy * (2.0 * PI / 256.0); -+ let len = length(k); -+ let fade = clamp((2.0 * PI / len) / max(footprint, 0.0001) * 0.25 - 0.5, 0.0, 1.0); -+ let cycles = round(sqrt(9.8 * len) * 0.5 * 600.0 / (2.0 * PI)); -+ let omega = cycles * 2.0 * PI / 600.0; -+ let phase = dot(k, xz) - omega * frame.water.y + w.w; -+ grad += (k / len) * (w.z * fade * cos(phase)); -+ } -+ return grad; -+} -+ -+fn cascade_at(p: vec3, c: i32) -> vec3 { -+ let s = frame.cascades[c] * vec4(p, 1.0); -+ return vec3(s.x * 0.5 + 0.5, 0.5 - s.y * 0.5, s.z); -+} -+ -+// The sun's (or moon's) light reaching p: 1 lit, 0 in shadow (as the lighting pass has it). -+fn shadow(p: vec3, n: vec3) -> f32 { -+ let dist = length(p); -+ let c = select(1, 0, dist < frame.shadow.y * 0.92); -+ let texel_size = select(frame.shadow.z, frame.shadow.y, c == 0) * 2.0 / 2048.0; -+ let q = p + n * texel_size * 1.5; -+ let s = cascade_at(q, c); -+ if any(s.xy < vec2(0.0)) || any(s.xy > vec2(1.0)) || s.z > 1.0 { return 1.0; } -+ var lit = 0.0; -+ let step = 1.0 / 2048.0; -+ for (var y = -1; y <= 1; y++) { -+ for (var x = -1; x <= 1; x++) { -+ lit += textureSampleCompareLevel(shadow_map, shadow_sampler, s.xy + vec2(f32(x), f32(y)) * step, c, s.z - 0.0005); -+ } -+ } -+ let fade = clamp((frame.shadow.z - dist) / 16.0, 0.0, 1.0); -+ return mix(1.0, lit / 9.0, fade); -+} -+ -+fn linear_fog(d: f32, start: f32, end: f32) -> f32 { -+ if d <= start { return 0.0; } -+ if d >= end { return 1.0; } -+ return (d - start) / (end - start); -+} -+ -+// What the sky shows along r when the ray leaves the screen: the horizon's colour rising to -+// the zenith's, darker where the water lies under cover (`vis` is the sky light there). -+fn sky_along(r: vec3, vis: f32) -> vec3 { -+ var c = frame.fog_color.rgb; -+ if frame.sky_color.w > 0.5 { -+ c = mix(frame.fog_color.rgb, frame.sky_color.rgb, smoothstep(0.0, 0.5, r.y)); -+ } -+ return c * mix(0.12, 1.0, vis); -+} -+ -+// Marches the ray from p along r over the depth buffer (28 steps, growing, jittered so TAA -+// averages the placement; then a binary search for the crossing). xyz: the colour found, -+// w: how much to trust it (0 for a miss, less near the screen's edge). -+fn reflection(p: vec3, r: vec3, jitter: f32) -> vec4 { -+ var near = 0.0; -+ for (var i = 0; i < REFLECT_STEPS; i++) { -+ let u = (f32(i) + jitter) / f32(REFLECT_STEPS); -+ let s = 0.12 + REFLECT_RANGE * u * u; -+ let q = p + r * s; -+ let sp = project(q); -+ if sp.z <= 0.0 || !on_screen(sp.xy) { break; } -+ let z = textureLoad(gbuffer_depth, texel(sp.xy), 0); -+ if z > 0.0 { -+ let c = frame.view_proj * vec4(q, 1.0); -+ // Reversed Z: a larger depth is nearer; the ray is behind the surface when the -+ // surface is nearer than it. -+ if z > c.z / c.w { -+ let behind = length(q) - length(position_at(sp.xy, z)); -+ if behind < 0.4 + 0.08 * s { -+ var lo = near; -+ var hi = s; -+ for (var k = 0; k < 5; k++) { -+ let m = 0.5 * (lo + hi); -+ let mq = p + r * m; -+ let mp = project(mq); -+ let mc = frame.view_proj * vec4(mq, 1.0); -+ let mz = textureLoad(gbuffer_depth, texel(mp.xy), 0); -+ if mz > mc.z / mc.w { hi = m; } else { lo = m; } -+ } -+ let hp = project(p + r * hi); -+ let edge = min(min(hp.x, 1.0 - hp.x), min(hp.y, 1.0 - hp.y)); -+ let colour = textureSampleLevel(lit_tex, linear, hp.xy, 0.0).rgb; -+ return vec4(colour, smoothstep(0.0, 0.08, edge)); -+ } -+ } -+ } -+ near = s; -+ } -+ return vec4(0.0); -+} -+ -+// The sun's glint on the waves (GGX, a rough lobe so it glitters instead of aliasing). -+fn glint(n: vec3, v: vec3, l: vec3) -> f32 { -+ let h = normalize(l + v); -+ let a = 0.05; -+ let nh = max(dot(n, h), 0.0); -+ let d = a * a / (PI * pow(nh * nh * (a * a - 1.0) + 1.0, 2.0)); -+ let f = 0.02 + 0.98 * pow(1.0 - max(dot(v, h), 0.0), 5.0); -+ return min(d * f * 0.25 * max(dot(n, l), 0.0) / max(dot(n, v), 0.1), 12.0); -+} -+ -+// The water over the lit world: the world behind it (refracted by the waves, dimmed and tinted -+// by the water's depth), the sky and the world above it (a screen-space reflection) by Fresnel, -+// and the sun's glint. Pixels without water pass through. -+@fragment -+fn fs_water(in: FullOut) -> @location(0) vec4 { -+ let px = vec2(in.position.xy); -+ let base = textureLoad(lit_tex, px, 0); -+ if base.a > 0.75 { return vec4(base.rgb, 1.0); } -+ let wd = textureLoad(water_depth, px, 0); -+ let g = textureLoad(gbuffer_depth, px, 0); -+ let tint = textureLoad(water_tint, px, 0); -+ let nt = textureLoad(water_normal, px, 0); -+ let p = position_at(in.uv, wd); -+ let dist = length(p); -+ let v = -p / dist; -+ let geometric = normalize(nt.xyz * 2.0 - 1.0); -+ // The waves tilt the flat parts of the surface only. -+ let footprint = dist * frame.resolution.w * 1.4; -+ let grad = wave_gradient(p.xz + frame.water.zw, footprint); -+ let flat_part = smoothstep(0.6, 0.95, geometric.y); -+ var n = normalize(geometric + vec3(-grad.x, 0.0, -grad.y) * flat_part); -+ n = normalize(n + v * max(0.03 - dot(n, v), 0.0)); -+ let ndv = max(dot(n, v), 0.02); -+ -+ // Light on the water: vanilla's lightmap at the surface, and the sun where it reaches. -+ let levels = vec2(nt.a, tint.a); -+ let sky_reach = smoothstep(0.35, 0.95, levels.x); -+ let ambient = textureSampleLevel(lightmap, linear, clamp(vec2(levels.y, levels.x) * (15.0 / 16.0) + 0.5 / 16.0, vec2(0.5 / 16.0), vec2(15.5 / 16.0)), 0.0).rgb; -+ let block = textureSampleLevel(lightmap, linear, vec2(levels.y * (15.0 / 16.0) + 0.5 / 16.0, 0.5 / 16.0), 0.0).rgb; -+ let sun = frame.sun.xyz; -+ let sun_on = frame.sun.w; -+ var sunlit = 0.0; -+ if sun_on > 0.0 && sun.y > 0.0 && sky_reach > 0.0 { sunlit = shadow(p, vec3(0.0, 1.0, 0.0)) * sky_reach; } -+ let volume_light = max(ambient * frame.ambient.x, block * frame.ambient.y) + frame.sun_color.rgb * sun_on * sunlit * 0.8; -+ -+ // Refraction: where the ray through the waves meets the world, against where the flat surface -+ // would send it (so still water looks as vanilla's and only the waves distort). -+ let floor0 = position_at(in.uv, g); -+ let depth0 = max(length(floor0) - dist, 0.0); -+ let bent = refract(-v, n, 0.75) - refract(-v, geometric, 0.75); -+ var uv_floor = in.uv; -+ var floor_z = g; -+ let moved = project(floor0 + bent * min(depth0, 24.0)); -+ if moved.z > 0.0 && on_screen(moved.xy) { -+ let z = textureLoad(gbuffer_depth, texel(moved.xy), 0); -+ // Only what lies behind the water: not the bank or an entity in front of it. -+ if z > 0.0 && z <= wd { -+ uv_floor = moved.xy; -+ floor_z = z; -+ } -+ } -+ let thickness = clamp(length(position_at(uv_floor, floor_z)) - dist, 0.0, 48.0); -+ let behind = textureSampleLevel(lit_tex, linear, uv_floor, 0.0).rgb; -+ let colour = clamp(tint.rgb, vec3(0.03), vec3(1.0)); -+ // The colours the water takes up fastest are the ones it has the least of. -+ let absorb = exp(-(vec3(1.0) - colour) * 0.8 * (thickness + 0.3)); -+ let refracted = behind * absorb + colour * volume_light * 0.9 * (vec3(1.0) - absorb); -+ -+ // Reflection: the screen where the ray stays on it, the sky where it leaves it or misses. -+ var r = reflect(-v, n); -+ r.y = abs(r.y); -+ // Looking down onto it the reflection is a few percent of the picture: the sky alone does. -+ let fresnel = 0.02 + 0.98 * pow(1.0 - ndv, 5.0); -+ var hit = vec4(0.0); -+ if fresnel > 0.04 { hit = reflection(p, r, ign(in.position.xy)); } -+ let sky_vis = mix(0.15, 1.0, sky_reach); -+ let reflected = mix(sky_along(r, sky_vis), hit.rgb, hit.w); -+ -+ var c = refracted * (1.0 - fresnel) + reflected * fresnel; -+ if sun_on > 0.0 && sunlit > 0.0 { -+ c += frame.sun_color.rgb * sun_on * sunlit * glint(n, v, sun); -+ } -+ // Vanilla's fog over it, at the surface. -+ let sph = dist; -+ let cyl = max(length(p.xz), abs(p.y)); -+ let f = max(linear_fog(sph, frame.fog.x, frame.fog.y), linear_fog(cyl, frame.fog.z, frame.fog.w)); -+ c = mix(c, frame.fog_color.rgb, f * frame.fog_color.a); -+ return vec4(max(c, vec3(0.0)), 1.0); - } - "#; - -@@ -829,6 +1110,12 @@ pub fn taa_enabled() -> bool { - *ON.get_or_init(|| std::env::var("HORIZON_HD_TAA").map_or(true, |v| v != "0")) - } - -+/// The water effect is on unless `HORIZON_HD_WATER=0`. -+pub fn water_enabled() -> bool { -+ static ON: std::sync::OnceLock = std::sync::OnceLock::new(); -+ *ON.get_or_init(|| std::env::var("HORIZON_HD_WATER").map_or(true, |v| v != "0")) -+} -+ - /// The Halton sequence's `index`-th number in `base`, in (0, 1). - fn halton(mut index: u32, base: u32) -> f32 { - let (mut f, mut r) = (1.0, 0.0); -@@ -895,6 +1182,22 @@ pub struct Hd { - /// The lit world, and the post pass's output. - pub lit: wgpu::TextureView, - pub fx: wgpu::TextureView, -+ /// Water: its surface colour and normal, its depth (the nearest water surface; 0 elsewhere), -+ /// the lit world with the water over it, and what draws and composites them. -+ water_tint: wgpu::TextureView, -+ water_normal: wgpu::TextureView, -+ water_depth: wgpu::TextureView, -+ lit_water: wgpu::TextureView, -+ water_layout: wgpu::BindGroupLayout, -+ water_pipeline: wgpu::RenderPipeline, -+ water_group: Option<((wgpu::TextureView, wgpu::Sampler), wgpu::BindGroup)>, -+ /// The water effect runs this frame (the scene allows it and there is translucent terrain on -+ /// screen), and `lit_view` is the composite. -+ water_active: bool, -+ /// The frame's translucent terrain exists (set by the renderer before `write`). -+ pub water_geometry: bool, -+ water_composited: bool, -+ started: std::time::Instant, - /// TAA: the resolved frame, and the two histories (one read, one written, alternating). - taa: wgpu::TextureView, - history: [wgpu::TextureView; 2], -@@ -1216,6 +1519,26 @@ impl Hd { - min_binding_size: None, - }, - UNFILTERED, -+ DEPTH, -+ ]), -+ }); -+ let water_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD water"), -+ entries: &entries(&[ -+ UNIFORM, -+ FLOAT, -+ DEPTH, -+ UNFILTERED, -+ UNFILTERED, -+ DEPTH, -+ wgpu::BindingType::Texture { -+ sample_type: wgpu::TextureSampleType::Depth, -+ view_dimension: wgpu::TextureViewDimension::D2Array, -+ multisampled: false, -+ }, -+ wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), -+ FLOAT, -+ SAMPLER, - ]), - }); - let taa_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -@@ -1278,6 +1601,16 @@ impl Hd { - let tone = module(device, "HD tone mapping", TONEMAP); - let ao = module(device, "HD ambient occlusion", AO); - let ao_blur = module(device, "HD ambient occlusion blur", AO_BLUR); -+ let water = module(device, "HD water", WATER); -+ let water_pipeline = fullscreen( -+ device, -+ "HD water", -+ &water_layout, -+ &water, -+ "fs_water", -+ HDR, -+ None, -+ ); - let ao_pipeline = fullscreen( - device, - "HD ambient occlusion", -@@ -1436,6 +1769,23 @@ impl Hd { - .view, - lit: target(device, "HD lit", [1, 1], HDR).view, - fx: target(device, "HD post", [1, 1], HDR).view, -+ water_tint: target(device, "HD water colour", [1, 1], WATER_TINT).view, -+ water_normal: target(device, "HD water normal", [1, 1], WATER_NORMAL).view, -+ water_depth: target( -+ device, -+ "HD water depth", -+ [1, 1], -+ wgpu::TextureFormat::Depth32Float, -+ ) -+ .view, -+ lit_water: target(device, "HD lit with water", [1, 1], HDR).view, -+ water_layout, -+ water_pipeline, -+ water_group: None, -+ water_active: false, -+ water_geometry: false, -+ water_composited: false, -+ started: std::time::Instant::now(), - taa: target(device, "HD TAA", [1, 1], HDR).view, - history: [ - target(device, "HD TAA history", [1, 1], HDR).view, -@@ -1502,6 +1852,17 @@ impl Hd { - self.depth = target(device, "HD depth", size, wgpu::TextureFormat::Depth32Float).view; - self.lit = target(device, "HD lit", size, HDR).view; - self.fx = target(device, "HD post", size, HDR).view; -+ self.water_tint = target(device, "HD water colour", size, WATER_TINT).view; -+ self.water_normal = target(device, "HD water normal", size, WATER_NORMAL).view; -+ self.water_depth = target( -+ device, -+ "HD water depth", -+ size, -+ wgpu::TextureFormat::Depth32Float, -+ ) -+ .view; -+ self.lit_water = target(device, "HD lit with water", size, HDR).view; -+ self.water_group = None; - self.taa = target(device, "HD TAA", out, HDR).view; - self.history = [ - target(device, "HD TAA history", out, HDR).view, -@@ -1591,6 +1952,7 @@ impl Hd { - let (dir, strength, color) = light_of(scene); - let cascades = scene.cascades; - let night = scene.dark.clamp(0.0, 1.0); -+ self.water_active = scene.water && water_enabled() && self.water_geometry; - self.taa_active = scene.taa && taa_enabled(); - // A long jump of the camera (a teleport) starts the history over. - if !self.taa_active || scene.camera_delta.length() > 64.0 { -@@ -1604,6 +1966,19 @@ impl Hd { - scene.camera_delta.z, - if self.history_ready { 1.0 } else { 0.0 }, - ], -+ view_proj: scene.view_proj.to_cols_array_2d(), -+ water: [ -+ if self.water_active { 1.0 } else { 0.0 }, -+ (self.started.elapsed().as_secs_f64() % 600.0) as f32, -+ scene.camera_mod[0], -+ scene.camera_mod[1], -+ ], -+ sky_color: [ -+ scene.sky_color[0], -+ scene.sky_color[1], -+ scene.sky_color[2], -+ if scene.sky { 1.0 } else { 0.0 }, -+ ], - taa_params: [ - if self.upscaling() { - UPSCALE_HISTORY -@@ -1821,6 +2196,10 @@ impl Hd { - binding: 13, - resource: wgpu::BindingResource::TextureView(&self.ao_blurred), - }, -+ wgpu::BindGroupEntry { -+ binding: 14, -+ resource: wgpu::BindingResource::TextureView(&self.water_depth), -+ }, - ], - }); - self.light_group = Some((key, group)); -@@ -1828,6 +2207,7 @@ impl Hd { - let Some((_, group)) = &self.light_group else { - return; - }; -+ self.water_composited = false; - Self::draw( - encoder, - "HD lighting", -@@ -1894,7 +2274,7 @@ impl Hd { - screen: &wgpu::TextureView, - ) { - let source = match source { -- HdSource::Lit => self.lit.clone(), -+ HdSource::Lit => self.lit_view().clone(), - HdSource::Fx => self.fx.clone(), - HdSource::Taa => self.taa.clone(), - }; -@@ -1993,7 +2373,7 @@ impl Hd { - return source; - } - let input = match source { -- HdSource::Lit => self.lit.clone(), -+ HdSource::Lit => self.lit_view().clone(), - HdSource::Fx => self.fx.clone(), - HdSource::Taa => return source, - }; -@@ -2052,6 +2432,102 @@ impl Hd { - HdSource::Taa - } - -+ /// The lit world as later passes see it: with the water over it when the effect ran. -+ pub fn lit_view(&self) -> &wgpu::TextureView { -+ if self.water_composited { -+ &self.lit_water -+ } else { -+ &self.lit -+ } -+ } -+ -+ /// The water effect runs this frame (the renderer draws the water quads with `water_pass`). -+ pub fn water_active(&self) -> bool { -+ self.water_active -+ } -+ -+ /// The water pass's attachments: the surface colour and normal, and the water's own depth, -+ /// all cleared. The caller draws the translucent terrain with `fs_water`. -+ pub fn water_pass<'e>( -+ &mut self, -+ encoder: &'e mut wgpu::CommandEncoder, -+ ) -> wgpu::RenderPass<'e> { -+ let clear = |view| { -+ Some(wgpu::RenderPassColorAttachment { -+ view, -+ resolve_target: None, -+ depth_slice: None, -+ ops: wgpu::Operations { -+ load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), -+ store: wgpu::StoreOp::Store, -+ }, -+ }) -+ }; -+ encoder -+ .begin_render_pass(&wgpu::RenderPassDescriptor { -+ label: Some("HD water"), -+ timestamp_writes: stamp(&mut self.timing, "hd water"), -+ color_attachments: &[clear(&self.water_tint), clear(&self.water_normal)], -+ depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { -+ view: &self.water_depth, -+ depth_ops: Some(wgpu::Operations { -+ load: wgpu::LoadOp::Clear(0.0), -+ store: wgpu::StoreOp::Store, -+ }), -+ stencil_ops: None, -+ }), -+ ..Default::default() -+ }) -+ .forget_lifetime() -+ } -+ -+ /// Lays the water over the lit world (refraction, reflection, glint) into a target of its -+ /// own; `lit_view` is that target after this. -+ pub fn water_composite( -+ &mut self, -+ device: &wgpu::Device, -+ encoder: &mut wgpu::CommandEncoder, -+ lightmap: (&wgpu::TextureView, &wgpu::Sampler), -+ ) { -+ if !self.water_active { -+ return; -+ } -+ let key = (lightmap.0.clone(), lightmap.1.clone()); -+ if self.water_group.as_ref().is_none_or(|(k, _)| *k != key) { -+ let group = self.make_group( -+ device, -+ "HD water", -+ &self.water_layout, -+ &[ -+ wgpu::BindingResource::Buffer(self.frame.as_entire_buffer_binding()), -+ wgpu::BindingResource::TextureView(&self.lit), -+ wgpu::BindingResource::TextureView(&self.depth), -+ wgpu::BindingResource::TextureView(&self.water_tint), -+ wgpu::BindingResource::TextureView(&self.water_normal), -+ wgpu::BindingResource::TextureView(&self.water_depth), -+ wgpu::BindingResource::TextureView(&self.shadow_array), -+ wgpu::BindingResource::Sampler(&self.shadow_sampler), -+ wgpu::BindingResource::TextureView(lightmap.0), -+ wgpu::BindingResource::Sampler(&self.sampler), -+ ], -+ ); -+ self.water_group = Some((key, group)); -+ } -+ let Some((_, group)) = &self.water_group else { -+ return; -+ }; -+ Self::draw( -+ encoder, -+ "HD water composite", -+ &self.lit_water, -+ false, -+ &self.water_pipeline, -+ group, -+ stamp(&mut self.timing, "hd water composite"), -+ ); -+ self.water_composited = true; -+ } -+ - /// The ambient occlusion from the G-buffer's depth and normals: the half-resolution pass, then - /// its blur. Run after the G-buffer pass; nothing when the strength is 0. - pub fn ao(&mut self, encoder: &mut wgpu::CommandEncoder) { -@@ -2181,6 +2657,7 @@ mod tests { - ("taa", TAA), - ("ao", AO), - ("ao blur", AO_BLUR), -+ ("water", WATER), - ] { - let source = format!("{COMMON}{body}"); - let module = naga::front::wgsl::parse_str(&source).unwrap_or_else(|e| { -diff --git a/crates/horizon-client/src/terrain.wgsl b/crates/horizon-client/src/terrain.wgsl -index c44449cc7..0d5d2fc89 100644 ---- a/crates/horizon-client/src/terrain.wgsl -+++ b/crates/horizon-client/src/terrain.wgsl -@@ -182,6 +182,25 @@ fn face_normal(pos: vec3) -> vec3 { - out.light = vec4(clamp(in.lm, vec2(0.0), vec2(1.0)), 0.0, 1.0); - return out; - } -+// AirDef fork, HD water: the water quads once more, after the G-buffer, into small targets of -+// their own: the surface colour (the water sprite times the biome tint) with the block light, and -+// the face normal with the sky light. The layers are the water sprites' (gfx.rs fills them in; -+// nothing else is water: glass, ice and the rest stay as vanilla draws them). -+override WATER_STILL: u32 = 0xffffu; -+override WATER_FLOW: u32 = 0xffffu; -+struct WaterOut { -+ @location(0) tint: vec4, -+ @location(1) normal: vec4, -+} -+@fragment fn fs_water(in: TerrainOut) -> WaterOut { -+ let layer = in.layer & 0x7fffu; -+ if layer != WATER_STILL && layer != WATER_FLOW { discard; } -+ let color = sprite(in.uv, layer) * in.tint; -+ var out: WaterOut; -+ out.tint = vec4(color.rgb, clamp(in.lm.y, 0.0, 1.0)); -+ out.normal = vec4(face_normal(in.pos) * 0.5 + 0.5, clamp(in.lm.x, 0.0, 1.0)); -+ return out; -+} - // AirDef fork: the sun's shadow map — depth only, cut-out leaves leave holes. - @fragment fn fs_shadow(in: TerrainOut) { - let a = sprite(in.uv, in.layer & 0x7fffu).a; --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0020-HD-water-no-vanilla-texture-on-the-surface-the-biome.patch b/horizon-patches/0020-HD-water-no-vanilla-texture-on-the-surface-the-biome.patch deleted file mode 100644 index 8f68f53..0000000 --- a/horizon-patches/0020-HD-water-no-vanilla-texture-on-the-surface-the-biome.patch +++ /dev/null @@ -1,54 +0,0 @@ -From dbc6f8a6e9c2eeabe1de15f1794013c90be7faa5 Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 17:56:04 +0300 -Subject: [PATCH 20/42] HD water: no vanilla texture on the surface (the biome - colour only), a darker, deeper colour - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx/hd.rs | 9 ++++++--- - crates/horizon-client/src/terrain.wgsl | 6 ++++-- - 2 files changed, 10 insertions(+), 5 deletions(-) - -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index f5caa2d0b..8070aab3c 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -591,10 +591,13 @@ fn fs_water(in: FullOut) -> @location(0) vec4 { - } - let thickness = clamp(length(position_at(uv_floor, floor_z)) - dist, 0.0, 48.0); - let behind = textureSampleLevel(lit_tex, linear, uv_floor, 0.0).rgb; -- let colour = clamp(tint.rgb, vec3(0.03), vec3(1.0)); -+ // A deep, dark water: the biome's colour darkened and a little greyed, so it reads as -+ // depth rather than as bright paint. -+ let biome = clamp(tint.rgb, vec3(0.03), vec3(1.0)); -+ let colour = mix(vec3(dot(biome, vec3(0.333))), biome, 0.8) * vec3(0.55, 0.68, 0.8); - // The colours the water takes up fastest are the ones it has the least of. -- let absorb = exp(-(vec3(1.0) - colour) * 0.8 * (thickness + 0.3)); -- let refracted = behind * absorb + colour * volume_light * 0.9 * (vec3(1.0) - absorb); -+ let absorb = exp(-(vec3(1.0) - colour) * 1.1 * (thickness + 0.3)); -+ let refracted = behind * absorb + colour * volume_light * 0.6 * (vec3(1.0) - absorb); - - // Reflection: the screen where the ray stays on it, the sky where it leaves it or misses. - var r = reflect(-v, n); -diff --git a/crates/horizon-client/src/terrain.wgsl b/crates/horizon-client/src/terrain.wgsl -index 0d5d2fc89..e892d5a0f 100644 ---- a/crates/horizon-client/src/terrain.wgsl -+++ b/crates/horizon-client/src/terrain.wgsl -@@ -195,9 +195,11 @@ struct WaterOut { - @fragment fn fs_water(in: TerrainOut) -> WaterOut { - let layer = in.layer & 0x7fffu; - if layer != WATER_STILL && layer != WATER_FLOW { discard; } -- let color = sprite(in.uv, layer) * in.tint; -+ // The biome's colour at the texture's mean brightness, not the texture: the HD water draws -+ // its own surface (waves, reflections), and vanilla's ripples on top would look painted on. -+ let color = vec3(0.62) * in.tint.rgb; - var out: WaterOut; -- out.tint = vec4(color.rgb, clamp(in.lm.y, 0.0, 1.0)); -+ out.tint = vec4(color, clamp(in.lm.y, 0.0, 1.0)); - out.normal = vec4(face_normal(in.pos) * 0.5 + 0.5, clamp(in.lm.x, 0.0, 1.0)); - return out; - } --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0021-HD-sky-and-light-a-physical-atmosphere-Rayleigh-Mie-.patch b/horizon-patches/0021-HD-sky-and-light-a-physical-atmosphere-Rayleigh-Mie-.patch deleted file mode 100644 index 9993f59..0000000 --- a/horizon-patches/0021-HD-sky-and-light-a-physical-atmosphere-Rayleigh-Mie-.patch +++ /dev/null @@ -1,945 +0,0 @@ -From 13e74537dd6e63482e3613e0e416de1158944dfe Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 18:38:19 +0300 -Subject: [PATCH 21/42] HD sky and light: a physical atmosphere (Rayleigh, Mie, - ozone; constants and transmittance after Photon, MIT) for the sky, haze, fog - and water reflections; warmer sunlight from the atmosphere's transmittance, a - gentle sky-coloured ambient, soft PCSS-lite shadows near the player, a haze - round the sun; lighter water with fine ripples - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx.rs | 4 + - .../src/gfx/THIRD_PARTY_PHOTON.md | 34 ++ - crates/horizon-client/src/gfx/hd.rs | 259 +++++++++++++- - crates/horizon-client/src/gfx/hd_sky.rs | 324 ++++++++++++++++++ - crates/horizon-client/src/terrain.wgsl | 2 +- - 5 files changed, 604 insertions(+), 19 deletions(-) - create mode 100644 crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md - create mode 100644 crates/horizon-client/src/gfx/hd_sky.rs - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index f9dab8abf..d01131433 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -72,6 +72,7 @@ mod cull; - mod end_sky; - mod flat; - mod hd; -+mod hd_sky; - mod hdr; - pub mod lightning; - mod lod; -@@ -4275,6 +4276,8 @@ impl Renderer { - camera.z.rem_euclid(256.0) as f32, - ], - water: !frame.underwater, -+ sunrise: frame.sky.sunrise_color, -+ sky_fog_end: frame.fog.map_or(frame.distance.min(512.0), |f| f.sky_end), - }; - let (shadow_dir, shadow_strength, _) = hd::light_of(&hd_scene); - let mut hd_cascades = hd::cascades(camera, shadow_dir); -@@ -5817,6 +5820,7 @@ impl Renderer { - { - // AirDef fork: lit by the mods' lights into the HD target, the mod's post pass on - // that, then bloom and the tone curve onto the screen. -+ hd.sky(&mut encoder); - hd.light( - &self.device, - &mut encoder, -diff --git a/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md b/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md -new file mode 100644 -index 000000000..03d945001 ---- /dev/null -+++ b/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md -@@ -0,0 +1,34 @@ -+# Third-party code: Photon -+ -+Parts of the HD renderer's atmosphere (`hd_sky.rs`) are ported from, or follow closely, the -+Photon shader pack by sixthsurge, `shaders/include/sky/atmosphere.glsl` (and the idea of the water -+waves in `shaders/include/misc/water_normal.glsl`) at commit -+26c2906ee02cc484359b835fba6993d443d45966, https://github.com/sixthsurge/photon -+ -+What is taken: the air's scattering and absorption coefficients (Rayleigh, Mie, ozone), the scale -+heights, the ozone density profile, the Mie albedo and Henyey-Greenstein anisotropy, the sunlight -+colour outside the atmosphere, and the Chapman-function approximation of the sun's transmittance. -+The sky-view texture, its march, the sun's colour and everything else are our own. -+ -+Photon is distributed under the MIT License: -+ -+ The MIT License -+ -+ Copyright © 2025 Benjamin Stott "sixthsurge" -+ -+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software -+ and associated documentation files (the "Software"), to deal in the Software without -+ restriction, including without limitation the rights to use, copy, modify, merge, publish, -+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -+ Software is furnished to do so, subject to the following conditions: -+ -+ The above copyright notice and this permission notice shall be included in all copies or -+ substantial portions of the Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -+ -+No other shader pack's code (BSL, Complementary and so on) may be used: their licences forbid it. -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index 8070aab3c..031e9f2de 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -5,6 +5,7 @@ - //! into a high-dynamic-range target; the mod's post pass runs on that; then - //! bright parts bloom and a tone curve brings it to the screen — vanilla's - //! range (up to about 0.8) left as it is, only what is brighter rolled off. -+use super::hd_sky; - use super::pack_timing::PassTiming; - use glam::Mat4; - use std::sync::atomic::{AtomicU64, Ordering}; -@@ -101,6 +102,12 @@ struct HdFrame { - water: [f32; 4], - /// rgb: the sky's colour; w: 1 when there is a sky. - sky_color: [f32; 4], -+ /// xyz: towards the sun itself (not the moon); w: 1 when the atmosphere draws the sky. -+ sky_sun: [f32; 4], -+ /// x: vanilla's sky fog end; z: how overcast the sky is (rain: 0 clear, 1 vanilla's colours). -+ sky_extra: [f32; 4], -+ /// Vanilla's sunrise colour fan (rgb, alpha), taken out of the sky before the atmosphere is put in. -+ sunrise: [f32; 4], - } - - /// What the world around looks like this frame (from the renderer's globals). -@@ -131,6 +138,9 @@ pub struct HdScene { - pub camera_mod: [f32; 2], - /// The water effect may run (not underwater, not disabled). - pub water: bool, -+ /// Vanilla's sunrise fan colour and its sky fog end (to subtract vanilla's sky from the world). -+ pub sunrise: [f32; 4], -+ pub sky_fog_end: f32, - } - - /// The cascades' half sizes (blocks) and shadow map size. -@@ -155,6 +165,9 @@ struct HdFrame { - view_proj: mat4x4, - water: vec4, - sky_color: vec4, -+ sky_sun: vec4, -+ sky_extra: vec4, -+ sunrise: vec4, - } - struct FullOut { - @builtin(position) position: vec4, -@@ -195,6 +208,9 @@ struct Light { - @group(0) @binding(13) var ao_tex: texture_2d; - // The water pass's depth: the nearest water surface (0 where there is none). - @group(0) @binding(14) var water_depth: texture_depth_2d; -+//SKY15 -+// The pixel being lit, for the shadow filter's noise. -+var frag_px: vec2; - - // The occlusion at a full-resolution pixel `dist` away: the four nearest half-resolution texels - // weighted by closeness and by how near their distance is, so it doesn't bleed across edges. -@@ -239,6 +255,42 @@ fn shadow(p: vec3, n: vec3) -> f32 { - return shadow_taps(p, n, 1); - } - -+// A point of a disc of radius 1 (Vogel's spiral), turned by `angle`. -+fn vogel(i: i32, count: i32, angle: f32) -> vec2 { -+ let r = sqrt((f32(i) + 0.5) / f32(count)); -+ let a = f32(i) * 2.39996323 + angle; -+ return vec2(cos(a), sin(a)) * r; -+} -+ -+// The near cascade's soft shadow: a blocker search sets the penumbra's width (wider the farther -+// the shadow's caster is above the receiver), then a ring of taps turned by a per-pixel noise -+// filters it; the temporal pass averages the noise. `s`: the point in the cascade (uv, depth), -+// `texel`: the cascade's texel in blocks. -+fn soft_shadow(s: vec3, texel: f32) -> f32 { -+ let angle = ign(frag_px) * 6.2831853; -+ var blocker = 0.0; -+ var found = 0.0; -+ for (var i = 0; i < 8; i++) { -+ let o = vogel(i, 8, angle) * (6.0 / 2048.0); -+ let q = clamp(vec2((s.xy + o) * 2048.0), vec2(0), vec2(2047)); -+ let z = textureLoad(shadow_map, q, 0, 0); -+ if z < s.z - 0.0006 { -+ blocker += z; -+ found += 1.0; -+ } -+ } -+ if found < 0.5 { return 1.0; } -+ // The caster's height above the receiver, in blocks (the cascade's depth spans 799 blocks). -+ let height = (s.z - blocker / found) * 799.0; -+ let radius = clamp(height * 0.18 / texel, 1.6, 8.0); -+ var lit = 0.0; -+ for (var i = 0; i < 12; i++) { -+ let o = vogel(i, 12, angle + 1.7) * (radius / 2048.0); -+ lit += textureSampleCompareLevel(shadow_map, shadow_sampler, s.xy + o, 0, s.z - 0.0005 - 0.00005 * radius); -+ } -+ return lit / 12.0; -+} -+ - // `shadow` with a (2 * r + 1) square of taps: the floor under water takes one (the water dims and - // blurs it anyway). - fn shadow_taps(p: vec3, n: vec3, r: i32) -> f32 { -@@ -249,13 +301,16 @@ fn shadow_taps(p: vec3, n: vec3, r: i32) -> f32 { - let s = cascade_at(q, c); - if any(s.xy < vec2(0.0)) || any(s.xy > vec2(1.0)) || s.z > 1.0 { return 1.0; } - var lit = 0.0; -+ let fade = clamp((frame.shadow.z - dist) / 16.0, 0.0, 1.0); -+ if r == 1 && c == 0 { -+ return mix(1.0, soft_shadow(s, texel), fade); -+ } - let step = 1.0 / 2048.0; - for (var y = -r; y <= r; y++) { - for (var x = -r; x <= r; x++) { - lit += textureSampleCompareLevel(shadow_map, shadow_sampler, s.xy + vec2(f32(x), f32(y)) * step, c, s.z - 0.0005); - } - } -- let fade = clamp((frame.shadow.z - dist) / 16.0, 0.0, 1.0); - return mix(1.0, lit / f32((2 * r + 1) * (2 * r + 1)), fade); - } - -@@ -300,7 +355,31 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let halo_start = tiles[tile + 2u]; - let halo_all = tiles[tile + 3u] == 0xffffffffu; - let halo_count = select(tiles[tile + 3u], count, halo_all); -+ frag_px = in.position.xy; -+ let atmosphere = frame.sky_sun.w > 0.5; - var c = scene; -+ // The sky: the atmosphere's, with what the world drew over vanilla's plain sky (sun, moon, -+ // stars, clouds) kept on top. -+ if d <= 0.0 && atmosphere { -+ var kept = max(scene - vanilla_sky(view), vec3(0.0)); -+ var sky = atmosphere_sky(view); -+ // The sun: vanilla's square sprite is taken out and a round disc with a soft haze round -+ // it (warm, strongest low) is drawn in its place. -+ let toward = dot(view, frame.sky_sun.xyz); -+ kept *= 1.0 - smoothstep(0.965, 0.975, toward); -+ let clear = 1.0 - frame.sky_extra.z; -+ let shine = smoothstep(-0.04, 0.02, frame.sky_sun.y) * clear; -+ let glow = max(toward, 0.0); -+ let angle = acos(clamp(toward, -1.0, 1.0)); -+ sky += frame.sun_color.rgb * shine * (pow(glow, 24.0) * 0.07 + pow(glow, 400.0) * 0.4 + 6.0 * (1.0 - smoothstep(0.016, 0.022, angle))); -+ c = sky + kept; -+ } -+ // Clouds (vanilla's, drawn into the world with depth but not into the G-buffer): lit by the low -+ // sun's colour at dusk and dawn instead of vanilla's flat white. -+ if atmosphere && !water && d > 0.0 && g <= 0.0 && p.y > 8.0 && dot(p, p) > 2304.0 { -+ let low = 1.0 - smoothstep(0.05, 0.5, frame.sky_sun.y); -+ c = scene * mix(vec3(1.0), mix(frame.sun_color.rgb, vec3(1.0), 0.45) * 1.1, low * 0.7 * (1.0 - frame.sky_extra.z)); -+ } - let surface = water || (d > 0.0 && abs(d - g) <= max(d, g) * 0.002); - if surface { - let n = normalize(textureLoad(normal_tex, px, 0).xyz); -@@ -323,7 +402,17 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - if frame.ambient.w > 0.0 && !water { occlusion = ao_at(px, length(p)); } - let ao_ambient = mix(1.0, occlusion, frame.ambient.w); - let ao_lights = mix(1.0, occlusion, frame.ambient.w * 0.5); -- var light = max(base * frame.ambient.x, block * frame.ambient.y) * ao_ambient + direct; -+ // The sky's light is bluish from above and carries the horizon's colour from the sides; -+ // faces turned up take more of it than the ones turned down. -+ var sky_tint = vec3(1.0); -+ if atmosphere { -+ let reach = smoothstep(0.25, 0.9, lm.x); -+ let up = clamp(n.y * 0.5 + 0.5, 0.0, 1.0); -+ let shape = mix(0.66, 1.1, smoothstep(0.1, 1.0, up)); -+ let hue = mix(vec3(1.0), sky_light_tint(n), 0.4 * (1.0 - 0.5 * frame.sun_color.a)); -+ sky_tint = mix(vec3(1.0), hue * shape, reach); -+ } -+ var light = max(base * sky_tint * frame.ambient.x, block * frame.ambient.y) * ao_ambient + direct; - // The mods' lights. - for (var k = 0u; k < surface_count; k++) { - let l = lights[select(tiles[surface_start + k], k, surface_all)]; -@@ -340,7 +429,12 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let sph = length(p); - let cyl = max(length(p.xz), abs(p.y)); - let f = max(linear_fog(sph, frame.fog.x, frame.fog.y), linear_fog(cyl, frame.fog.z, frame.fog.w)); -- c = mix(c, frame.fog_color.rgb, f * frame.fog_color.a); -+ let haze = haze_colour(view); -+ c = mix(c, haze, f * frame.fog_color.a); -+ // Aerial perspective: far things take the horizon's colour a little before the fog does. -+ if atmosphere && frame.fog.x > 1.0e6 { -+ c = mix(c, haze, (1.0 - exp(-sph / 700.0)) * 0.5); -+ } - } - // Light in the air: the sun's rays between the shadows, and halos round the mods' lights. - let far = select(min(length(p), frame.shadow.z), frame.shadow.z, d <= 0.0); -@@ -387,6 +481,7 @@ const REFLECT_RANGE: f32 = 96.0; - @group(0) @binding(7) var shadow_sampler: sampler_comparison; - @group(0) @binding(8) var lightmap: texture_2d; - @group(0) @binding(9) var linear: sampler; -+//SKY10 - - fn ign(px: vec2) -> f32 { - let p = px + vec2(5.588238 * frame.params.w); -@@ -417,7 +512,7 @@ fn texel(uv: vec2) -> vec2 { - // are whole cycles per 600 s, so the sum repeats every 256 blocks and 10 minutes and holds still - // in the world as the camera moves. x, y: the vector in units of 2 pi / 256; z: the slope the - // wave adds; w: its phase. --const WAVES = array, 10>( -+const WAVES = array, 26>( - vec4(3.0, 1.0, 0.040, 0.3), - vec4(-2.0, 5.0, 0.045, 2.1), - vec4(7.0, 2.0, 0.040, 4.4), -@@ -428,17 +523,36 @@ const WAVES = array, 10>( - vec4(9.0, 23.0, 0.030, 2.7), - vec4(-27.0, 19.0, 0.025, 4.9), - vec4(31.0, 30.0, 0.025, 1.8), -+ // Fine ripples (wavelengths of 0.7 to 3 blocks) for the many small sun sparkles: slopes small, -+ // faded out by distance and pixel size like the rest. -+ vec4(83.0, 41.0, 0.042, 0.4), -+ vec4(-97.0, 59.0, 0.042, 3.1), -+ vec4(71.0, -113.0, 0.042, 5.0), -+ vec4(131.0, 89.0, 0.040, 1.5), -+ vec4(-157.0, -103.0, 0.040, 4.2), -+ vec4(191.0, -67.0, 0.038, 2.4), -+ vec4(-223.0, 149.0, 0.038, 0.8), -+ vec4(101.0, 241.0, 0.036, 3.7), -+ vec4(-263.0, -181.0, 0.034, 5.5), -+ vec4(311.0, 127.0, 0.034, 2.0), -+ // Finer still (0.35 to 0.6 blocks): the small sparkles. -+ vec4(397.0, -263.0, 0.050, 1.1), -+ vec4(-467.0, 331.0, 0.050, 4.6), -+ vec4(521.0, 449.0, 0.045, 3.0), -+ vec4(-613.0, -199.0, 0.045, 5.9), -+ vec4(293.0, 577.0, 0.045, 0.2), -+ vec4(-701.0, 263.0, 0.040, 2.8), - ); - - // The gradient of the water's height at world x, z: the waves that a pixel `footprint` blocks - // wide can still resolve (finer ones fade out, or they would shimmer). - fn wave_gradient(xz: vec2, footprint: f32) -> vec2 { - var grad = vec2(0.0); -- for (var i = 0; i < 10; i++) { -+ for (var i = 0; i < 26; i++) { - let w = WAVES[i]; - let k = w.xy * (2.0 * PI / 256.0); - let len = length(k); -- let fade = clamp((2.0 * PI / len) / max(footprint, 0.0001) * 0.25 - 0.5, 0.0, 1.0); -+ let fade = clamp((2.0 * PI / len) / max(footprint, 0.0001) * 0.2 - 0.6, 0.0, 1.0); - let cycles = round(sqrt(9.8 * len) * 0.5 * 600.0 / (2.0 * PI)); - let omega = cycles * 2.0 * PI / 600.0; - let phase = dot(k, xz) - omega * frame.water.y + w.w; -@@ -481,7 +595,9 @@ fn linear_fog(d: f32, start: f32, end: f32) -> f32 { - // the zenith's, darker where the water lies under cover (`vis` is the sky light there). - fn sky_along(r: vec3, vis: f32) -> vec3 { - var c = frame.fog_color.rgb; -- if frame.sky_color.w > 0.5 { -+ if frame.sky_sun.w > 0.5 { -+ c = atmosphere_sky(r); -+ } else if frame.sky_color.w > 0.5 { - c = mix(frame.fog_color.rgb, frame.sky_color.rgb, smoothstep(0.0, 0.5, r.y)); - } - return c * mix(0.12, 1.0, vis); -@@ -531,7 +647,7 @@ fn reflection(p: vec3, r: vec3, jitter: f32) -> vec4 { - // The sun's glint on the waves (GGX, a rough lobe so it glitters instead of aliasing). - fn glint(n: vec3, v: vec3, l: vec3) -> f32 { - let h = normalize(l + v); -- let a = 0.05; -+ let a = 0.07; - let nh = max(dot(n, h), 0.0); - let d = a * a / (PI * pow(nh * nh * (a * a - 1.0) + 1.0, 2.0)); - let f = 0.02 + 0.98 * pow(1.0 - max(dot(v, h), 0.0), 5.0); -@@ -565,7 +681,10 @@ fn fs_water(in: FullOut) -> @location(0) vec4 { - // Light on the water: vanilla's lightmap at the surface, and the sun where it reaches. - let levels = vec2(nt.a, tint.a); - let sky_reach = smoothstep(0.35, 0.95, levels.x); -- let ambient = textureSampleLevel(lightmap, linear, clamp(vec2(levels.y, levels.x) * (15.0 / 16.0) + 0.5 / 16.0, vec2(0.5 / 16.0), vec2(15.5 / 16.0)), 0.0).rgb; -+ var ambient = textureSampleLevel(lightmap, linear, clamp(vec2(levels.y, levels.x) * (15.0 / 16.0) + 0.5 / 16.0, vec2(0.5 / 16.0), vec2(15.5 / 16.0)), 0.0).rgb; -+ if frame.sky_sun.w > 0.5 { -+ ambient *= mix(vec3(1.0), sky_light_tint(vec3(0.0, 1.0, 0.0)), 0.7 * sky_reach); -+ } - let block = textureSampleLevel(lightmap, linear, vec2(levels.y * (15.0 / 16.0) + 0.5 / 16.0, 0.5 / 16.0), 0.0).rgb; - let sun = frame.sun.xyz; - let sun_on = frame.sun.w; -@@ -594,10 +713,10 @@ fn fs_water(in: FullOut) -> @location(0) vec4 { - // A deep, dark water: the biome's colour darkened and a little greyed, so it reads as - // depth rather than as bright paint. - let biome = clamp(tint.rgb, vec3(0.03), vec3(1.0)); -- let colour = mix(vec3(dot(biome, vec3(0.333))), biome, 0.8) * vec3(0.55, 0.68, 0.8); -+ let colour = mix(vec3(dot(biome, vec3(0.333))), biome, 0.85) * vec3(0.92, 1.0, 1.0); - // The colours the water takes up fastest are the ones it has the least of. -- let absorb = exp(-(vec3(1.0) - colour) * 1.1 * (thickness + 0.3)); -- let refracted = behind * absorb + colour * volume_light * 0.6 * (vec3(1.0) - absorb); -+ let absorb = exp(-(vec3(1.0) - colour) * 0.8 * (thickness + 0.3)); -+ let refracted = behind * absorb + colour * volume_light * mix(0.85, 0.4, frame.sun_color.a) * (vec3(1.0) - absorb); - - // Reflection: the screen where the ray stays on it, the sky where it leaves it or misses. - var r = reflect(-v, n); -@@ -617,7 +736,11 @@ fn fs_water(in: FullOut) -> @location(0) vec4 { - let sph = dist; - let cyl = max(length(p.xz), abs(p.y)); - let f = max(linear_fog(sph, frame.fog.x, frame.fog.y), linear_fog(cyl, frame.fog.z, frame.fog.w)); -- c = mix(c, frame.fog_color.rgb, f * frame.fog_color.a); -+ let haze = haze_colour(-v); -+ c = mix(c, haze, f * frame.fog_color.a); -+ if frame.sky_sun.w > 0.5 && frame.fog.x > 1.0e6 { -+ c = mix(c, haze, (1.0 - exp(-dist / 700.0)) * 0.5); -+ } - return vec4(max(c, vec3(0.0)), 1.0); - } - "#; -@@ -1200,6 +1323,14 @@ pub struct Hd { - /// The frame's translucent terrain exists (set by the renderer before `write`). - pub water_geometry: bool, - water_composited: bool, -+ /// The sky-view texture (see `hd_sky`), the pass that marches it and its bind group. -+ sky_lut: wgpu::TextureView, -+ sky_pipeline: wgpu::RenderPipeline, -+ sky_group: wgpu::BindGroup, -+ /// The sun's height the sky-view texture was marched for (it depends on nothing else), and -+ /// whether this frame's sun has moved enough to march it again. -+ sky_drawn_for: f32, -+ sky_stale: bool, - started: std::time::Instant, - /// TAA: the resolved frame, and the two histories (one read, one written, alternating). - taa: wgpu::TextureView, -@@ -1403,10 +1534,23 @@ fn ao_strength_default() -> f32 { - .unwrap_or(AO_STRENGTH) - } - -+/// The shader's source: the common part, then the body with the sky-reading functions where it -+/// has a marker (`//SKY15`: the sky-view texture is binding 15, `//SKY10`: binding 10). -+fn assemble(body: &str) -> String { -+ let mut body = body.to_owned(); -+ for binding in ["15", "10"] { -+ body = body.replace( -+ &format!("//SKY{binding}"), -+ &hd_sky::SKY_FUNCS.replace("SKYBIND", binding), -+ ); -+ } -+ format!("{COMMON}{body}") -+} -+ - fn module(device: &wgpu::Device, label: &str, body: &str) -> wgpu::ShaderModule { - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some(label), -- source: wgpu::ShaderSource::Wgsl(format!("{COMMON}{body}").into()), -+ source: wgpu::ShaderSource::Wgsl(assemble(body).into()), - }) - } - -@@ -1523,6 +1667,7 @@ impl Hd { - }, - UNFILTERED, - DEPTH, -+ FLOAT, - ]), - }); - let water_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -@@ -1542,8 +1687,13 @@ impl Hd { - wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), - FLOAT, - SAMPLER, -+ FLOAT, - ]), - }); -+ let sky_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { -+ label: Some("HD sky"), -+ entries: &entries(&[UNIFORM]), -+ }); - let taa_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("HD TAA"), - entries: &entries(&[UNIFORM, UNFILTERED, DEPTH, FLOAT, SAMPLER]), -@@ -1605,6 +1755,16 @@ impl Hd { - let ao = module(device, "HD ambient occlusion", AO); - let ao_blur = module(device, "HD ambient occlusion blur", AO_BLUR); - let water = module(device, "HD water", WATER); -+ let sky_module = module(device, "HD sky", hd_sky::SKY_PASS); -+ let sky_pipeline = fullscreen( -+ device, -+ "HD sky", -+ &sky_layout, -+ &sky_module, -+ "fs_sky_lut", -+ hd_sky::SKY_LUT_FORMAT, -+ None, -+ ); - let water_pipeline = fullscreen( - device, - "HD water", -@@ -1691,6 +1851,21 @@ impl Hd { - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); -+ let sky_lut = target( -+ device, -+ "HD sky", -+ hd_sky::SKY_LUT_SIZE, -+ hd_sky::SKY_LUT_FORMAT, -+ ) -+ .view; -+ let sky_group = device.create_bind_group(&wgpu::BindGroupDescriptor { -+ label: Some("HD sky"), -+ layout: &sky_layout, -+ entries: &[wgpu::BindGroupEntry { -+ binding: 0, -+ resource: frame.as_entire_binding(), -+ }], -+ }); - let lights = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("HD lights"), - size: (MAX_LIGHTS * std::mem::size_of::()) as u64, -@@ -1788,6 +1963,11 @@ impl Hd { - water_active: false, - water_geometry: false, - water_composited: false, -+ sky_lut, -+ sky_pipeline, -+ sky_group, -+ sky_drawn_for: 2.0, -+ sky_stale: true, - started: std::time::Instant::now(), - taa: target(device, "HD TAA", [1, 1], HDR).view, - history: [ -@@ -1961,6 +2141,11 @@ impl Hd { - if !self.taa_active || scene.camera_delta.length() > 64.0 { - self.history_ready = false; - } -+ let sun_height = scene.sun.normalize_or(glam::Vec3::Y).y; -+ self.sky_stale = (sun_height - self.sky_drawn_for).abs() > 0.001; -+ if self.sky_stale { -+ self.sky_drawn_for = sun_height; -+ } - let frame = HdFrame { - prev_view_proj: scene.prev_view_proj.to_cols_array_2d(), - taa: [ -@@ -1982,6 +2167,17 @@ impl Hd { - scene.sky_color[2], - if scene.sky { 1.0 } else { 0.0 }, - ], -+ sky_sun: { -+ let sun = scene.sun.normalize_or(glam::Vec3::Y); -+ [sun.x, sun.y, sun.z, if scene.sky { 1.0 } else { 0.0 }] -+ }, -+ sky_extra: [ -+ scene.sky_fog_end, -+ 0.0, -+ (scene.rain / 0.7).clamp(0.0, 1.0) * 0.85, -+ 0.0, -+ ], -+ sunrise: scene.sunrise, - taa_params: [ - if self.upscaling() { - UPSCALE_HISTORY -@@ -2123,6 +2319,23 @@ impl Hd { - pass.draw(0..3, 0..1); - } - -+ /// Marches the sky-view texture for this frame's sun (before the lighting and the water); skipped -+ /// while the sun's height has moved less than a thousandth since the last march. -+ pub fn sky(&mut self, encoder: &mut wgpu::CommandEncoder) { -+ if !self.sky_stale { -+ return; -+ } -+ Self::draw( -+ encoder, -+ "HD sky", -+ &self.sky_lut, -+ false, -+ &self.sky_pipeline, -+ &self.sky_group, -+ stamp(&mut self.timing, "hd sky"), -+ ); -+ } -+ - /// Lights the world (its colour and depth) into `lit`. - pub fn light( - &mut self, -@@ -2203,6 +2416,10 @@ impl Hd { - binding: 14, - resource: wgpu::BindingResource::TextureView(&self.water_depth), - }, -+ wgpu::BindGroupEntry { -+ binding: 15, -+ resource: wgpu::BindingResource::TextureView(&self.sky_lut), -+ }, - ], - }); - self.light_group = Some((key, group)); -@@ -2512,6 +2729,7 @@ impl Hd { - wgpu::BindingResource::Sampler(&self.shadow_sampler), - wgpu::BindingResource::TextureView(lightmap.0), - wgpu::BindingResource::Sampler(&self.sampler), -+ wgpu::BindingResource::TextureView(&self.sky_lut), - ], - ); - self.water_group = Some((key, group)); -@@ -2608,9 +2826,13 @@ pub fn light_of(scene: &HdScene) -> (glam::Vec3, f32, glam::Vec3) { - let clear = 1.0 - scene.rain * 0.85; - if sun.y > -0.02 { - let up = smooth(-0.02, 0.18, sun.y); -- let warm = smooth(0.0, 0.45, sun.y); -- let color = glam::Vec3::new(1.0, 0.62, 0.38).lerp(glam::Vec3::new(1.0, 0.95, 0.86), warm); -- (sun, 0.6 * up * clear, color) -+ // The atmosphere's transmittance along the sun's ray (orange when low, a clean warm white -+ // high), kept at full brightness and tempered, with a slight warm tint on top. -+ let t = hd_sky::sun_transmittance(sun.y); -+ let hue = t / t.max_element().max(1e-4); -+ let color = glam::Vec3::new(hue.x.powf(0.6), hue.y.powf(0.6), hue.z.powf(0.6)) -+ * glam::Vec3::new(1.0, 0.93, 0.78); -+ (sun, 0.7 * up * clear, color) - } else { - let moon = -sun; - let up = smooth(-0.02, 0.18, moon.y); -@@ -2661,8 +2883,9 @@ mod tests { - ("ao", AO), - ("ao blur", AO_BLUR), - ("water", WATER), -+ ("sky", hd_sky::SKY_PASS), - ] { -- let source = format!("{COMMON}{body}"); -+ let source = assemble(body); - let module = naga::front::wgsl::parse_str(&source).unwrap_or_else(|e| { - panic!("{name}: {}", e.emit_to_string(&source)); - }); -diff --git a/crates/horizon-client/src/gfx/hd_sky.rs b/crates/horizon-client/src/gfx/hd_sky.rs -new file mode 100644 -index 000000000..b5c231a7b ---- /dev/null -+++ b/crates/horizon-client/src/gfx/hd_sky.rs -@@ -0,0 +1,324 @@ -+//! AirDef fork: the HD renderer's atmosphere. A small sky-view texture (azimuth from the sun by -+//! elevation, plus a row of ambient samples) is marched each frame from single scattering of -+//! Rayleigh and Mie particles and ozone absorption with a cheap multiple-scattering term; the -+//! lighting and water shaders read it for the sky, the aerial-perspective haze, the water's -+//! reflection fallback and the colour of the sky's light. The sunlight's colour is the same -+//! atmosphere's transmittance at the sun's elevation (`sun_transmittance`). -+//! -+//! The coefficients, the density profiles (scale heights, the ozone layer), the Henyey-Greenstein -+//! Mie parameters and the Chapman-function transmittance come from Photon by sixthsurge (MIT -+//! licence, see THIRD_PARTY_PHOTON.md), shaders/include/sky/atmosphere.glsl at commit 26c2906. -+ -+/// The sky-view texture: 128 azimuths by 64 elevations, then 8 rows for the ambient samples -+/// (row 64: texel 0 the zenith, texel 1 the horizon's average). -+pub const SKY_LUT_SIZE: [u32; 2] = [128, 72]; -+pub const SKY_LUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; -+ -+/// The atmosphere's constants and the pass that marches the sky-view texture (after `COMMON`). -+pub const SKY_PASS: &str = r#" -+@group(0) @binding(0) var frame: HdFrame; -+ -+// Photon's atmosphere (sixthsurge, MIT): metres, Earth's size; the camera stands on the surface. -+const PI: f32 = 3.14159265; -+const PLANET_R: f32 = 6371000.0; -+const ATMOSPHERE_TOP: f32 = 6481000.0; -+const GROUND_R: f32 = 6370000.0; -+const SCALE_HEIGHTS: vec2 = vec2(8400.0, 1250.0); -+const RAYLEIGH: vec3 = vec3(8.059375432e-06, 1.671209429e-05, 4.080133294e-05); -+const MIE: vec3 = vec3(1.666442358e-06, 1.812685127e-06, 1.958927896e-06); -+const OZONE: vec3 = vec3(8.304280072e-07, 1.314911970e-06, 5.440679729e-08); -+const MIE_ALBEDO: f32 = 0.9; -+const MIE_G: f32 = 0.77; -+// Sunlight in space (Photon's AM0 colour) and how bright the sun and the moon shine here. -+const SUN_I: vec3 = vec3(1.051, 0.985, 0.940) * 24.0; -+const MOON_I: vec3 = vec3(0.62, 0.78, 1.0) * 0.07; -+// The share of the light scattered a second time, spread evenly (the haze's lift). -+const MULTI_SCATTER: f32 = 0.6; -+const STEPS: i32 = 32; -+ -+// Photon's density: Rayleigh, Mie (exponential) and the ozone layer, at an altitude in metres. -+fn density_at(altitude: f32) -> vec3 { -+ let rm = exp(-max(altitude, 0.0) / SCALE_HEIGHTS); -+ let km = altitude * 1e-3; -+ let o1 = 12.5 * exp(-km / 8.0); -+ let o2 = 30.0 * exp((18.0 - km) * (km - 18.0) / 80.0); -+ let o3 = 75.0 * exp((23.5 - km) * (km - 23.5) / 50.0); -+ let o4 = 50.0 * exp((30.0 - km) * (km - 30.0) / 150.0); -+ return vec3(rm, 7.428e-3 * (o1 + o2 + o3 + o4)); -+} -+ -+// Chapman function approximation (thetenthplanet.de/archives/4519, as Photon uses it). -+fn chapman(x: f32, cos_theta: f32) -> f32 { -+ let c = sqrt(1.5707963 * x); -+ if cos_theta >= 0.0 { -+ return c / ((c - 1.0) * cos_theta + 1.0); -+ } -+ let sin_theta = sqrt(clamp(1.0 - cos_theta * cos_theta, 0.0, 1.0)); -+ return c / ((c - 1.0) * cos_theta - 1.0) + 2.0 * c * exp(min(x - x * sin_theta, 80.0)) * sqrt(sin_theta); -+} -+ -+// The light reaching a point r from the planet's centre (altitude h, whose sky direction makes -+// cos mu with the up there) from the sun or the moon; 0 when the planet is in the way. -+fn light_transmittance(mu: f32, r: f32, h: f32) -> vec3 { -+ if mu < 0.0 && r * r * (1.0 - mu * mu) < PLANET_R * PLANET_R { return vec3(0.0); } -+ let dens = exp(-max(h, 0.0) / SCALE_HEIGHTS); -+ var air = SCALE_HEIGHTS * dens; -+ air.x *= chapman(r / SCALE_HEIGHTS.x, mu); -+ air.y *= chapman(r / SCALE_HEIGHTS.y, mu); -+ return clamp(exp(-(RAYLEIGH * air.x + MIE * air.y + OZONE * air.x)), vec3(0.0), vec3(1.0)); -+} -+ -+fn rayleigh_phase(nu: f32) -> f32 { -+ return 3.0 / (16.0 * PI) * (1.0 + nu * nu); -+} -+ -+fn mie_phase(nu: f32) -> f32 { -+ let g = MIE_G; -+ return (1.0 - g * g) / (4.0 * PI * pow(1.0 + g * g - 2.0 * g * nu, 1.5)); -+} -+ -+// The radiance the camera sees along `view` (y up) with the sun and the moon in the directions -+// given: single scattering plus a flat multiple-scattering term. -+fn march(view: vec3, sun: vec3, moon: vec3) -> vec3 { -+ let b = PLANET_R * view.y; -+ let t_max = -b + sqrt(b * b + ATMOSPHERE_TOP * ATMOSPHERE_TOP - PLANET_R * PLANET_R); -+ let nu_sun = dot(view, sun); -+ let nu_moon = dot(view, moon); -+ let phase_r_sun = rayleigh_phase(nu_sun); -+ let phase_m_sun = mie_phase(nu_sun); -+ let phase_r_moon = rayleigh_phase(nu_moon); -+ let phase_m_moon = mie_phase(nu_moon); -+ var od = vec3(0.0); -+ var sum = vec3(0.0); -+ var t0 = 0.0; -+ for (var i = 0; i < STEPS; i++) { -+ let u = (f32(i) + 1.0) / f32(STEPS); -+ let t1 = t_max * u * u; -+ let dt = t1 - t0; -+ let t = 0.5 * (t0 + t1); -+ t0 = t1; -+ let r2 = PLANET_R * PLANET_R + 2.0 * PLANET_R * t * view.y + t * t; -+ let r = sqrt(r2); -+ // The altitude without the cancellation of r - planet radius. -+ let altitude = (2.0 * PLANET_R * t * view.y + t * t) / (r + PLANET_R); -+ let dens = density_at(altitude); -+ let extinction = RAYLEIGH * dens.x + MIE * dens.y + OZONE * dens.z; -+ let view_t = exp(-(od + 0.5 * extinction * dt)); -+ od += extinction * dt; -+ let mu_sun = (PLANET_R * sun.y + t * nu_sun) / r; -+ let mu_moon = (PLANET_R * moon.y + t * nu_moon) / r; -+ let sun_t = light_transmittance(mu_sun, r, altitude); -+ let moon_t = light_transmittance(mu_moon, r, altitude); -+ let scatter_r = RAYLEIGH * dens.x; -+ let scatter_m = MIE * MIE_ALBEDO * dens.y; -+ let single = (scatter_r * phase_r_sun + scatter_m * phase_m_sun) * sun_t * SUN_I -+ + (scatter_r * phase_r_moon + scatter_m * phase_m_moon) * moon_t * MOON_I; -+ let multi = (scatter_r + scatter_m) * (1.0 / (4.0 * PI)) * MULTI_SCATTER -+ * (sun_t * SUN_I + moon_t * MOON_I) * 0.5; -+ sum += view_t * (single + multi) * dt; -+ } -+ return sum; -+} -+ -+@fragment -+fn fs_sky_lut(in: FullOut) -> @location(0) vec4 { -+ let px = vec2(in.position.xy); -+ // The texture's frame: the sun in the xy plane, the moon opposite. -+ let sin_e = clamp(frame.sky_sun.y, -1.0, 1.0); -+ let sun = vec3(sqrt(1.0 - sin_e * sin_e), sin_e, 0.0); -+ let moon = -sun; -+ // A faint night glow, so the night sky is not black. -+ let night = vec3(0.0006, 0.0011, 0.0026); -+ if px.y < 64 { -+ let phi = f32(px.x) / 127.0 * PI; -+ let v = f32(px.y) / 63.0; -+ let l = v * v * PI * 0.5; -+ let dir = vec3(cos(l) * cos(phi), sin(l), cos(l) * sin(phi)); -+ return vec4(march(dir, sun, moon) + night, 1.0); -+ } -+ if px.y == 64 && px.x == 0 { -+ return vec4(march(vec3(0.0, 1.0, 0.0), sun, moon) + night, 1.0); -+ } -+ if px.y == 64 && px.x == 1 { -+ var sum = vec3(0.0); -+ for (var k = 0; k < 6; k++) { -+ let phi = (f32(k) + 0.5) / 6.0 * PI; -+ let l = 0.3; -+ sum += march(vec3(cos(l) * cos(phi), sin(l), cos(l) * sin(phi)), sun, moon); -+ } -+ return vec4(sum / 6.0 + night, 1.0); -+ } -+ return vec4(0.0); -+} -+"#; -+ -+/// Reading the sky-view texture, in the lighting and the water shaders (`SKYBIND` is replaced by -+/// the binding of the texture; `frame` and the `linear` sampler come before it). -+pub const SKY_FUNCS: &str = r#" -+@group(0) @binding(SKYBIND) var sky_lut: texture_2d; -+const SKY_PI: f32 = 3.14159265; -+// How bright the sky is shown (the exposure of the physical radiance) and its gamma. -+const SKY_EXPOSURE: f32 = 0.85; -+ -+// Where the direction `dir` (world, y up) lies in the sky-view texture. -+fn sky_uv(dir: vec3) -> vec2 { -+ let s = frame.sky_sun.xyz; -+ let l = asin(clamp(dir.y, 0.0, 1.0)); -+ var cphi = 1.0; -+ let dl = length(dir.xz); -+ let sl = length(s.xz); -+ if dl > 1e-4 && sl > 1e-4 { cphi = dot(dir.xz / dl, s.xz / sl); } -+ let u = acos(clamp(cphi, -1.0, 1.0)) / SKY_PI; -+ let v = sqrt(l / (SKY_PI * 0.5)); -+ return vec2((u * 127.0 + 0.5) / 128.0, (v * 63.0 + 0.5) / 72.0); -+} -+ -+// The physical sky's colour as it is shown: a soft shoulder, then the display's gamma. -+fn sky_show(radiance: vec3) -> vec3 { -+ // Compressed by the brightest channel, so the hue stays as the atmosphere made it. -+ let peak = max(max(radiance.r, radiance.g), max(radiance.b, 1e-6)); -+ let c = pow(radiance * ((1.0 - exp(-peak * SKY_EXPOSURE)) / peak), vec3(1.0 / 1.9)); -+ // A little more colour (Photon boosts the atmosphere's saturation too). -+ // The low sun gets more, for orange and pink in place of pastel. -+ let boost = 1.2 + 0.45 * (1.0 - smoothstep(0.0, 0.35, frame.sky_sun.y)); -+ return max(mix(vec3(dot(c, vec3(0.2126, 0.7152, 0.0722))), c, boost), vec3(0.0)); -+} -+ -+// The vanilla sky's own gradient (the overcast and the rain keep it). -+fn vanilla_gradient(dir: vec3) -> vec3 { -+ return mix(frame.fog_color.rgb, frame.sky_color.rgb, smoothstep(0.0, 0.5, max(dir.y, 0.0))); -+} -+ -+// The sky along `dir`: the atmosphere, greyed towards vanilla's colours in rain. -+fn atmosphere_sky(dir: vec3) -> vec3 { -+ let lin = textureSampleLevel(sky_lut, linear, sky_uv(dir), 0.0).rgb; -+ var c = sky_show(lin); -+ let overcast = frame.sky_extra.z; -+ if overcast > 0.0 { c = mix(c, vanilla_gradient(dir), overcast); } -+ return c; -+} -+ -+// The colour distant things fade to: the sky just above the horizon in their direction (so the -+// haze leans towards the sun), or vanilla's fog colour where the environment sets its own fog. -+fn haze_colour(dir: vec3) -> vec3 { -+ if frame.sky_sun.w < 0.5 || frame.fog.x < 1.0e6 { return frame.fog_color.rgb; } -+ let up = max(dir.y, 0.0) * 0.5 + 0.035; -+ return atmosphere_sky(normalize(vec3(dir.x, 0.0, dir.z) + vec3(0.0, up, 0.0))); -+} -+ -+// The sky's light for a surface of normal n: the horizon's colour from the sides, the zenith's -+// from above, as a hue (luminance 1) to tint vanilla's sky light with. -+fn sky_light_tint(n: vec3) -> vec3 { -+ let zenith = sky_show(textureLoad(sky_lut, vec2(0, 64), 0).rgb); -+ let horizon = sky_show(textureLoad(sky_lut, vec2(1, 64), 0).rgb); -+ let up = clamp(n.y * 0.5 + 0.5, 0.0, 1.0); -+ let c = mix(horizon, zenith, smoothstep(0.35, 1.0, up)); -+ let luma = dot(c, vec3(0.2126, 0.7152, 0.0722)); -+ return c / max(luma, 0.003); -+} -+ -+// vanilla's sky (terrain.wgsl: fs_sky), to tell what the world drew on the sky pixel (the sun and -+// moon sprites, the stars, the clouds) from its plain gradient. -+fn vanilla_disc_distance(d: vec3, y: f32) -> vec2 { -+ if d.y * y <= 0.0 { return vec2(-1.0); } -+ let p = d * (y / d.y); -+ let h = length(p.xz); -+ let phi = atan2(p.z, p.x); -+ let sector = SKY_PI / 4.0; -+ let delta = phi - (floor(phi / sector) * sector + sector * 0.5); -+ let edge = 512.0 * cos(sector * 0.5) / cos(delta); -+ if h > edge { return vec2(-1.0); } -+ let s = h / edge; -+ let ay = abs(y); -+ let rim = sqrt(512.0 * 512.0 + y * y); -+ return vec2(ay + s * (rim - ay), ay + s * (512.0 - ay)); -+} -+fn vanilla_linear_fog(d: f32, start: f32, end: f32) -> f32 { -+ if d <= start { return 0.0; } -+ if d >= end { return 1.0; } -+ return (d - start) / (end - start); -+} -+fn vanilla_rotate_x90(v: vec3) -> vec3 { return vec3(v.x, -v.z, v.y); } -+fn vanilla_sunrise_weight(d: vec3) -> f32 { -+ let alpha = frame.sunrise.a; -+ if alpha <= 0.001 { return 0.0; } -+ let flip = select(0.0, 180.0, frame.sky_sun.x > 0.0); -+ let theta = radians(flip + 90.0); -+ let c = cos(theta); -+ let s = sin(theta); -+ var best = 0.0; -+ let apex = vanilla_rotate_x90(vec3(-100.0 * s, 100.0 * c, 0.0)); -+ for (var i = 0; i < 16; i++) { -+ let a0 = f32(i) * 2.0 * SKY_PI / 16.0; -+ let a1 = f32(i + 1) * 2.0 * SKY_PI / 16.0; -+ let l0 = vec3(sin(a0) * 120.0, cos(a0) * 120.0, -cos(a0) * 40.0 * alpha); -+ let l1 = vec3(sin(a1) * 120.0, cos(a1) * 120.0, -cos(a1) * 40.0 * alpha); -+ let r0 = vanilla_rotate_x90(vec3(l0.x * c - l0.y * s, l0.x * s + l0.y * c, l0.z)); -+ let r1 = vanilla_rotate_x90(vec3(l1.x * c - l1.y * s, l1.x * s + l1.y * c, l1.z)); -+ let e1 = r0 - apex; -+ let e2 = r1 - apex; -+ let pv = cross(d, e2); -+ let det = dot(e1, pv); -+ if abs(det) < 1e-6 { continue; } -+ let tv = -apex; -+ let u = dot(tv, pv) / det; -+ let qv = cross(tv, e1); -+ let v = dot(d, qv) / det; -+ let t = dot(e2, qv) / det; -+ if u >= 0.0 && v >= 0.0 && u + v <= 1.0 && t > 0.0 { -+ best = max(best, 1.0 - u - v); -+ } -+ } -+ return best; -+} -+fn vanilla_sky(d: vec3) -> vec3 { -+ var color = frame.fog_color.rgb; -+ let dist = vanilla_disc_distance(d, 16.0); -+ if dist.x >= 0.0 { -+ let end = frame.sky_extra.x; -+ let f = max(vanilla_linear_fog(dist.x, 0.0, end), vanilla_linear_fog(dist.y, end, end)); -+ color = mix(frame.sky_color.rgb, frame.fog_color.rgb, f * frame.fog_color.a); -+ } -+ let w = vanilla_sunrise_weight(d); -+ if w > 0.0 { -+ color = mix(color, frame.sunrise.rgb, w * frame.sunrise.a); -+ } -+ return color; -+} -+"#; -+ -+/// The sun's light at the ground: the atmosphere's transmittance along the sun's direction -+/// (`sin_elevation`, the y of the unit vector towards it), linear, as a fraction of the light -+/// outside the air. Same Chapman model as the sky pass. -+pub fn sun_transmittance(sin_elevation: f32) -> glam::Vec3 { -+ const PLANET: f32 = 6_371_000.0; -+ const SCALE: [f32; 2] = [8400.0, 1250.0]; -+ const RAYLEIGH: glam::Vec3 = glam::Vec3::new(8.059_375e-6, 1.671_209_4e-5, 4.080_133_4e-5); -+ const MIE: glam::Vec3 = glam::Vec3::new(1.666_442_4e-6, 1.812_685e-6, 1.958_927_9e-6); -+ const OZONE: glam::Vec3 = glam::Vec3::new(8.304_28e-7, 1.314_912e-6, 5.440_679_7e-8); -+ // A sun below the horizon keeps the horizon's colour (its strength is faded elsewhere). -+ let mu = sin_elevation.clamp(0.0, 1.0); -+ let chapman = |x: f32, c: f32| { -+ let k = (std::f32::consts::FRAC_PI_2 * x).sqrt(); -+ k / ((k - 1.0) * c + 1.0) -+ }; -+ let air = [ -+ SCALE[0] * chapman(PLANET / SCALE[0], mu), -+ SCALE[1] * chapman(PLANET / SCALE[1], mu), -+ ]; -+ let tau = RAYLEIGH * air[0] + MIE * air[1] + OZONE * air[0]; -+ glam::Vec3::new((-tau.x).exp(), (-tau.y).exp(), (-tau.z).exp()) -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ #[test] -+ fn the_low_sun_is_redder_than_the_high_one() { -+ let noon = sun_transmittance(1.0); -+ let low = sun_transmittance(0.1); -+ assert!(noon.x > 0.85 && noon.z > 0.6, "{noon:?}"); -+ assert!(low.z / low.x < noon.z / noon.x * 0.6, "{low:?} vs {noon:?}"); -+ } -+} -diff --git a/crates/horizon-client/src/terrain.wgsl b/crates/horizon-client/src/terrain.wgsl -index e892d5a0f..0a2a6cdba 100644 ---- a/crates/horizon-client/src/terrain.wgsl -+++ b/crates/horizon-client/src/terrain.wgsl -@@ -197,7 +197,7 @@ struct WaterOut { - if layer != WATER_STILL && layer != WATER_FLOW { discard; } - // The biome's colour at the texture's mean brightness, not the texture: the HD water draws - // its own surface (waves, reflections), and vanilla's ripples on top would look painted on. -- let color = vec3(0.62) * in.tint.rgb; -+ let color = vec3(0.85) * in.tint.rgb; - var out: WaterOut; - out.tint = vec4(color, clamp(in.lm.y, 0.0, 1.0)); - out.normal = vec4(face_normal(in.pos) * 0.5 + 0.5, clamp(in.lm.x, 0.0, 1.0)); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0022-HD-water-a-darker-blue-a-little-clearer-ripples-a-li.patch b/horizon-patches/0022-HD-water-a-darker-blue-a-little-clearer-ripples-a-li.patch deleted file mode 100644 index 2481ba2..0000000 --- a/horizon-patches/0022-HD-water-a-darker-blue-a-little-clearer-ripples-a-li.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 6277e051a89c2537570982f6ef275887193c05cf Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 19:02:16 +0300 -Subject: [PATCH 22/42] HD water: a darker blue, a little clearer, ripples a - little higher - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx/hd.rs | 9 +++++---- - 1 file changed, 5 insertions(+), 4 deletions(-) - -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index 031e9f2de..f078d423c 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -558,7 +558,8 @@ fn wave_gradient(xz: vec2, footprint: f32) -> vec2 { - let phase = dot(k, xz) - omega * frame.water.y + w.w; - grad += (k / len) * (w.z * fade * cos(phase)); - } -- return grad; -+ // A little higher than the table's slopes. -+ return grad * 1.25; - } - - fn cascade_at(p: vec3, c: i32) -> vec3 { -@@ -713,10 +714,10 @@ fn fs_water(in: FullOut) -> @location(0) vec4 { - // A deep, dark water: the biome's colour darkened and a little greyed, so it reads as - // depth rather than as bright paint. - let biome = clamp(tint.rgb, vec3(0.03), vec3(1.0)); -- let colour = mix(vec3(dot(biome, vec3(0.333))), biome, 0.85) * vec3(0.92, 1.0, 1.0); -+ let colour = mix(vec3(dot(biome, vec3(0.333))), biome, 0.85) * vec3(0.78, 0.86, 0.95); - // The colours the water takes up fastest are the ones it has the least of. -- let absorb = exp(-(vec3(1.0) - colour) * 0.8 * (thickness + 0.3)); -- let refracted = behind * absorb + colour * volume_light * mix(0.85, 0.4, frame.sun_color.a) * (vec3(1.0) - absorb); -+ let absorb = exp(-(vec3(1.0) - colour) * 0.6 * (thickness + 0.3)); -+ let refracted = behind * absorb + colour * volume_light * mix(0.65, 0.3, frame.sun_color.a) * (vec3(1.0) - absorb); - - // Reflection: the screen where the ray stays on it, the sky where it leaves it or misses. - var r = reflect(-v, n); --- -2.54.0 (Apple Git-157) - diff --git a/horizon-patches/0023-HD-Photon-style-sunsets-Rec.-2020-atmosphere-sun-tin.patch b/horizon-patches/0023-HD-Photon-style-sunsets-Rec.-2020-atmosphere-sun-tin.patch deleted file mode 100644 index 944ab63..0000000 --- a/horizon-patches/0023-HD-Photon-style-sunsets-Rec.-2020-atmosphere-sun-tin.patch +++ /dev/null @@ -1,1914 +0,0 @@ -From 982265d21bb9d74ec0040db03feb5a55762286ad Mon Sep 17 00:00:00 2001 -From: chexit -Date: Thu, 29 Oct 2026 21:41:48 +0300 -Subject: [PATCH 23/42] HD: Photon-style sunsets (Rec. 2020 atmosphere, sun - tint, blue hour; MIT), volumetric clouds with ground shadows instead of - vanilla's in HD, volumetric fog with sun shafts and morning mist - (HORIZON_HD_CLOUDS=0, HORIZON_HD_VOLFOG=0) - -Co-Authored-By: Claude Opus 5.5 ---- - crates/horizon-client/src/gfx.rs | 24 +- - .../src/gfx/THIRD_PARTY_PHOTON.md | 22 +- - crates/horizon-client/src/gfx/hd.rs | 345 ++++++- - crates/horizon-client/src/gfx/hd_noise.rs | 163 ++++ - crates/horizon-client/src/gfx/hd_sky.rs | 90 +- - crates/horizon-client/src/gfx/hd_volumes.rs | 857 ++++++++++++++++++ - 6 files changed, 1448 insertions(+), 53 deletions(-) - create mode 100644 crates/horizon-client/src/gfx/hd_noise.rs - create mode 100644 crates/horizon-client/src/gfx/hd_volumes.rs - -diff --git a/crates/horizon-client/src/gfx.rs b/crates/horizon-client/src/gfx.rs -index d01131433..072090e33 100644 ---- a/crates/horizon-client/src/gfx.rs -+++ b/crates/horizon-client/src/gfx.rs -@@ -72,7 +72,9 @@ mod cull; - mod end_sky; - mod flat; - mod hd; -+mod hd_noise; - mod hd_sky; -+mod hd_volumes; - mod hdr; - pub mod lightning; - mod lod; -@@ -4278,6 +4280,17 @@ impl Renderer { - water: !frame.underwater, - sunrise: frame.sky.sunrise_color, - sky_fog_end: frame.fog.map_or(frame.distance.min(512.0), |f| f.sky_end), -+ // AirDef fork: volumetric clouds stand where vanilla's would (option on, Overworld). -+ clouds: frame.clouds.is_some() && hd::clouds_enabled(), -+ camera_world: [ -+ camera.x.rem_euclid(65536.0) as f32, -+ camera.z.rem_euclid(65536.0) as f32, -+ camera.y as f32, -+ ], -+ seconds: frame -+ .clouds -+ .as_ref() -+ .map_or(0.0, |c| ((c.time as f64 + c.partial_tick) / 20.0) as f32), - }; - let (shadow_dir, shadow_strength, _) = hd::light_of(&hd_scene); - let mut hd_cascades = hd::cascades(camera, shadow_dir); -@@ -5071,6 +5084,7 @@ impl Renderer { - } - } - let pack_reads_world = pack_post || native_post || mod_post || hd_on; -+ let hd_volumetric_clouds = hd_on && hd::clouds_enabled(); - // The pack's program reads the vertices this frame's draws bind. - let pack_draws_terrain = pack_terrain && flat_frame == self.pack_terrain_flat; - // The shadow camera's lists, drawn by the pack's shadow programs. -@@ -5128,9 +5142,9 @@ impl Renderer { - let format = self.config.format.remove_srgb_suffix(); - let device = &self.device; - let globals_size = std::mem::size_of::() as u64; -- let hd = self -- .hd -- .get_or_insert_with(|| hd::Hd::new(device, size, format, globals_size)); -+ let hd = self.hd.get_or_insert_with(|| { -+ hd::Hd::new(device, &self.queue, size, format, globals_size) -+ }); - hd.resize(device, size, [self.config.width, self.config.height]); - // Translucent terrain on screen: the water pass has something to draw. - hd.water_geometry = match self.flat.as_ref().filter(|_| flat_frame) { -@@ -5691,7 +5705,8 @@ impl Renderer { - 0..1, - ); - } -- if cloud_quads > 0 && !pack_draws_clouds { -+ // AirDef fork: HD draws the clouds volumetrically (in the lighting) instead. -+ if cloud_quads > 0 && !pack_draws_clouds && !hd_volumetric_clouds { - pass.set_pipeline(&self.cloud_pipeline); - pass.set_bind_group(0, &self.entity_group, &[]); - pass.set_bind_group(1, &self.cloud_group, &[]); -@@ -5821,6 +5836,7 @@ impl Renderer { - // AirDef fork: lit by the mods' lights into the HD target, the mod's post pass on - // that, then bloom and the tone curve onto the screen. - hd.sky(&mut encoder); -+ hd.volumes(&self.device, &mut encoder, depth); - hd.light( - &self.device, - &mut encoder, -diff --git a/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md b/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md -index 03d945001..fd9f4d029 100644 ---- a/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md -+++ b/crates/horizon-client/src/gfx/THIRD_PARTY_PHOTON.md -@@ -1,14 +1,26 @@ - # Third-party code: Photon - - Parts of the HD renderer's atmosphere (`hd_sky.rs`) are ported from, or follow closely, the --Photon shader pack by sixthsurge, `shaders/include/sky/atmosphere.glsl` (and the idea of the water -+Photon shader pack by sixthsurge, `shaders/include/sky/atmosphere.glsl`, `shaders/include/lighting/colors/light_color.glsl`, `shaders/include/utility/phase_functions.glsl`, `shaders/include/sky/clouds/{common,cumulus}.glsl`, `shaders/include/lighting/cloud_shadows.glsl`, `shaders/include/fog/air_fog_vl.glsl` (and the idea of the water - waves in `shaders/include/misc/water_normal.glsl`) at commit - 26c2906ee02cc484359b835fba6993d443d45966, https://github.com/sixthsurge/photon - --What is taken: the air's scattering and absorption coefficients (Rayleigh, Mie, ozone), the scale --heights, the ozone density profile, the Mie albedo and Henyey-Greenstein anisotropy, the sunlight --colour outside the atmosphere, and the Chapman-function approximation of the sun's transmittance. --The sky-view texture, its march, the sun's colour and everything else are our own. -+What is taken from `atmosphere.glsl`: the air's scattering and absorption coefficients (Rayleigh, Mie, -+ozone) moved to Rec. 2020 as Photon does (the march runs in that space, the result goes back to Rec. 709), -+the scale heights, the ozone density profile, the Mie albedo, the Klein-Nishina Mie phase and the -+depolarised Rayleigh phase (`phase_functions.glsl`), the sunlight colour outside the atmosphere, and the -+Chapman-function approximation of the sun's transmittance. From `light_color.glsl`: `get_sun_tint` and -+the blue-hour / sunset exposure lift (`sun_light_tint`, `sun_tint`), and `pulse`. The sky-view texture and -+its march are our own. -+ -+The volumetric clouds and fog (`hd_volumes.rs`, `hd_noise.rs` for the CPU-made noise textures) port -+from `include/sky/clouds/common.glsl` and `cumulus.glsl` (the density shaping with the egg shape and -+edge sharpening, Worley erosion, the single and multiple scattering phase functions, the powder effect, -+the eight-octave multiple-scattering loop, the light march with doubling steps, the transmittance remap, -+the aerial perspective), `include/lighting/cloud_shadows.glsl` (the cloud shadow map, here a lookup into a -+small map) and `include/fog/air_fog_vl.glsl` (the phase mix of two Henyey-Greenstein lobes, the evening -+glow). The 3D Worley and 2D noise textures are generated on our side; the ray march, the passes, the -+reprojection and the integration are our own. - - Photon is distributed under the MIT License: - -diff --git a/crates/horizon-client/src/gfx/hd.rs b/crates/horizon-client/src/gfx/hd.rs -index f078d423c..3b9daabf4 100644 ---- a/crates/horizon-client/src/gfx/hd.rs -+++ b/crates/horizon-client/src/gfx/hd.rs -@@ -6,6 +6,8 @@ - //! bright parts bloom and a tone curve brings it to the screen — vanilla's - //! range (up to about 0.8) left as it is, only what is brighter rolled off. - use super::hd_sky; -+pub use super::hd_volumes::clouds_enabled; -+use super::hd_volumes::{self, HdVolumes}; - use super::pack_timing::PassTiming; - use glam::Mat4; - use std::sync::atomic::{AtomicU64, Ordering}; -@@ -108,6 +110,18 @@ struct HdFrame { - sky_extra: [f32; 4], - /// Vanilla's sunrise colour fan (rgb, alpha), taken out of the sky before the atmosphere is put in. - sunrise: [f32; 4], -+ /// x: the cloud slab's base height relative to the camera; y: its thickness; z: 1 when the -+ /// volumetric clouds are drawn; w: seconds (the wind). -+ clouds: [f32; 4], -+ /// xy: the camera's x and z in the world modulo 65536; z: extra coverage (rain); w: 1 when the -+ /// clouds' history is usable. -+ cloud_cam: [f32; 4], -+ /// xyz: the direction the clouds are lit from (the sun, or the moon at night). -+ cloud_dir: [f32; 4], -+ /// rgb: the colour of that light at the clouds; w: its strength. -+ cloud_sun: [f32; 4], -+ /// x: 1 when the volumetric fog is drawn; y: the camera's height in the world; z: the morning mist. -+ vfog: [f32; 4], - } - - /// What the world around looks like this frame (from the renderer's globals). -@@ -141,6 +155,11 @@ pub struct HdScene { - /// Vanilla's sunrise fan colour and its sky fog end (to subtract vanilla's sky from the world). - pub sunrise: [f32; 4], - pub sky_fog_end: f32, -+ /// The volumetric clouds are drawn (vanilla's option is on, in the Overworld). -+ pub clouds: bool, -+ /// The camera's world position, x and z modulo 65536 (precision), and y; seconds of game time. -+ pub camera_world: [f32; 3], -+ pub seconds: f32, - } - - /// The cascades' half sizes (blocks) and shadow map size. -@@ -168,6 +187,11 @@ struct HdFrame { - sky_sun: vec4, - sky_extra: vec4, - sunrise: vec4, -+ clouds: vec4, -+ cloud_cam: vec4, -+ cloud_dir: vec4, -+ cloud_sun: vec4, -+ vfog: vec4, - } - struct FullOut { - @builtin(position) position: vec4, -@@ -209,6 +233,13 @@ struct Light { - // The water pass's depth: the nearest water surface (0 where there is none). - @group(0) @binding(14) var water_depth: texture_depth_2d; - //SKY15 -+// The volumetric fog (half resolution: light in rgb, transmittance in a), the distance it was marched -+// to, the volumetric clouds (half resolution: light, transmittance) and the clouds' shadow map. -+@group(0) @binding(16) var fog_tex: texture_2d; -+@group(0) @binding(17) var fog_dist_tex: texture_2d; -+@group(0) @binding(18) var cloud_tex: texture_2d; -+@group(0) @binding(19) var cloud_shadow_tex: texture_2d; -+const CLOUD_SHADOW_R: f32 = 1200.0; - // The pixel being lit, for the shadow filter's noise. - var frag_px: vec2; - -@@ -327,6 +358,51 @@ fn linear_fog(d: f32, start: f32, end: f32) -> f32 { - return (d - start) / (end - start); - } - -+// The share of the sunlight the clouds let through at a camera-relative point: the shadow map -+// holds, for the sun's way down through the slab from a point of its base, the optical depth -+// (Photon's cloud shadow map, as a lookup rather than a pass). -+fn cloud_shadow(p: vec3) -> f32 { -+ if frame.clouds.z < 0.5 { return 1.0; } -+ let l = frame.cloud_dir.xyz; -+ if l.y < 0.1 { return 1.0; } -+ let t = (frame.clouds.x - p.y) / l.y; -+ if t <= 0.0 { return 1.0; } -+ let q = p.xz + l.xz * t; -+ let uv = q / (2.0 * CLOUD_SHADOW_R) + vec2(0.5); -+ if any(uv < vec2(0.0)) || any(uv > vec2(1.0)) { return 1.0; } -+ let od = textureSampleLevel(cloud_shadow_tex, linear, uv, 0.0).r; -+ // Fade out as the sun gets low (the shadow then falls far from the cloud). -+ let fade = smoothstep(0.1, 0.25, l.y); -+ return mix(1.0, mix(1.0, exp(-od * 0.9), 0.9), fade); -+} -+ -+// The volumetric fog at a full-resolution pixel `dist` away: the four nearest half-resolution -+// texels weighted by closeness and by how near their march's length is (so it does not bleed over edges). -+fn fog_at(px: vec2, dist: f32) -> vec4 { -+ let size = vec2(textureDimensions(fog_tex)); -+ let f = vec2(px) * 0.5 - vec2(0.25); -+ let base = vec2(floor(f)); -+ let t = f - floor(f); -+ var sum = vec4(0.0); -+ var total = 0.0; -+ for (var j = 0; j < 2; j++) { -+ for (var i = 0; i < 2; i++) { -+ let q = clamp(base + vec2(i, j), vec2(0), size - 1); -+ let s = textureLoad(fog_tex, q, 0); -+ let sd = textureLoad(fog_dist_tex, q, 0).x; -+ let bw = select(1.0 - t.x, t.x, i == 1) * select(1.0 - t.y, t.y, j == 1); -+ let dz = (sd - dist) / (0.06 * dist + 0.4); -+ let w = bw / (1.0 + dz * dz * dz * dz); -+ sum += s * w; -+ total += w; -+ } -+ } -+ if total < 0.0001 { -+ return textureLoad(fog_tex, clamp(px / 2, vec2(0), size - 1), 0); -+ } -+ return sum / total; -+} -+ - @fragment - fn fs_light(in: FullOut) -> @location(0) vec4 { - let px = vec2(in.position.xy); -@@ -373,10 +449,15 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let angle = acos(clamp(toward, -1.0, 1.0)); - sky += frame.sun_color.rgb * shine * (pow(glow, 24.0) * 0.07 + pow(glow, 400.0) * 0.4 + 6.0 * (1.0 - smoothstep(0.016, 0.022, angle))); - c = sky + kept; -+ // The volumetric clouds over the sky and the sun. -+ if frame.clouds.z > 0.5 { -+ let cl = textureSampleLevel(cloud_tex, linear, in.uv, 0.0); -+ c = c * cl.a + cl.rgb; -+ } - } - // Clouds (vanilla's, drawn into the world with depth but not into the G-buffer): lit by the low - // sun's colour at dusk and dawn instead of vanilla's flat white. -- if atmosphere && !water && d > 0.0 && g <= 0.0 && p.y > 8.0 && dot(p, p) > 2304.0 { -+ if atmosphere && frame.clouds.z < 0.5 && !water && d > 0.0 && g <= 0.0 && p.y > 8.0 && dot(p, p) > 2304.0 { - let low = 1.0 - smoothstep(0.05, 0.5, frame.sky_sun.y); - c = scene * mix(vec3(1.0), mix(frame.sun_color.rgb, vec3(1.0), 0.45) * 1.1, low * 0.7 * (1.0 - frame.sky_extra.z)); - } -@@ -391,7 +472,7 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - let sky_reach = smoothstep(0.35, 0.95, lm.x); - var s = 1.0; - let ndl = dot(n, sun); -- if sun_on > 0.0 && ndl > 0.0 && sky_reach > 0.0 { s = shadow_taps(p, n, select(1, 0, water)); } else { s = 0.0; } -+ if sun_on > 0.0 && ndl > 0.0 && sky_reach > 0.0 { s = shadow_taps(p, n, select(1, 0, water)) * cloud_shadow(p); } else { s = 0.0; } - // Shadowed sky light (ambient) keeps vanilla's look, blocks' light stays as is; the sun adds - // direct light by the face's angle. - let direct = frame.sun_color.rgb * sun_on * max(ndl, 0.0) * s * sky_reach; -@@ -436,10 +517,27 @@ fn fs_light(in: FullOut) -> @location(0) vec4 { - c = mix(c, haze, (1.0 - exp(-sph / 700.0)) * 0.5); - } - } -- // Light in the air: the sun's rays between the shadows, and halos round the mods' lights. -+ // The volumetric fog: the light the air scatters towards the camera (shafts through the trees, -+ // mist, haze), and how much of the pixel it hides. - let far = select(min(length(p), frame.shadow.z), frame.shadow.z, d <= 0.0); - let density = frame.ambient.z; -- if sun_on > 0.0 && density > 0.0 { -+ let volumetric = frame.vfog.x > 0.5 && atmosphere; -+ if volumetric { -+ var fog_len = 192.0; -+ if d > 0.0 { -+ var pf = p; -+ if water { -+ let hf = frame.inverse_view_proj * vec4(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0, max(d, 0.000001), 1.0); -+ pf = hf.xyz / hf.w; -+ } -+ fog_len = min(length(pf), 192.0); -+ } -+ let fg = fog_at(px, fog_len); -+ c = c * fg.a + fg.rgb; -+ } -+ // Light in the air (without the volumetric fog): the sun's rays between the shadows; always the -+ // halos round the mods' lights. -+ if !volumetric && sun_on > 0.0 && density > 0.0 { - let steps = 14; - let jitter = ign(in.position.xy); - var acc = 0.0; -@@ -1185,11 +1283,11 @@ fn fs_taau(in: FullOut) -> TaaOut { - } - "#; - --struct Target { -- view: wgpu::TextureView, -+pub(super) struct Target { -+ pub(super) view: wgpu::TextureView, - } - --fn target( -+pub(super) fn target( - device: &wgpu::Device, - label: &str, - size: [u32; 2], -@@ -1324,6 +1422,9 @@ pub struct Hd { - /// The frame's translucent terrain exists (set by the renderer before `write`). - pub water_geometry: bool, - water_composited: bool, -+ /// The volumetric clouds and the fog run this frame. -+ clouds_active: bool, -+ fog_active: bool, - /// The sky-view texture (see `hd_sky`), the pass that marches it and its bind group. - sky_lut: wgpu::TextureView, - sky_pipeline: wgpu::RenderPipeline, -@@ -1346,7 +1447,9 @@ pub struct Hd { - bloom: Vec, - /// Bind groups, rebuilt only when their views change: `resize` drops all of them, the - /// lighting and the source-reading ones are keyed by the views they read. -- light_group: Option<(LightKey, wgpu::BindGroup)>, -+ light_group: [Option<(LightKey, wgpu::BindGroup)>; 2], -+ /// The volumetric clouds and fog (see `hd_volumes`). -+ volumes: HdVolumes, - prefilter_group: Option<(wgpu::TextureView, wgpu::BindGroup)>, - tone_group: Option<(wgpu::TextureView, wgpu::BindGroup)>, - /// Reading bloom level k - 1 (for the down pass into level k, index k - 1) and level k (for -@@ -1537,25 +1640,76 @@ fn ao_strength_default() -> f32 { - - /// The shader's source: the common part, then the body with the sky-reading functions where it - /// has a marker (`//SKY15`: the sky-view texture is binding 15, `//SKY10`: binding 10). --fn assemble(body: &str) -> String { -+pub(super) fn assemble(body: &str) -> String { - let mut body = body.to_owned(); -- for binding in ["15", "10"] { -+ // Highest number first: `//SKY1` must not eat the start of `//SKY15`. -+ for binding in (0..32).rev() { - body = body.replace( - &format!("//SKY{binding}"), -- &hd_sky::SKY_FUNCS.replace("SKYBIND", binding), -+ &hd_sky::SKY_FUNCS.replace("SKYBIND", &binding.to_string()), - ); - } - format!("{COMMON}{body}") - } - --fn module(device: &wgpu::Device, label: &str, body: &str) -> wgpu::ShaderModule { -+pub(super) fn module(device: &wgpu::Device, label: &str, body: &str) -> wgpu::ShaderModule { -+ // Development: with `HORIZON_HD_SHADER_DIR` set, a shader's body is read from -+ // `/