Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2838a8f08 | |||
| c42d97af08 | |||
| 6efec3e546 | |||
| d3359f4b39 |
@@ -24,12 +24,8 @@ jobs:
|
||||
run: |
|
||||
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target wasm32-unknown-unknown
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
# Инструмент упаковки (cargo-horizon-mod без пробного запуска) — единственное, что стоит кэшировать.
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ~/.cargo/bin/cargo-horizon-mod
|
||||
key: tool-${{ hashFiles('HORIZON_COMMIT', 'horizon-patches/*') }}
|
||||
# Без actions/cache: кэш-сервер раннера из контейнеров задач недоступен — восстановление
|
||||
# падало по таймауту, а сохранение вешало «Complete job» на пару минут. Инструмент собирается заново.
|
||||
- name: Форк Horizon
|
||||
run: nice -n 19 python3 tools/airdef.py fork
|
||||
- name: Мод
|
||||
|
||||
@@ -68,6 +68,45 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает мо
|
||||
**Сохранение.** Комплексы, объекты, их повреждения и настройки сохраняются отдельно для каждого мира (и для каждого
|
||||
сервера); уничтоженный комплекс остаётся остовом и после перезахода, пока его не отремонтируют.
|
||||
|
||||
**Сеть ПВО.** Каждая единица делится целями со всеми в радиусе 1,5 км (линия связи): РЛС дальнего обнаружения
|
||||
даёт раннее предупреждение всем, пусковая без своей РЛС (P1-SUN) стреляет по чужим данным. Пушкам (Гепард,
|
||||
пушки Панциря) нужна своя цель: они стреляют только по тому, что видят сами. Уничтоженная РЛС — дыра в сети.
|
||||
|
||||
**Логистика.** Пока нет ни одного склада, магазины пополняются сами, как раньше. Как только поставлен склад,
|
||||
боекомплект считается: комплекс без ракет пишет «ЖДЁТ БК»; в 60 м от склада он перезаряжается прямо с него, а
|
||||
дальние обслуживают **ТЗМ**: заправляются на складе (2 перезарядки), сами едут к ждущим комплексам, ближайшим
|
||||
первым, и возвращаются. Запас склада — 30 перезарядок, он виден в сводке. Склад и ТЗМ детонируют всем грузом.
|
||||
|
||||
**Перемещение.** **Пульт перемещения**: навестись на технику (выбрать), затем на точку на земле — она
|
||||
сворачивается (С-400 и другие с вертикальным пуском дольше, Гепард и Панцирь быстро), едет по рельефу и
|
||||
разворачивается; всё это время не стреляет и не светит РЛС. Или `/airdef move <номер>` — к тому месту, где ты стоишь.
|
||||
|
||||
**РЭБ.** Клавиша **U** (или `/airdef ew on|off`) включает все станции. Станция в радиусе 350 м сбивает
|
||||
спутниковую навигацию у Шахедов, Герань, Гербер, Х-101 и Калибров: они теряют маршрут и уводятся с цели
|
||||
(баллистике и Ланцетам с оптикой всё равно). Передатчик работает до 20 с и перегревается; остывает ~35 с,
|
||||
пока не остынет — не включить. Включённая станция себя выдаёт: умный противник запоминает её и шлёт на неё
|
||||
Ланцеты и Х-22, а маршруты спутниковых дронов прокладывает в обход.
|
||||
|
||||
**Умный противник учится.** Он запоминает, откуда его налёты сбивали, а откуда они прорывались, и следующий
|
||||
налёт ведёт с лучшего направления; помнит комплексы, выдавшие себя пуском, и станции РЭБ, и во время налёта
|
||||
перенацеливает ещё не взлетевшие Ланцеты и Шахеды на них; если цель-комплекс уничтожен, дроны идут к
|
||||
следующему известному. Дроны под обстрелом прижимаются к земле.
|
||||
|
||||
**Ночь.** Ночью пушки попадают втрое хуже, если цель не освещена. Освещают **прожекторы** (луч ведёт ближайшую
|
||||
цель из сети, приоритет — цели пушек) и **осветительные ракеты**: пушечные комплексы пускают их сами (раз в
|
||||
25 с на цель в темноте), игрок — **осветительным пистолетом** или `/airdef flare`. Ракета висит ~30 с и
|
||||
освещает всё вокруг на ~60–90 м.
|
||||
|
||||
**Графика: три пресета** (меню N → «Огонь, ремонт, эффекты…» → «Эффекты», или `/airdef shader off|on|hd`):
|
||||
выкл — ванильная картинка; обычные — шейдерные огненные шары, ударная волна и подсветка; **HD** — свой рендер
|
||||
освещения в форке: мир дополнительно рисуется в G-буфер (цвет без света и нормали), и все источники света мода
|
||||
освещают его попиксельно в HDR — факелы ракет и двигатели, раскалённые боевые блоки, осветительные ракеты, вспышки
|
||||
выстрелов, трассеры, взрывы (вспышка и догорающий шар), горящие остовы, пятна прожекторов, — затем bloom и мягкий
|
||||
тонмаппинг (ванильный диапазон не трогается, сжимается только то, что ярче). Источники передаются движку новым
|
||||
API мода `render.set-lights` (до 512, плавно интерполируются), режим — `render.set-lighting`.
|
||||
|
||||
**Сетка J** после сбития ещё 3,5 с держит камеру на сбитой цели, и комплекс в это время не пропадает из сетки.
|
||||
|
||||
**Повреждения.** Взрыв рядом с комплексом повреждает его: он хуже поворачивается, хуже видит и попадает,
|
||||
дымит, ниже 35% горит. Уничтоженный остаётся горящим остовом, его боекомплект детонирует: чем больше ракет
|
||||
оставалось, тем мощнее взрыв и дольше вторичные (у Гепарда рвутся снаряды); соседние комплексы тоже могут
|
||||
@@ -103,7 +142,7 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает мо
|
||||
Чтобы в повторах и камерах комплексов была видна земля далеко от игрока, мод включает дальний ландшафт
|
||||
(48 чанков, упрощённый); выключается в меню N → «Огонь, ремонт, эффекты…» → «Дальний ландшафт».
|
||||
|
||||
Клавиши N, K, J и R можно переназначить в «Настройки → Моды».
|
||||
Клавиши N, K, J, R и U можно переназначить в «Настройки → Моды».
|
||||
|
||||
Команды:
|
||||
- `/airdef wave <тип> [кол-во] [дальность]` — налёт на ближайшую цель. Типы: `shahed`, `geran3`, `lancet`, `gerbera`, `kh101`, `kalibr`, `kh22`, `zircon`, `iskander`, `kinzhal`, `oreshnik` и `mix` (комбинированный удар двумя секторами: «Герберы», шахеды, Герани-3, Х-101, «Калибры», Х-22, Циркон, «Искандер», «Кинжал», при 30+ — «Орешник»);
|
||||
@@ -152,6 +191,15 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает мо
|
||||
| Панцирь-С1 | 12 × 57Э6 и 2 × 30 мм; модуль поворачивается, пакеты и стволы поднимаются | ракеты 25–150, пушки 60 |
|
||||
| С-400 | 4 × 48Н6, вертикальный холодный старт, РЛС обзора на прицепе | 30–480 |
|
||||
| С-500 | 4 × 77Н6, холодный старт, самые быстрые ракеты | 40–700 |
|
||||
| IRIS-T SLM | 8 ракет, вертикальный пуск, быстрые и манёвренные | 15–400 |
|
||||
| NASAMS | 6 × AMRAAM, наводимая пусковая на прицепе | 10–300 |
|
||||
| Бук-М2 | 4 × 9М317 на направляющих, башня с РЛС подсвета | 20–450 |
|
||||
| Тор-М2 | 8 × 9М338 вертикально из башни, быстрая реакция против дронов | 8–160 |
|
||||
| P1-SUN | 8 дронов-перехватчиков против Шахедов и Герань; своей РЛС нет — стреляет по данным сети | 5–450 |
|
||||
|
||||
Обслуживающая техника (тоже пультами или `/airdef deploy <ключ>`): **РЛС дальнего обнаружения** (`radar`,
|
||||
видит на 1,7 км с высокой мачты, хуже прячутся за рельефом), **склад боеприпасов** (`depot`), **ТЗМ** (`tzm`),
|
||||
**станция РЭБ** (`ew`), **прожектор** (`light`). Новые комплексы: `irist`, `nasams`, `buk`, `tor`, `p1sun`.
|
||||
|
||||
**Согласование огня.** Комплексы делят цели между собой: на обычную цель идёт одна ракета, на баллистическую две,
|
||||
пушки расходятся по разным целям. Первыми бьют по самым опасным (быстрым и тяжёлым) целям, при этом ложные
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/beam"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/buk_chassis"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/buk_rails"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/buk_turret"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/depot"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/ew_truck"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/flare"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/irist_chassis"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/irist_tubes"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/nasams_launcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/nasams_trailer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/p1sun"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/p1sun_launcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/radar_truck"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/searchlight"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/searchlight_head"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/tor_chassis"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/tor_turret"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "airdef:block/tzm_truck"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_buk"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_depot"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_ew"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_irist"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_light"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_nasams"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_p1sun"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_radar"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_tor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/deploy_tzm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/flare_gun"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "airdef:item/mover"
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,25 @@
|
||||
"block.airdef.iskander": "Искандер-М",
|
||||
"block.airdef.kinzhal": "Кинжал",
|
||||
"block.airdef.tracer": "Трассер",
|
||||
"block.airdef.irist_chassis": "Шасси IRIS-T SLM",
|
||||
"block.airdef.irist_tubes": "Пакет IRIS-T",
|
||||
"block.airdef.nasams_trailer": "Прицеп NASAMS",
|
||||
"block.airdef.nasams_launcher": "Пусковая NASAMS",
|
||||
"block.airdef.buk_chassis": "Шасси Бука",
|
||||
"block.airdef.buk_turret": "Башня Бука",
|
||||
"block.airdef.buk_rails": "Ракеты Бука",
|
||||
"block.airdef.tor_chassis": "Шасси Тора",
|
||||
"block.airdef.tor_turret": "Башня Тора",
|
||||
"block.airdef.p1sun_launcher": "Пусковая P1-SUN",
|
||||
"block.airdef.p1sun": "Дрон-перехватчик P1-SUN",
|
||||
"block.airdef.radar_truck": "Шасси РЛС",
|
||||
"block.airdef.depot": "Склад боеприпасов",
|
||||
"block.airdef.tzm_truck": "Транспортно-заряжающая машина",
|
||||
"block.airdef.ew_truck": "Станция РЭБ",
|
||||
"block.airdef.searchlight": "Прожектор",
|
||||
"block.airdef.searchlight_head": "Голова прожектора",
|
||||
"block.airdef.beam": "Луч прожектора",
|
||||
"block.airdef.flare": "Осветительная ракета",
|
||||
"block.airdef.reentry": "Боевой блок",
|
||||
"item.airdef.designator": "Целеуказатель",
|
||||
"item.airdef.launch_shahed": "Пуск: Шахед-136",
|
||||
@@ -56,6 +75,18 @@
|
||||
"item.airdef.launch_iskander": "Пуск: Искандер-М",
|
||||
"item.airdef.launch_kinzhal": "Пуск: Кинжал",
|
||||
"item.airdef.launch_oreshnik": "Пуск: Орешник",
|
||||
"item.airdef.deploy_irist": "Развернуть IRIS-T SLM",
|
||||
"item.airdef.deploy_nasams": "Развернуть NASAMS",
|
||||
"item.airdef.deploy_buk": "Развернуть Бук-М2",
|
||||
"item.airdef.deploy_tor": "Развернуть Тор-М2",
|
||||
"item.airdef.deploy_p1sun": "Развернуть P1-SUN",
|
||||
"item.airdef.deploy_radar": "Развернуть РЛС дальнего обнаружения",
|
||||
"item.airdef.deploy_depot": "Развернуть склад боеприпасов",
|
||||
"item.airdef.deploy_tzm": "Развернуть ТЗМ",
|
||||
"item.airdef.deploy_ew": "Развернуть станцию РЭБ",
|
||||
"item.airdef.deploy_light": "Развернуть прожектор",
|
||||
"item.airdef.mover": "Пульт перемещения",
|
||||
"item.airdef.flare_gun": "Осветительная ракета",
|
||||
"item.airdef.remover": "Свернуть ПВО",
|
||||
"item.airdef.cam_shahed": "Шахед-136",
|
||||
"item.airdef.cam_geran3": "Герань-3",
|
||||
|
||||
@@ -39,6 +39,25 @@
|
||||
"block.airdef.iskander": "Искандер-М",
|
||||
"block.airdef.kinzhal": "Кинжал",
|
||||
"block.airdef.tracer": "Трассер",
|
||||
"block.airdef.irist_chassis": "Шасси IRIS-T SLM",
|
||||
"block.airdef.irist_tubes": "Пакет IRIS-T",
|
||||
"block.airdef.nasams_trailer": "Прицеп NASAMS",
|
||||
"block.airdef.nasams_launcher": "Пусковая NASAMS",
|
||||
"block.airdef.buk_chassis": "Шасси Бука",
|
||||
"block.airdef.buk_turret": "Башня Бука",
|
||||
"block.airdef.buk_rails": "Ракеты Бука",
|
||||
"block.airdef.tor_chassis": "Шасси Тора",
|
||||
"block.airdef.tor_turret": "Башня Тора",
|
||||
"block.airdef.p1sun_launcher": "Пусковая P1-SUN",
|
||||
"block.airdef.p1sun": "Дрон-перехватчик P1-SUN",
|
||||
"block.airdef.radar_truck": "Шасси РЛС",
|
||||
"block.airdef.depot": "Склад боеприпасов",
|
||||
"block.airdef.tzm_truck": "Транспортно-заряжающая машина",
|
||||
"block.airdef.ew_truck": "Станция РЭБ",
|
||||
"block.airdef.searchlight": "Прожектор",
|
||||
"block.airdef.searchlight_head": "Голова прожектора",
|
||||
"block.airdef.beam": "Луч прожектора",
|
||||
"block.airdef.flare": "Осветительная ракета",
|
||||
"block.airdef.reentry": "Боевой блок",
|
||||
"item.airdef.designator": "Целеуказатель",
|
||||
"item.airdef.launch_shahed": "Пуск: Шахед-136",
|
||||
@@ -56,6 +75,18 @@
|
||||
"item.airdef.launch_iskander": "Пуск: Искандер-М",
|
||||
"item.airdef.launch_kinzhal": "Пуск: Кинжал",
|
||||
"item.airdef.launch_oreshnik": "Пуск: Орешник",
|
||||
"item.airdef.deploy_irist": "Развернуть IRIS-T SLM",
|
||||
"item.airdef.deploy_nasams": "Развернуть NASAMS",
|
||||
"item.airdef.deploy_buk": "Развернуть Бук-М2",
|
||||
"item.airdef.deploy_tor": "Развернуть Тор-М2",
|
||||
"item.airdef.deploy_p1sun": "Развернуть P1-SUN",
|
||||
"item.airdef.deploy_radar": "Развернуть РЛС дальнего обнаружения",
|
||||
"item.airdef.deploy_depot": "Развернуть склад боеприпасов",
|
||||
"item.airdef.deploy_tzm": "Развернуть ТЗМ",
|
||||
"item.airdef.deploy_ew": "Развернуть станцию РЭБ",
|
||||
"item.airdef.deploy_light": "Развернуть прожектор",
|
||||
"item.airdef.mover": "Пульт перемещения",
|
||||
"item.airdef.flare_gun": "Осветительная ракета",
|
||||
"item.airdef.remover": "Свернуть ПВО",
|
||||
"item.airdef.cam_shahed": "Шахед-136",
|
||||
"item.airdef.cam_geran3": "Герань-3",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"beam": "airdef:block/beam",
|
||||
"particle": "airdef:block/beam"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
6.5,
|
||||
6.5,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
9.5,
|
||||
9.5,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#beam"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#beam"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#beam"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#beam"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#beam"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#beam"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"ru_green": "airdef:block/ru_green",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"track": "airdef:block/track",
|
||||
"particle": "airdef:block/ru_green"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
-10.0,
|
||||
4.0,
|
||||
-14.0
|
||||
],
|
||||
"to": [
|
||||
26.0,
|
||||
15.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
6.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-8.0,
|
||||
15.0,
|
||||
22.0
|
||||
],
|
||||
"to": [
|
||||
24.0,
|
||||
16.0,
|
||||
32.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,
|
||||
10.0,
|
||||
1.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
10.0,
|
||||
1.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
10.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
10.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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"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,
|
||||
-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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"texture": "#track"
|
||||
},
|
||||
"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,
|
||||
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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
5.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
5.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
5.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
5.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"missile_ru": "airdef:block/missile_ru",
|
||||
"booster": "airdef:block/booster",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"ru_green": "airdef:block/ru_green",
|
||||
"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": "#ru_green"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"ru_green": "airdef:block/ru_green",
|
||||
"radar_face": "airdef:block/radar_face",
|
||||
"particle": "airdef:block/ru_green"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
-4.0,
|
||||
0.0,
|
||||
-4.0
|
||||
],
|
||||
"to": [
|
||||
20.0,
|
||||
9.0,
|
||||
20.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
1.0,
|
||||
2.0,
|
||||
-10.0
|
||||
],
|
||||
"to": [
|
||||
15.0,
|
||||
14.0,
|
||||
-4.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
14.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#radar_face"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
14.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,
|
||||
14.0,
|
||||
6.0
|
||||
],
|
||||
"texture": "#radar_face"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
14.0,
|
||||
6.0
|
||||
],
|
||||
"texture": "#radar_face"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-2.0,
|
||||
9.0,
|
||||
6.0
|
||||
],
|
||||
"to": [
|
||||
18.0,
|
||||
11.0,
|
||||
18.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
12.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
12.0
|
||||
],
|
||||
"texture": "#ru_green"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"concrete": "airdef:block/concrete",
|
||||
"crate": "airdef:block/crate",
|
||||
"tube": "airdef:block/tube",
|
||||
"tube_cap": "airdef:block/tube_cap",
|
||||
"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": [
|
||||
-12.0,
|
||||
2.0,
|
||||
-12.0
|
||||
],
|
||||
"to": [
|
||||
0.0,
|
||||
10.0,
|
||||
0.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-12.0,
|
||||
2.0,
|
||||
4.0
|
||||
],
|
||||
"to": [
|
||||
0.0,
|
||||
10.0,
|
||||
16.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
4.0,
|
||||
2.0,
|
||||
-12.0
|
||||
],
|
||||
"to": [
|
||||
16.0,
|
||||
10.0,
|
||||
0.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
8.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#crate"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"lamp": "airdef:block/lamp",
|
||||
"antenna": "airdef:block/antenna",
|
||||
"particle": "airdef:block/lamp"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
6.5,
|
||||
6.5,
|
||||
6.5
|
||||
],
|
||||
"to": [
|
||||
9.5,
|
||||
9.5,
|
||||
9.5
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#lamp"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#lamp"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#lamp"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#lamp"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#lamp"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#lamp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
7.6,
|
||||
9.5,
|
||||
7.6
|
||||
],
|
||||
"to": [
|
||||
8.4,
|
||||
16.0,
|
||||
8.4
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.8000000000000007,
|
||||
6.5
|
||||
],
|
||||
"texture": "#antenna"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.8000000000000007,
|
||||
6.5
|
||||
],
|
||||
"texture": "#antenna"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.8000000000000007,
|
||||
6.5
|
||||
],
|
||||
"texture": "#antenna"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.8000000000000007,
|
||||
6.5
|
||||
],
|
||||
"texture": "#antenna"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.8000000000000007,
|
||||
0.8000000000000007
|
||||
],
|
||||
"texture": "#antenna"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.8000000000000007,
|
||||
0.8000000000000007
|
||||
],
|
||||
"texture": "#antenna"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"canister": "airdef:block/canister",
|
||||
"tube_cap": "airdef:block/tube_cap",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"particle": "airdef:block/canister"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"canister": "airdef:block/canister",
|
||||
"tube_cap": "airdef:block/tube_cap",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"particle": "airdef:block/canister"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"nato_green": "airdef:block/nato_green",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"metal": "airdef:block/metal",
|
||||
"tire": "airdef:block/tire",
|
||||
"particle": "airdef:block/nato_green"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
-6.0,
|
||||
5.0,
|
||||
-10.0
|
||||
],
|
||||
"to": [
|
||||
22.0,
|
||||
9.0,
|
||||
26.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
4.0
|
||||
],
|
||||
"texture": "#nato_green"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
4.0
|
||||
],
|
||||
"texture": "#nato_green"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
4.0
|
||||
],
|
||||
"texture": "#nato_green"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
4.0
|
||||
],
|
||||
"texture": "#nato_green"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#nato_green"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#nato_green"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-4.0,
|
||||
9.0,
|
||||
10.0
|
||||
],
|
||||
"to": [
|
||||
20.0,
|
||||
13.0,
|
||||
24.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,
|
||||
14.0,
|
||||
4.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
14.0,
|
||||
4.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
14.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
14.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
-9.0,
|
||||
0.0,
|
||||
4.0
|
||||
],
|
||||
"to": [
|
||||
-5.5,
|
||||
9.0,
|
||||
11.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
21.5,
|
||||
0.0,
|
||||
4.0
|
||||
],
|
||||
"to": [
|
||||
25.0,
|
||||
9.0,
|
||||
11.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-9.0,
|
||||
0.0,
|
||||
14.0
|
||||
],
|
||||
"to": [
|
||||
-5.5,
|
||||
9.0,
|
||||
21.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
21.5,
|
||||
0.0,
|
||||
14.0
|
||||
],
|
||||
"to": [
|
||||
25.0,
|
||||
9.0,
|
||||
21.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
7.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.5,
|
||||
7.0
|
||||
],
|
||||
"texture": "#tire"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"missile": "airdef:block/missile",
|
||||
"prop": "airdef:block/prop",
|
||||
"particle": "airdef:block/dark_metal"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
6.5,
|
||||
6.5,
|
||||
-10.0
|
||||
],
|
||||
"to": [
|
||||
9.5,
|
||||
9.5,
|
||||
10.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.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,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
7.0,
|
||||
7.0,
|
||||
-14.0
|
||||
],
|
||||
"to": [
|
||||
9.0,
|
||||
9.0,
|
||||
-10.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
2.0
|
||||
],
|
||||
"texture": "#missile"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
2.0
|
||||
],
|
||||
"texture": "#missile"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.0,
|
||||
2.0
|
||||
],
|
||||
"texture": "#missile"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.0,
|
||||
2.0
|
||||
],
|
||||
"texture": "#missile"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
4.0
|
||||
],
|
||||
"texture": "#missile"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
4.0
|
||||
],
|
||||
"texture": "#missile"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
2.0,
|
||||
2.0,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
8.0,
|
||||
8.0,
|
||||
1.5
|
||||
],
|
||||
"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,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-0.5,
|
||||
-0.5,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
4.5,
|
||||
4.5,
|
||||
0.6
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
2.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
8.0,
|
||||
14.0,
|
||||
1.5
|
||||
],
|
||||
"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,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-0.5,
|
||||
11.5,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
4.5,
|
||||
16.5,
|
||||
0.6
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8.0,
|
||||
2.0,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
14.0,
|
||||
8.0,
|
||||
1.5
|
||||
],
|
||||
"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,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
11.5,
|
||||
-0.5,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
16.5,
|
||||
4.5,
|
||||
0.6
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
14.0,
|
||||
14.0,
|
||||
1.5
|
||||
],
|
||||
"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,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1.5,
|
||||
6.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6.0,
|
||||
1.5
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
11.5,
|
||||
11.5,
|
||||
0.0
|
||||
],
|
||||
"to": [
|
||||
16.5,
|
||||
16.5,
|
||||
0.6
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
0.6,
|
||||
5.0
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
5.0,
|
||||
0.6
|
||||
],
|
||||
"texture": "#prop"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,690 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"tube_cap": "airdef:block/tube_cap",
|
||||
"particle": "airdef:block/dark_metal"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
0.5,
|
||||
0.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
3.5,
|
||||
3.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
0.5,
|
||||
4.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
3.5,
|
||||
7.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
4.5,
|
||||
0.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
7.5,
|
||||
3.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
4.5,
|
||||
4.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
7.5,
|
||||
7.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8.5,
|
||||
0.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
11.5,
|
||||
3.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8.5,
|
||||
4.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
11.5,
|
||||
7.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
12.5,
|
||||
0.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
15.5,
|
||||
3.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
12.5,
|
||||
4.0,
|
||||
-16.0
|
||||
],
|
||||
"to": [
|
||||
15.5,
|
||||
7.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#tube_cap"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
3.0,
|
||||
16
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-0.5,
|
||||
0.0,
|
||||
26.0
|
||||
],
|
||||
"to": [
|
||||
16.5,
|
||||
7.0,
|
||||
28.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
7.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.5,
|
||||
0.0,
|
||||
-2.0
|
||||
],
|
||||
"to": [
|
||||
16.5,
|
||||
7.0,
|
||||
0.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2.0,
|
||||
7.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"particle": "airdef:block/dark_metal"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
2.0,
|
||||
0.0,
|
||||
2.0
|
||||
],
|
||||
"to": [
|
||||
14.0,
|
||||
3.0,
|
||||
14.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
3.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
12.0,
|
||||
12.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
6.0,
|
||||
3.0,
|
||||
6.0
|
||||
],
|
||||
"to": [
|
||||
10.0,
|
||||
12.0,
|
||||
10.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.0,
|
||||
9.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.0,
|
||||
9.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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"lamp": "airdef:block/lamp",
|
||||
"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": [
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"ru_khaki": "airdef:block/ru_khaki",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"track": "airdef:block/track",
|
||||
"particle": "airdef:block/ru_khaki"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
-10.0,
|
||||
4.0,
|
||||
-14.0
|
||||
],
|
||||
"to": [
|
||||
26.0,
|
||||
15.0,
|
||||
32.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
11.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
6.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
2.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
-8.0,
|
||||
15.0,
|
||||
22.0
|
||||
],
|
||||
"to": [
|
||||
24.0,
|
||||
16.0,
|
||||
32.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,
|
||||
10.0,
|
||||
1.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
10.0,
|
||||
1.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
10.0
|
||||
],
|
||||
"texture": "#dark_metal"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
10.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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"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,
|
||||
-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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
9.0
|
||||
],
|
||||
"texture": "#track"
|
||||
},
|
||||
"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,
|
||||
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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
5.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
5.0
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4.5,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"ru_khaki": "airdef:block/ru_khaki",
|
||||
"radar_face": "airdef:block/radar_face",
|
||||
"dark_metal": "airdef:block/dark_metal",
|
||||
"tube_cap": "airdef:block/tube_cap",
|
||||
"particle": "airdef:block/ru_khaki"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
-4.0,
|
||||
0.0,
|
||||
-6.0
|
||||
],
|
||||
"to": [
|
||||
20.0,
|
||||
16.0,
|
||||
22.0
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"south": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"east": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"west": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"up": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
},
|
||||
"down": {
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
16
|
||||
],
|
||||
"texture": "#ru_khaki"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_buk"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_depot"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_ew"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_irist"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_light"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_nasams"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_p1sun"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_radar"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_tor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/deploy_tzm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/flare_gun"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "airdef:item/mover"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 342 B |
|
After Width: | Height: | Size: 97 B |
|
After Width: | Height: | Size: 374 B |
|
After Width: | Height: | Size: 328 B |
|
After Width: | Height: | Size: 97 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 404 B |
|
After Width: | Height: | Size: 397 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 388 B |
|
After Width: | Height: | Size: 395 B |
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 403 B |
|
After Width: | Height: | Size: 401 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 404 B |
|
Before Width: | Height: | Size: 407 B After Width: | Height: | Size: 411 B |
@@ -115,7 +115,8 @@ fn post(uv: vec2<f32>, pixel: vec2<f32>) -> vec4<f32> {
|
||||
// the pixel sees, much stronger at night.
|
||||
let night = clamp(0.35 - frame.time.z * 1.8, 0.0, 1.0);
|
||||
let strength = mix(0.35, 2.2, night);
|
||||
if wall < 1.0e5 {
|
||||
// Under the HD lighting (params[0].w) the engine lights the world from the same lights.
|
||||
if wall < 1.0e5 && frame.params[0].w < 0.5 {
|
||||
let p = dir * wall;
|
||||
var light = vec3<f32>(0.0);
|
||||
for (var i = 0; i < blasts; i++) {
|
||||
|
||||
@@ -7,7 +7,7 @@ use horizon_mod_sdk::{
|
||||
Mod, Registration, chat, command, game,
|
||||
hud::{self, Corner},
|
||||
input::{self, Event},
|
||||
render::{self, ModelInstance, ParticleSpawn},
|
||||
render::{self, Lighting, ModelInstance, ParticleSpawn, PointLight},
|
||||
storage,
|
||||
ui::{self, ButtonWidget, ItemWidget, ListWidget, Screen, Widget},
|
||||
};
|
||||
@@ -20,7 +20,7 @@ mod sim;
|
||||
mod wave;
|
||||
use sim::{Class, DroneKind, Ground, Out, Sam, SamKind, Sim};
|
||||
|
||||
const ITEMS: [&str; 17] = [
|
||||
const ITEMS: [&str; 29] = [
|
||||
"airdef:designator",
|
||||
"airdef:launch_shahed",
|
||||
"airdef:launch_geran3",
|
||||
@@ -37,6 +37,18 @@ const ITEMS: [&str; 17] = [
|
||||
"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",
|
||||
];
|
||||
|
||||
@@ -58,6 +70,10 @@ struct AirDef {
|
||||
replay: Option<replay::Player>,
|
||||
/// The world drawn beyond the render distance (replays and cameras see far off).
|
||||
far: bool,
|
||||
/// The unit the move remote picked (its id), waiting for where to go.
|
||||
moving: Option<u32>,
|
||||
/// The HD lighting preset (the engine's lights, bloom, tone mapping).
|
||||
hd: bool,
|
||||
/// The world whose state is loaded (`game.world-id`), once one is.
|
||||
world: Option<Option<String>>,
|
||||
/// The systems' and objects' health as last saved, to save again when it changes.
|
||||
@@ -198,6 +214,47 @@ impl AirDef {
|
||||
None => say("Наведитесь на землю, где поставить комплекс"),
|
||||
}
|
||||
}
|
||||
"airdef:mover" => {
|
||||
let Some(at) = aim() else {
|
||||
say("Наведитесь на технику или на точку на земле");
|
||||
return;
|
||||
};
|
||||
let near = self
|
||||
.sim
|
||||
.sams
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, s)| s.pos.distance(at) < 6.0 && s.alive())
|
||||
.min_by(|a, b| a.1.pos.distance(at).total_cmp(&b.1.pos.distance(at)))
|
||||
.map(|(i, s)| (i, s.id));
|
||||
match (near, self.moving) {
|
||||
// A unit pointed at: picked (or another picked instead).
|
||||
(Some((i, id)), _) => {
|
||||
let s = &self.sim.sams[i];
|
||||
if s.kind.mobile() {
|
||||
self.moving = Some(id);
|
||||
say(&format!("{} №{} выбран: укажите, куда ехать", s.kind.name(), i + 1));
|
||||
} else {
|
||||
say(&format!("{} не перемещается", s.kind.name()));
|
||||
}
|
||||
}
|
||||
// The ground: the picked unit drives there.
|
||||
(None, Some(id)) => {
|
||||
if let Some(i) = self.sim.sams.iter().position(|s| s.id == id)
|
||||
&& self.sim.order_move(i, at)
|
||||
{
|
||||
let s = &self.sim.sams[i];
|
||||
say(&format!("{} №{}: свёртывание и марш, {}", s.kind.name(), i + 1, panel::distance(s.pos.distance(at))));
|
||||
out.sound("minecraft:ui.button.click", at, 1.0, 1.0);
|
||||
}
|
||||
self.moving = None;
|
||||
}
|
||||
(None, None) => say("Сначала наведитесь на технику, которую перемещать"),
|
||||
}
|
||||
}
|
||||
"airdef:flare_gun" => {
|
||||
self.sim.fire_flare(eye(&p) + view(&p), view(&p), &mut out);
|
||||
}
|
||||
"airdef:remover" => {
|
||||
let at = aim().unwrap_or(eye(&p));
|
||||
if let Some(i) = self
|
||||
@@ -221,11 +278,79 @@ impl AirDef {
|
||||
self.emit(out);
|
||||
}
|
||||
|
||||
/// 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<PointLight> {
|
||||
let mut lights: Vec<PointLight> = Vec::new();
|
||||
let mut add = |id: u32, at: DVec3, rgb: (f32, f32, f32), intensity: f32, radius: f32| {
|
||||
if lights.len() < 500 && intensity > 0.05 {
|
||||
lights.push(PointLight { id, x: at.x, y: at.y, z: at.z, color: rgb, intensity, radius: radius.clamp(1.0, 256.0) });
|
||||
}
|
||||
};
|
||||
let tick = self.sim.tick;
|
||||
let flicker = |id: u32| 0.85 + 0.15 * (((tick as u32).wrapping_mul(2654435761) ^ id.wrapping_mul(97)) % 100) as f32 / 100.0;
|
||||
for m in out.models.iter().filter(|m| m.glow) {
|
||||
let s = m.scale.max(0.1);
|
||||
match m.name {
|
||||
"airdef:flame" => add(m.id, m.at, (1.0, 0.55, 0.2), 45.0 * s * flicker(m.id), 32.0 * s.sqrt()),
|
||||
"airdef:geran3_flame" => add(m.id, m.at, (1.0, 0.6, 0.25), 25.0 * s * flicker(m.id), 22.0),
|
||||
"airdef:reentry" => add(m.id, m.at, (1.0, 0.6, 0.32), 60.0 * s, 40.0),
|
||||
"airdef:flare" => add(m.id, m.at, (1.0, 0.93, 0.78), 420.0 * s, 110.0),
|
||||
"airdef:flash" => add(m.id, m.at, (1.0, 0.8, 0.45), 30.0, 14.0),
|
||||
"airdef:tracer" => add(m.id, m.at, (1.0, 0.25, 0.1), 2.5, 6.0),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for b in &self.sim.blasts {
|
||||
let age = tick.saturating_sub(b.start) as f32;
|
||||
let size = b.size;
|
||||
let glow = (-age / (b.life as f32 * 0.15).max(1.0)).exp();
|
||||
let flash = (-age * 0.35).exp() * 4.0;
|
||||
let id = 0x6100_0000 ^ (b.start as u32).wrapping_mul(31) ^ (b.at.x as i32 as u32).wrapping_mul(7);
|
||||
add(id, b.at + DVec3::Y * size as f64 * 0.5, (1.0, 0.58, 0.25), size * size * 18.0 * (glow + flash), size * 14.0);
|
||||
}
|
||||
for s in &self.sim.sams {
|
||||
if !s.alive() && s.burning() {
|
||||
add(s.id + 15, s.pos + DVec3::Y * 1.5, (1.0, 0.5, 0.18), 80.0 * flicker(s.id), 30.0);
|
||||
}
|
||||
// A searchlight's spot where its beam meets the target.
|
||||
if s.kind == SamKind::Light
|
||||
&& let Some(t) = s.target.and_then(|t| self.sim.drones.iter().find(|d| d.id == t))
|
||||
{
|
||||
add(s.id + 14, t.pos, (0.95, 0.97, 1.0), 220.0, 30.0);
|
||||
}
|
||||
}
|
||||
lights
|
||||
}
|
||||
|
||||
/// The effects preset: none, the post pass, or the post pass under the HD lighting.
|
||||
fn set_effects(&mut self, shader: bool, hd: bool) {
|
||||
self.sim.shader = shader;
|
||||
if shader {
|
||||
render::set_post(include_str!("../shaders/effects.wgsl"));
|
||||
} else {
|
||||
render::clear_post();
|
||||
}
|
||||
self.hd = shader && hd;
|
||||
self.apply_hd();
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
/// Switches the HD lighting.
|
||||
fn apply_hd(&self) {
|
||||
render::set_lighting(Lighting { hd: self.hd, exposure: 1.0, bloom: 0.55 });
|
||||
if !self.hd {
|
||||
render::set_lights(&[]);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<f32> {
|
||||
let mut p = vec![0.0f32; 4];
|
||||
p[0] = self.sim.tick as f32;
|
||||
p[3] = if self.hd { 1.0 } else { 0.0 };
|
||||
let blasts: Vec<&sim::Blast> = self.sim.blasts.iter().rev().take(48).collect();
|
||||
p[1] = blasts.len() as f32;
|
||||
for b in &blasts {
|
||||
@@ -244,9 +369,13 @@ impl AirDef {
|
||||
"airdef:target" => (1.0, 0.1, 0.1, 0.5),
|
||||
"airdef:tracer" => (1.0, 0.12, 0.05, 0.9),
|
||||
"airdef:reentry" => (1.0, 0.6, 0.3, 1.4),
|
||||
"airdef:flare" => (1.0, 0.94, 0.78, 1.2),
|
||||
_ => continue,
|
||||
};
|
||||
p.extend([m.at.x as f32, m.at.y as f32, m.at.z as f32, m.scale * 1.6]);
|
||||
// The shader lights about six times the radius round a light and draws a glow of
|
||||
// its radius: a flare lights some sixty blocks round, the rest about their size.
|
||||
let radius = if m.name == "airdef:flare" { 10.0 } else { m.scale * 1.6 };
|
||||
p.extend([m.at.x as f32, m.at.y as f32, m.at.z as f32, radius]);
|
||||
p.extend([r, g, b, i]);
|
||||
lights += 1;
|
||||
}
|
||||
@@ -258,6 +387,9 @@ impl AirDef {
|
||||
if self.sim.shader {
|
||||
render::set_params(&self.shader_params(&out));
|
||||
}
|
||||
if self.hd {
|
||||
render::set_lights(&self.lights(&out));
|
||||
}
|
||||
let models: Vec<ModelInstance> = out
|
||||
.models
|
||||
.into_iter()
|
||||
@@ -355,7 +487,7 @@ impl AirDef {
|
||||
fn save(&mut self) {
|
||||
let mut text = String::new();
|
||||
for s in &self.sim.sams {
|
||||
text += &format!("sam {} {} {} {} {} {} {}\n", s.kind.key(), s.pos.x, s.pos.y, s.pos.z, s.heading, s.roles, s.health);
|
||||
text += &format!("sam {} {} {} {} {} {} {} {}\n", s.kind.key(), s.pos.x, s.pos.y, s.pos.z, s.heading, s.roles, s.health, s.cargo);
|
||||
}
|
||||
for (i, t) in self.sim.targets.iter().enumerate() {
|
||||
let h = self.sim.target_health.get(i).copied().unwrap_or(100.0);
|
||||
@@ -363,6 +495,7 @@ impl AirDef {
|
||||
}
|
||||
text += &format!("damage {}\n", self.sim.damage);
|
||||
text += &format!("far {}\n", self.far);
|
||||
text += &format!("effects {} {}\n", self.sim.shader, self.hd);
|
||||
for (kind, roles) in &self.sim.roles {
|
||||
text += &format!("roles {} {}\n", kind.key(), roles);
|
||||
}
|
||||
@@ -395,6 +528,9 @@ impl AirDef {
|
||||
if let Some(h) = num(7) {
|
||||
sam.health = h.clamp(0.0, 1.0);
|
||||
}
|
||||
if let Some(c) = num(8) {
|
||||
sam.cargo = c as u32;
|
||||
}
|
||||
self.sim.sams.push(sam);
|
||||
}
|
||||
}
|
||||
@@ -406,6 +542,11 @@ impl AirDef {
|
||||
}
|
||||
Some(&"damage") => self.sim.damage = w.get(1) != Some(&"false"),
|
||||
Some(&"far") => self.far = w.get(1) != Some(&"false"),
|
||||
Some(&"effects") => {
|
||||
let shader = w.get(1) != Some(&"false");
|
||||
let hd = w.get(2) == Some(&"true");
|
||||
self.set_effects(shader, hd);
|
||||
}
|
||||
Some(&"roles") => {
|
||||
if let (Some(kind), Some(r)) = (w.get(1).and_then(|k| SamKind::from_key(k)), w.get(2).and_then(|v| v.parse().ok())) {
|
||||
self.sim.roles.insert(kind, r);
|
||||
@@ -425,6 +566,19 @@ impl AirDef {
|
||||
}
|
||||
|
||||
impl AirDef {
|
||||
/// The EW stations: on if none is, else off.
|
||||
fn toggle_ew(&mut self) {
|
||||
let on = !self.sim.ew_active();
|
||||
let (n, hot) = self.sim.set_ew(on);
|
||||
if !self.sim.sams.iter().any(|s| s.kind == SamKind::Ew && s.alive()) {
|
||||
say("Нет станций РЭБ: поставьте их пультом");
|
||||
} else if on {
|
||||
say(&format!("РЭБ включён: станций {n}{}", if hot > 0 { format!(", остывают {hot}") } else { String::new() }));
|
||||
} else {
|
||||
say("РЭБ выключен");
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the open world's state is kept: a key of its own per world.
|
||||
fn state_key(&self) -> String {
|
||||
match self.world.as_ref().and_then(|w| w.as_ref()) {
|
||||
@@ -528,13 +682,15 @@ impl AirDef {
|
||||
/// monitored one always; those shown keep a head start, so the grid doesn't flicker.
|
||||
fn pick_grid(&mut self) {
|
||||
let shown = &self.grid_ids;
|
||||
// Units that shoot, and the radars; depots, trucks and lights have nothing to show.
|
||||
let mut scored: Vec<(f64, u32)> = self
|
||||
.sim
|
||||
.sams
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, s)| s.kind.armed() || s.kind == SamKind::Radar)
|
||||
.map(|(i, s)| {
|
||||
let busy = match s.state {
|
||||
let busy = if self.sim.showing_kill(s) { 98.0 } else { match s.state {
|
||||
sim::SamState::Launch | sim::SamState::Fire => 100.0,
|
||||
sim::SamState::Lock => 85.0,
|
||||
sim::SamState::Slew => 75.0,
|
||||
@@ -543,7 +699,9 @@ impl AirDef {
|
||||
sim::SamState::Search => 10.0,
|
||||
sim::SamState::Hold => 5.0,
|
||||
sim::SamState::Destroyed => if s.burning() { 90.0 } else { 1.0 },
|
||||
};
|
||||
sim::SamState::Waiting => 35.0,
|
||||
sim::SamState::Packing | sim::SamState::Moving | sim::SamState::Deploying => 40.0,
|
||||
} };
|
||||
let near = self.sim.watched(s).map_or(0.0, |d| 15.0 * (1.0 - (d.pos.distance(s.pos) / s.kind.range()).min(1.0)));
|
||||
let hurt = if s.alive() && s.health < 0.7 { 15.0 } else { 0.0 };
|
||||
let keep = if shown.contains(&s.id) { 12.0 } else { 0.0 };
|
||||
@@ -574,7 +732,7 @@ impl AirDef {
|
||||
match page {
|
||||
Page::Main => {
|
||||
title = "AirDef — командный пункт (N меню, K монитор)".to_string();
|
||||
let rows = SamKind::ALL
|
||||
let rows = SamKind::ARMED
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let n = self.sim.sams.iter().filter(|s| s.kind == *k).count();
|
||||
@@ -619,7 +777,8 @@ impl AirDef {
|
||||
}));
|
||||
let broken = self.sim.sams.iter().filter(|s| s.health < 1.0).count();
|
||||
widgets.push(Widget::Button(ButtonWidget { id: BTN_REPAIR, label: format!("Ремонт всех (повреждено: {broken})") }));
|
||||
widgets.push(Widget::Button(ButtonWidget { id: BTN_SHADER, label: format!("Эффекты: {}", onoff(self.sim.shader)) }));
|
||||
let fx = if !self.sim.shader { "выкл" } else if self.hd { "HD (свет, bloom)" } else { "обычные" };
|
||||
widgets.push(Widget::Button(ButtonWidget { id: BTN_SHADER, label: format!("Эффекты: {fx}") }));
|
||||
widgets.push(Widget::Button(ButtonWidget { id: BTN_DAMAGE, label: format!("Разрушения: {}", onoff(self.sim.damage)) }));
|
||||
widgets.push(Widget::Button(ButtonWidget { id: BTN_FAR, label: format!("Дальний ландшафт: {}", onoff(self.far)) }));
|
||||
widgets.push(Widget::Button(ButtonWidget { id: BTN_BACK, label: "← Назад".into() }));
|
||||
@@ -684,7 +843,7 @@ impl AirDef {
|
||||
}
|
||||
}
|
||||
|
||||
const HELP: &str = "/airdef menu | monitor | grid | replay [map|full|reel|stop] | raid | repair | import | target here | give | deploy <комплекс> | wave <тип|mix> [кол-во] [дальность] | clear | targets clear | damage on|off | shader on|off | hold on|off | roles [комплекс классы] | stats";
|
||||
const HELP: &str = "/airdef menu | monitor | grid | replay [map|full|reel|stop] | raid | repair | import | ew on|off | flare | move <номер> | target here | give | deploy <комплекс> | wave <тип|mix> [кол-во] [дальность] | clear | targets clear | damage on|off | shader on|off|hd | hold on|off | roles [комплекс классы] | stats";
|
||||
|
||||
impl Mod for AirDef {
|
||||
fn init() -> Result<(Self, Registration), String> {
|
||||
@@ -701,6 +860,8 @@ impl Mod for AirDef {
|
||||
wave: wave::WaveConfig::default(),
|
||||
replay: None,
|
||||
far: true,
|
||||
moving: None,
|
||||
hd: false,
|
||||
world: None,
|
||||
health_mark: 0,
|
||||
menu: None,
|
||||
@@ -711,6 +872,7 @@ impl Mod for AirDef {
|
||||
let _ = input::register_key("monitor", "Монитор ПВО: следующий комплекс", "key.keyboard.k");
|
||||
let _ = input::register_key("grid", "Командный пункт: все комплексы", "key.keyboard.j");
|
||||
let _ = input::register_key("replay", "Повтор налёта: карта / выход", "key.keyboard.r");
|
||||
let _ = input::register_key("ew", "РЭБ: включить / выключить", "key.keyboard.u");
|
||||
// The replay's own keys (they do nothing outside a replay).
|
||||
let _ = input::register_key("rp_back", "Повтор: назад", "key.keyboard.left");
|
||||
let _ = input::register_key("rp_forward", "Повтор: вперёд", "key.keyboard.right");
|
||||
@@ -759,6 +921,7 @@ impl Mod for AirDef {
|
||||
}
|
||||
Event::KeyDown(k) if k == "monitor" => self.cycle_monitor(),
|
||||
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() {
|
||||
self.stop_replay();
|
||||
@@ -780,6 +943,7 @@ impl Mod for AirDef {
|
||||
self.ground.answer(render::ground(), now);
|
||||
let mut out = Out::default();
|
||||
self.listener = DVec3::new(p.x, p.y + 1.6, p.z);
|
||||
self.sim.night = game::get_clock().is_some_and(|c| (13000..23000).contains(&(c.day_time % 24000)));
|
||||
self.sim.step(&mut self.ground, Some(DVec3::new(p.x, p.y, p.z)), &mut out);
|
||||
let wanted = self.ground.take_wanted();
|
||||
if !wanted.is_empty() {
|
||||
@@ -850,7 +1014,7 @@ impl Mod for AirDef {
|
||||
let page = self.menu.unwrap_or(Page::Main);
|
||||
match (page, id) {
|
||||
(Page::Main, MENU_KINDS) => {
|
||||
if let Some(k) = SamKind::ALL.get(row as usize) {
|
||||
if let Some(k) = SamKind::ARMED.get(row as usize) {
|
||||
self.open_menu(Page::Roles(*k));
|
||||
}
|
||||
return;
|
||||
@@ -948,12 +1112,13 @@ impl Mod for AirDef {
|
||||
return;
|
||||
}
|
||||
(Page::More, BTN_SHADER) => {
|
||||
self.sim.shader = !self.sim.shader;
|
||||
if self.sim.shader {
|
||||
render::set_post(include_str!("../shaders/effects.wgsl"));
|
||||
} else {
|
||||
render::clear_post();
|
||||
}
|
||||
// Off → effects → HD → off.
|
||||
let (shader, hd) = match (self.sim.shader, self.hd) {
|
||||
(false, _) => (true, false),
|
||||
(true, false) => (true, true),
|
||||
(true, true) => (false, false),
|
||||
};
|
||||
self.set_effects(shader, hd);
|
||||
}
|
||||
(Page::More, BTN_FAR) => {
|
||||
self.far = !self.far;
|
||||
@@ -1006,7 +1171,8 @@ impl Mod for AirDef {
|
||||
}
|
||||
["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")?;
|
||||
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);
|
||||
@@ -1050,6 +1216,25 @@ impl Mod for AirDef {
|
||||
self.launch_wave(&mut out)?;
|
||||
None
|
||||
}
|
||||
["move", n] => {
|
||||
// Unit n (as the summary numbers them) drives to where the player stands.
|
||||
let p = player().ok_or("Нет игрока")?;
|
||||
let i = n.parse::<usize>().ok().and_then(|n| n.checked_sub(1)).ok_or("/airdef move <номер>")?;
|
||||
if self.sim.order_move(i, DVec3::new(p.x, p.y, p.z)) {
|
||||
Some(format!("{} №{}: на марш", self.sim.sams[i].kind.name(), i + 1))
|
||||
} else {
|
||||
Some("Эта техника не может ехать".into())
|
||||
}
|
||||
}
|
||||
["ew", v @ ("on" | "off")] => {
|
||||
let (n, hot) = self.sim.set_ew(*v == "on");
|
||||
Some(format!("РЭБ: включено станций {n}{}", if hot > 0 { format!(", остывают {hot}") } else { String::new() }))
|
||||
}
|
||||
["flare"] => {
|
||||
let p = player().ok_or("Нет игрока")?;
|
||||
self.sim.fire_flare(eye(&p) + view(&p), view(&p), &mut out);
|
||||
None
|
||||
}
|
||||
["import"] => {
|
||||
// The state the mod kept before worlds had their own: brought into this world.
|
||||
let before = self.sim.sams.len();
|
||||
@@ -1085,7 +1270,7 @@ impl Mod for AirDef {
|
||||
Some(format!("Разрушения: {}", if self.sim.damage { "вкл" } else { "выкл" }))
|
||||
}
|
||||
["roles"] => {
|
||||
for kind in SamKind::ALL {
|
||||
for kind in SamKind::ARMED {
|
||||
let r = self.sim.roles.get(&kind).copied().unwrap_or(kind.spec().roles);
|
||||
let names: Vec<&str> = Class::ALL.iter().filter(|c| r & c.bit() != 0).map(|c| c.name()).collect();
|
||||
say(&format!("{}: {}", kind.name(), if names.is_empty() { "ничего".into() } else { names.join(", ") }));
|
||||
@@ -1094,9 +1279,9 @@ impl Mod for AirDef {
|
||||
}
|
||||
["roles", who, what] => {
|
||||
let kinds: Vec<SamKind> = if *who == "all" {
|
||||
SamKind::ALL.to_vec()
|
||||
SamKind::ARMED.to_vec()
|
||||
} else {
|
||||
vec![SamKind::from_key(who).ok_or("Комплекс: patriot, gepard, pantsir, s400, s500 или all")?]
|
||||
vec![SamKind::from_key(who).ok_or("Комплекс: patriot, gepard, pantsir, s400, s500, irist, nasams, buk, tor, p1sun или all")?]
|
||||
};
|
||||
for kind in kinds {
|
||||
let mask = if *what == "default" {
|
||||
@@ -1120,14 +1305,9 @@ impl Mod for AirDef {
|
||||
self.sim.hold = *v == "on";
|
||||
Some(if self.sim.hold { "ПВО: огонь запрещён".into() } else { "ПВО: огонь разрешён".into() })
|
||||
}
|
||||
["shader", v @ ("on" | "off")] => {
|
||||
self.sim.shader = *v == "on";
|
||||
if self.sim.shader {
|
||||
render::set_post(include_str!("../shaders/effects.wgsl"));
|
||||
} else {
|
||||
render::clear_post();
|
||||
}
|
||||
Some(format!("Шейдерные эффекты: {}", if self.sim.shader { "вкл" } else { "выкл" }))
|
||||
["shader", v @ ("on" | "off" | "hd")] => {
|
||||
self.set_effects(*v != "off", *v == "hd");
|
||||
Some(format!("Эффекты: {}", match *v { "off" => "выкл", "hd" => "HD", _ => "обычные" }))
|
||||
}
|
||||
["stats"] => Some(format!(
|
||||
"В воздухе: {}, ракет: {}, ПВО: {}, сбито: {}, поражений: {}",
|
||||
|
||||
@@ -53,9 +53,9 @@ const PICTURE: (u32, u32) = (320, 200);
|
||||
/// target fills a good part of the picture; else ahead and a little up.
|
||||
pub fn camera(sim: &Sim, sam: &Sam) -> CameraView {
|
||||
let eye = sam.pos + DVec3::Y * 3.5;
|
||||
let (dir, fov) = match sim.watched(sam) {
|
||||
Some(d) => {
|
||||
let rel = d.pos - eye;
|
||||
let (dir, fov) = match sim.focus(sam) {
|
||||
Some(p) => {
|
||||
let rel = p - eye;
|
||||
let dist = rel.length().max(1.0);
|
||||
// About 14 blocks of view round a missile far off, wider when it is near.
|
||||
let fov = (2.0 * (7.0 / dist).atan()).to_degrees().clamp(1.5, 50.0);
|
||||
@@ -180,7 +180,7 @@ fn cell(sim: &Sim, index: usize, x: f32, y: f32, w: f32, h: f32, s: &mut Vec<Sha
|
||||
if lock {
|
||||
s.push(text("ЗАХВАТ", cx + cw / 2.0 - 18.0, cy + 3.0, color));
|
||||
}
|
||||
if !t.alive() {
|
||||
if !t.alive() || sim.showing_kill(sam) {
|
||||
s.push(text("ПОРАЖЕНА", cx + cw / 2.0 - 24.0, cy + ch / 2.0 + bh / 2.0 + 2.0, 0xFF60FF60));
|
||||
}
|
||||
let rel = t.pos - sam.pos;
|
||||
|
||||
@@ -78,7 +78,11 @@ pub fn summary(sim: &Sim, air: &[&Drone], player: DVec3) -> Vec<Line> {
|
||||
lines.push(line(format!("Перехватчиков в полёте: {}", sim.missiles.len()), YELLOW));
|
||||
}
|
||||
for sam in sim.sams.iter().take(8) {
|
||||
let mut text = format!("{} [{}]", sam.status(), crate::sim::roles_text(sam.roles));
|
||||
let mut text = if sam.kind.armed() {
|
||||
format!("{} [{}]", sam.status(), crate::sim::roles_text(sam.roles))
|
||||
} else {
|
||||
sam.status()
|
||||
};
|
||||
if let Some(t) = sam.gun_target.or(sam.target).and_then(|t| sim.drones.iter().find(|d| d.id == t)) {
|
||||
text += &format!(" > {} {}", t.kind.spec().name, distance(t.pos.distance(sam.pos)));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ impl MissileKind {
|
||||
MissileKind::M57e6 => 0.45,
|
||||
MissileKind::M48n6 => 1.8,
|
||||
MissileKind::M77n6 => 2.2,
|
||||
MissileKind::IrisT => 0.6,
|
||||
MissileKind::Amraam => 0.5,
|
||||
MissileKind::M9m317 => 1.2,
|
||||
MissileKind::M9m338 => 0.4,
|
||||
MissileKind::P1sun => 0.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,11 +87,16 @@ impl Sim {
|
||||
let pos = sam.pos + DVec3::Y;
|
||||
out.messages.push(format!("{} №{} УНИЧТОЖЕН", sam.kind.name(), i + 1));
|
||||
let (id, kind) = (sam.id, sam.kind);
|
||||
let charge = spec.missile.as_ref().map_or(0.0, |m| m.kind.charge() * missiles as f64) + bursts as f64 / 60.0;
|
||||
// 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 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.
|
||||
let first = (1.0 + charge * 0.6).round().clamp(1.0, 8.0) as u32;
|
||||
self.later.push((now + 2, pos, first, false));
|
||||
for k in 0..missiles {
|
||||
for k in 0..missiles.min(40) {
|
||||
let when = now + 15 + (k as u64) * 12 + (self.rng.f() * 25.0) as u64;
|
||||
let at = pos + DVec3::new(self.rng.s() * 3.0, self.rng.f() * 2.0, self.rng.s() * 3.0);
|
||||
let tnt = if charge > 4.0 && self.rng.chance(0.4) { 2 } else { 1 };
|
||||
@@ -186,14 +196,11 @@ impl Sim {
|
||||
let hull = sam.pos + DVec3::Y * (0.5 * s - 0.35);
|
||||
let side = DQuat::from_rotation_y(sam.heading) * DVec3::new(1.8 * s, 0.0, 0.6 * s);
|
||||
let thrown = DQuat::from_rotation_y(sam.heading + 1.1) * DQuat::from_rotation_z(1.2);
|
||||
let (body, part) = match sam.kind {
|
||||
SamKind::Patriot => ("airdef:patriot_trailer", "airdef:patriot_launcher"),
|
||||
SamKind::Gepard => ("airdef:gepard_hull", "airdef:gepard_turret"),
|
||||
SamKind::Pantsir => ("airdef:pantsir_truck", "airdef:pantsir_turret"),
|
||||
SamKind::S400 => ("airdef:s400_chassis", "airdef:s400_tubes"),
|
||||
SamKind::S500 => ("airdef:s500_chassis", "airdef:s500_tubes"),
|
||||
};
|
||||
let parts = &sam.kind.spec().parts;
|
||||
let (body, part) = (parts.body, parts.launcher.or(parts.pack));
|
||||
out.model(sam.id, body, hull, tilt, s, false);
|
||||
out.model(sam.id + 1, part, sam.pos + side + DVec3::Y * 0.4 * s, thrown, s, false);
|
||||
if let Some(part) = part {
|
||||
out.model(sam.id + 1, part, sam.pos + side + DVec3::Y * 0.4 * s, thrown, s, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Electronic warfare, and what the enemy learns.
|
||||
//!
|
||||
//! An EW station jams the satellite navigation of the drones and cruise
|
||||
//! missiles near it (ballistic missiles and the Lancet's optics don't care):
|
||||
//! they lose their route and drift off their aim. It runs only a short while
|
||||
//! before its transmitter overheats, and while it transmits it gives itself
|
||||
//! 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).
|
||||
use super::*;
|
||||
|
||||
/// How far a station jams.
|
||||
pub const EW_RADIUS: f64 = 350.0;
|
||||
/// Heat a tick on (20 s to overheat), and cooling a tick off (45 s from hot).
|
||||
const HEAT: f64 = 1.0 / 400.0;
|
||||
const COOL: f64 = 1.0 / 900.0;
|
||||
/// After overheating it may switch on again only below this.
|
||||
const READY: f64 = 0.3;
|
||||
/// Approach sectors the enemy keeps score of.
|
||||
pub const SECTORS: usize = 12;
|
||||
|
||||
#[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)>,
|
||||
}
|
||||
|
||||
impl DroneKind {
|
||||
/// It navigates by satellite: EW throws it off.
|
||||
pub fn jammable(self) -> bool {
|
||||
matches!(self, DroneKind::Shahed | DroneKind::Geran3 | DroneKind::Gerbera | DroneKind::Kh101 | DroneKind::Kalibr)
|
||||
}
|
||||
}
|
||||
|
||||
/// The sector of a bearing (radians).
|
||||
pub fn sector(bearing: f64) -> usize {
|
||||
((bearing.rem_euclid(TAU) / TAU) * SECTORS as f64) as usize % SECTORS
|
||||
}
|
||||
|
||||
impl Sim {
|
||||
/// Switches every EW station on or off; one cooling down after overheating stays off.
|
||||
/// Returns how many are now on, and how many are still too hot.
|
||||
pub fn set_ew(&mut self, on: bool) -> (u32, u32) {
|
||||
let (mut n, mut hot) = (0, 0);
|
||||
for s in self.sams.iter_mut().filter(|s| s.kind == SamKind::Ew && s.alive()) {
|
||||
if on && s.ew_lock {
|
||||
hot += 1;
|
||||
continue;
|
||||
}
|
||||
s.ew_on = on && !s.moving();
|
||||
n += u32::from(s.ew_on);
|
||||
}
|
||||
(n, hot)
|
||||
}
|
||||
|
||||
pub fn ew_active(&self) -> bool {
|
||||
self.sams.iter().any(|s| s.ew_on)
|
||||
}
|
||||
|
||||
/// The stations' heat, and their jamming.
|
||||
pub(super) fn step_ew(&mut self, out: &mut Out) {
|
||||
let now = self.tick;
|
||||
let mut on: Vec<DVec3> = Vec::new();
|
||||
for s in self.sams.iter_mut().filter(|s| s.kind == SamKind::Ew) {
|
||||
if !s.alive() || s.moving() {
|
||||
s.ew_on = false;
|
||||
}
|
||||
if s.ew_on {
|
||||
s.heat += HEAT;
|
||||
if s.heat >= 1.0 {
|
||||
s.heat = 1.0;
|
||||
s.ew_on = false;
|
||||
s.ew_lock = true;
|
||||
out.messages.push("РЭБ: передатчик перегрет, выключен".into());
|
||||
out.sound("minecraft:block.fire.extinguish", s.pos, 2.0, 0.7);
|
||||
}
|
||||
} else {
|
||||
s.heat = (s.heat - COOL).max(0.0);
|
||||
if s.ew_lock && s.heat < READY {
|
||||
s.ew_lock = false;
|
||||
}
|
||||
}
|
||||
if s.ew_on {
|
||||
let top = s.pos + DVec3::Y * 2.6 * s.kind.spec().scale;
|
||||
on.push(s.pos);
|
||||
if now % 3 == 0 {
|
||||
out.particles("minecraft:electric_spark", top, 0.6, DVec3::ZERO, 0.2, 3);
|
||||
}
|
||||
if now % 40 == 0 {
|
||||
out.sound("minecraft:block.beacon.ambient", top, 2.0, 1.4);
|
||||
}
|
||||
// A transmitter on the air: the enemy finds it.
|
||||
if now % 20 == 0 {
|
||||
self.intel.revealed.push((s.id, s.pos, now));
|
||||
}
|
||||
}
|
||||
}
|
||||
if on.is_empty() {
|
||||
return;
|
||||
}
|
||||
for d in self.drones.iter_mut().filter(|d| d.alive() && d.kind.jammable()) {
|
||||
let Some(k) = on.iter().map(|p| 1.0 - horizontal(d.pos - *p).length() / EW_RADIUS).reduce(f64::max) else { continue };
|
||||
if k <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
// Its fix wanders: the aim drifts, the route is lost.
|
||||
d.route.clear();
|
||||
d.target += DVec3::new(self.rng.s(), 0.0, self.rng.s()) * (1.2 * k);
|
||||
d.jammed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// What the enemy's raids learned: their drones' fates by sector.
|
||||
pub(super) fn learn(&mut self, sector: usize, killed: bool) {
|
||||
if killed {
|
||||
self.intel.kills[sector % SECTORS] += 1;
|
||||
} else {
|
||||
self.intel.hits[sector % SECTORS] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// The bearing a smart raid comes from: the sector its raids got through best, a little random.
|
||||
pub(super) fn best_bearing(&mut self) -> f64 {
|
||||
let score = |i: usize| {
|
||||
let (k, h) = (self.intel.kills[i] as f64, self.intel.hits[i] as f64);
|
||||
(h + 1.0) / (k + h + 2.0)
|
||||
};
|
||||
let best = (0..SECTORS).max_by(|&a, &b| score(a).total_cmp(&score(b))).unwrap_or(0);
|
||||
// Unknown sectors are worth a look too.
|
||||
let pick = if self.rng.chance(0.3) { (self.rng.f() * SECTORS as f64) as usize } else { best };
|
||||
(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));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// During a raid: 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.
|
||||
pub(super) fn adapt(&mut self) {
|
||||
if self.tick % 40 != 0 {
|
||||
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;
|
||||
let mut n = 0usize;
|
||||
for (_, d) in self.queue.iter_mut() {
|
||||
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) < 6.0) {
|
||||
continue;
|
||||
}
|
||||
let to = if !ew.is_empty() { pick(n, &ew) } else { pick(n, &known) };
|
||||
d.target = to;
|
||||
d.route.clear();
|
||||
n += 1;
|
||||
}
|
||||
// In flight: a target system destroyed — the next known one, or keep going.
|
||||
for d in self.drones.iter_mut().filter(|d| d.alive()) {
|
||||
let aimed_at_wreck = self.sams.iter().any(|s| !s.alive() && s.pos.distance(d.target) < 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.route.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
//! 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
|
||||
//! 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::*;
|
||||
|
||||
/// A flare's life, how far it lights, and when it stops climbing.
|
||||
const FLARE_LIFE: u32 = 600;
|
||||
const FLARE_LIGHT: f64 = 90.0;
|
||||
const FLARE_CLIMB: u32 = 28;
|
||||
/// How far a searchlight's beam reaches.
|
||||
pub const BEAM: f64 = 450.0;
|
||||
/// Guns' hits at night on an unlit target.
|
||||
pub const DARK_HITS: f64 = 0.4;
|
||||
/// Ticks between a gun's own flares.
|
||||
const FLARE_EVERY: u32 = 500;
|
||||
|
||||
pub struct Flare {
|
||||
id: u32,
|
||||
pub pos: DVec3,
|
||||
vel: DVec3,
|
||||
age: u32,
|
||||
}
|
||||
|
||||
impl Sim {
|
||||
/// Fires an illumination flare from `from` towards `dir`: it climbs, bursts and hangs.
|
||||
pub fn fire_flare(&mut self, from: DVec3, dir: DVec3, out: &mut Out) {
|
||||
let id = self.id();
|
||||
let vel = dir.normalize_or(DVec3::Y) * 2.4 + DVec3::Y * 1.4;
|
||||
self.flares.push(Flare { id, pos: from, vel, age: 0 });
|
||||
out.sound("minecraft:entity.firework_rocket.launch", from, 2.0, 0.6);
|
||||
if self.flares.len() > 12 {
|
||||
self.flares.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The flares and the searchlights, and what they light; the guns' own flares.
|
||||
pub(super) fn step_lights(&mut self, out: &mut Out) {
|
||||
self.lit.clear();
|
||||
let now = self.tick;
|
||||
// Flares: up, a burst, then down slowly, swaying, lighting all round.
|
||||
let mut keep = Vec::with_capacity(self.flares.len());
|
||||
for mut f in std::mem::take(&mut self.flares) {
|
||||
f.age += 1;
|
||||
if f.age < FLARE_CLIMB {
|
||||
f.pos += f.vel;
|
||||
f.vel = f.vel * 0.94 - DVec3::Y * 0.04;
|
||||
} else {
|
||||
if f.age == FLARE_CLIMB {
|
||||
out.sound("minecraft:entity.firework_rocket.blast", f.pos, 3.0, 0.5);
|
||||
out.particles("minecraft:firework", f.pos, 1.0, DVec3::ZERO, 0.15, 30);
|
||||
}
|
||||
let sway = (f.age as f64 * 0.05 + f.id as f64).sin() * 0.04;
|
||||
f.pos += DVec3::new(sway, -0.06, sway * 0.5);
|
||||
for d in self.drones.iter().filter(|d| d.alive() && d.pos.distance(f.pos) < FLARE_LIGHT) {
|
||||
self.lit.push(d.id);
|
||||
}
|
||||
if now % 3 == 0 {
|
||||
out.particles("minecraft:firework", f.pos, 0.2, DVec3::new(0.0, -0.05, 0.0), 0.02, 1);
|
||||
}
|
||||
}
|
||||
let fade = if f.age > FLARE_LIFE - 60 { (FLARE_LIFE - f.age) as f64 / 60.0 } else { 1.0 };
|
||||
out.model(f.id, "airdef:flare", f.pos, DQuat::IDENTITY, 1.4 * fade.max(0.05), true);
|
||||
if f.age < FLARE_LIFE {
|
||||
keep.push(f);
|
||||
}
|
||||
}
|
||||
self.flares = keep;
|
||||
// What the network tracks, for the searchlights.
|
||||
let tracked: Vec<u32> = self.sams.iter().flat_map(|s| s.seen.iter().copied()).collect();
|
||||
let gun_targets: Vec<u32> = self.sams.iter().filter_map(|s| s.gun_target).collect();
|
||||
for i in 0..self.sams.len() {
|
||||
if self.sams[i].kind != SamKind::Light || !self.sams[i].alive() || self.sams[i].moving() {
|
||||
continue;
|
||||
}
|
||||
let (pos, id) = (self.sams[i].pos, self.sams[i].id);
|
||||
if !self.night {
|
||||
let sam = &mut self.sams[i];
|
||||
sam.target = None;
|
||||
sam.pitch += (0.0 - sam.pitch) * 0.05;
|
||||
continue;
|
||||
}
|
||||
// The guns' target first, else the nearest tracked drone in reach.
|
||||
let target = self
|
||||
.drones
|
||||
.iter()
|
||||
.filter(|d| d.alive() && tracked.contains(&d.id) && d.pos.distance(pos) < BEAM)
|
||||
.min_by(|a, b| {
|
||||
let key = |d: &Drone| d.pos.distance(pos) * if gun_targets.contains(&d.id) { 0.3 } else { 1.0 };
|
||||
key(a).total_cmp(&key(b))
|
||||
})
|
||||
.map(|d| (d.id, d.pos));
|
||||
let sam = &mut self.sams[i];
|
||||
let (head, _) = sam.mount();
|
||||
let (want_yaw, want_pitch, reach) = match target {
|
||||
Some((_, at)) => {
|
||||
let to = at - head;
|
||||
(heading_of(to), (to.y / to.length().max(0.01)).asin(), to.length())
|
||||
}
|
||||
// No target: a slow sweep of the sky.
|
||||
None => (now as f64 * 0.01 + id as f64, 0.9 + (now as f64 * 0.013).sin() * 0.3, 260.0),
|
||||
};
|
||||
sam.yaw = slew(sam.yaw, want_yaw, 0.12);
|
||||
sam.pitch = slew(sam.pitch, want_pitch.clamp(0.05, 1.5), 0.08);
|
||||
sam.target = target.map(|t| t.0);
|
||||
if let Some((t, _)) = target {
|
||||
self.lit.push(t);
|
||||
}
|
||||
// The beam: segments widening with distance, out to the target.
|
||||
let (head, rot) = self.sams[i].mount();
|
||||
let dir = rot * DVec3::NEG_Z;
|
||||
let mut along = 1.0;
|
||||
for k in 0..10u32 {
|
||||
if along >= reach {
|
||||
break;
|
||||
}
|
||||
let size = 0.8 + along * 0.025;
|
||||
let len = 3.0 * size;
|
||||
out.model(id + 2 + k, "airdef:beam", head + dir * (along + len / 2.0), rot, size, true);
|
||||
along += len;
|
||||
}
|
||||
}
|
||||
// Guns at night light their own target with a flare now and then.
|
||||
if self.night {
|
||||
let mut fire: Vec<(DVec3, DVec3)> = Vec::new();
|
||||
for sam in &mut self.sams {
|
||||
sam.flare_cd = sam.flare_cd.saturating_sub(1);
|
||||
if sam.kind.spec().gun.is_none() || !sam.alive() || sam.flare_cd > 0 {
|
||||
continue;
|
||||
}
|
||||
let Some(t) = sam.gun_target.and_then(|t| self.drones.iter().find(|d| d.id == t)) else { continue };
|
||||
if self.lit.contains(&t.id) || self.flares.iter().any(|f| f.pos.distance(t.pos) < FLARE_LIGHT) {
|
||||
continue;
|
||||
}
|
||||
sam.flare_cd = FLARE_EVERY;
|
||||
let from = sam.pos + DVec3::Y * 2.0;
|
||||
fire.push((from, (t.pos + DVec3::Y * 25.0) - from));
|
||||
}
|
||||
for (from, dir) in fire {
|
||||
self.fire_flare(from, dir, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//! Logistics: once a depot stands, magazines are no longer refilled from
|
||||
//! nowhere. A system out of missiles waits; one within reach of a depot
|
||||
//! reloads from its stock; the reload trucks fill up at a depot and drive
|
||||
//! 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 stands (without one, magazines refill as before).
|
||||
pub fn logistics(&self) -> bool {
|
||||
self.sams.iter().any(|s| s.kind == SamKind::Depot && s.alive())
|
||||
}
|
||||
|
||||
/// Reloads from depots near the systems, and the trucks' rounds.
|
||||
pub(super) fn step_supply(&mut self, out: &mut Out) {
|
||||
if !self.logistics() {
|
||||
for s in &mut self.sams {
|
||||
s.empty = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
let now = self.tick;
|
||||
// Systems by a depot reload from it.
|
||||
for i in 0..self.sams.len() {
|
||||
if !self.sams[i].empty || !self.sams[i].alive() {
|
||||
continue;
|
||||
}
|
||||
let pos = self.sams[i].pos;
|
||||
if let Some(d) = self
|
||||
.sams
|
||||
.iter()
|
||||
.position(|d| d.kind == SamKind::Depot && d.alive() && d.cargo > 0 && d.pos.distance(pos) < DEPOT_REACH)
|
||||
{
|
||||
self.sams[d].cargo -= 1;
|
||||
self.reload_now(i, out);
|
||||
}
|
||||
}
|
||||
// The trucks: a job each, when idle.
|
||||
for t in 0..self.sams.len() {
|
||||
let truck = &self.sams[t];
|
||||
if truck.kind != SamKind::Tzm || !truck.alive() || truck.moving() || now % 10 != t as u64 % 10 {
|
||||
continue;
|
||||
}
|
||||
let at = truck.pos;
|
||||
// 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)
|
||||
{
|
||||
if self.sams[j].pos.distance(at) <= HANDOVER {
|
||||
if self.sams[j].kind == SamKind::Depot {
|
||||
let take = TRUCK_LOAD.saturating_sub(self.sams[t].cargo).min(self.sams[j].cargo);
|
||||
self.sams[j].cargo -= take;
|
||||
self.sams[t].cargo += take;
|
||||
out.sound("minecraft:block.piston.extend", at, 1.5, 0.8);
|
||||
} else if self.sams[j].empty && self.sams[t].cargo > 0 {
|
||||
self.sams[t].cargo -= 1;
|
||||
self.reload_now(j, out);
|
||||
}
|
||||
self.sams[t].job = None;
|
||||
} else if self.sams[j].alive() && (self.sams[j].empty || self.sams[j].kind == SamKind::Depot) {
|
||||
continue; // still on its way (a new order follows when it arrives)
|
||||
} else {
|
||||
self.sams[t].job = None;
|
||||
}
|
||||
}
|
||||
// A new job: the nearest waiting system no other truck is driving to, or a depot to load at.
|
||||
let taken: Vec<u32> = self.sams.iter().filter(|s| s.kind == SamKind::Tzm).filter_map(|s| s.job).collect();
|
||||
let next = if self.sams[t].cargo > 0 {
|
||||
self.sams
|
||||
.iter()
|
||||
.filter(|s| s.empty && s.alive() && !s.moving() && !taken.contains(&s.id))
|
||||
.min_by(|a, b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
.map(|s| (s.id, s.pos))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let next = next.or_else(|| {
|
||||
(self.sams[t].cargo < TRUCK_LOAD)
|
||||
.then(|| {
|
||||
self.sams
|
||||
.iter()
|
||||
.filter(|d| d.kind == SamKind::Depot && d.alive() && d.cargo > 0)
|
||||
.min_by(|a, b| a.pos.distance(at).total_cmp(&b.pos.distance(at)))
|
||||
.map(|d| (d.id, d.pos))
|
||||
})
|
||||
.flatten()
|
||||
});
|
||||
if let Some((id, to)) = next {
|
||||
self.sams[t].job = Some(id);
|
||||
// Stop beside it, not on it.
|
||||
let side = horizontal(at - to).normalize_or(DVec3::X) * (HANDOVER * 0.6);
|
||||
if to.distance(at) > HANDOVER {
|
||||
self.order_move(t, to + side);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// System `i` gets a reload: its magazine fills after its reload time.
|
||||
fn reload_now(&mut self, i: usize, out: &mut Out) {
|
||||
let sam = &mut self.sams[i];
|
||||
sam.empty = false;
|
||||
sam.reloading = sam.kind.spec().reload.max(1);
|
||||
out.sound("minecraft:block.piston.extend", sam.pos, 2.0, 0.6);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,14 @@ mod damage;
|
||||
mod raid;
|
||||
pub mod record;
|
||||
mod sight;
|
||||
mod ew;
|
||||
mod lights;
|
||||
mod logistics;
|
||||
mod orders;
|
||||
mod units;
|
||||
pub use ew::EW_RADIUS;
|
||||
pub use units::{Family, SamKind};
|
||||
use units::Muzzle;
|
||||
pub use raid::{Approach, Aim, RaidOrder};
|
||||
|
||||
pub const TAU: f64 = std::f64::consts::TAU;
|
||||
@@ -645,6 +653,10 @@ pub struct Drone {
|
||||
route: Vec<DVec3>,
|
||||
/// Hugs the land all the way, under the radars.
|
||||
low: bool,
|
||||
/// Its navigation is being jammed.
|
||||
pub jammed: bool,
|
||||
/// The approach sector it came from (the enemy keeps score).
|
||||
sector: u8,
|
||||
}
|
||||
|
||||
impl Drone {
|
||||
@@ -667,6 +679,12 @@ pub enum MissileKind {
|
||||
M57e6,
|
||||
M48n6,
|
||||
M77n6,
|
||||
IrisT,
|
||||
Amraam,
|
||||
M9m317,
|
||||
M9m338,
|
||||
/// Not a missile: an interceptor drone, slow and cheap, that rams Shaheds.
|
||||
P1sun,
|
||||
}
|
||||
|
||||
struct MissileSpec {
|
||||
@@ -695,6 +713,9 @@ impl MissileKind {
|
||||
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 {
|
||||
@@ -755,6 +776,77 @@ impl MissileKind {
|
||||
flame: 1.6,
|
||||
rear: 32.0,
|
||||
},
|
||||
MissileKind::IrisT => &MissileSpec {
|
||||
model: "airdef:pac3",
|
||||
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:pac3",
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,120 +864,6 @@ pub struct Missile {
|
||||
|
||||
// ---------------------------------------------------------------- air defence
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum SamKind {
|
||||
Patriot,
|
||||
Gepard,
|
||||
Pantsir,
|
||||
S400,
|
||||
S500,
|
||||
}
|
||||
|
||||
struct MissileLoad {
|
||||
kind: MissileKind,
|
||||
magazine: u32,
|
||||
min: f64,
|
||||
max: f64,
|
||||
/// Ticks between launches.
|
||||
interval: u32,
|
||||
}
|
||||
|
||||
struct GunSpec {
|
||||
range: f64,
|
||||
/// Starts tracking from here.
|
||||
track: f64,
|
||||
/// Chance per burst at point blank.
|
||||
p: f64,
|
||||
bursts: u32,
|
||||
/// Shell speed for the lead, blocks a tick.
|
||||
shell: f64,
|
||||
slew: (f64, f64),
|
||||
}
|
||||
|
||||
pub struct SamSpec {
|
||||
pub name: &'static str,
|
||||
pub key: &'static str,
|
||||
missile: Option<MissileLoad>,
|
||||
gun: Option<GunSpec>,
|
||||
reload: u32,
|
||||
scale: f64,
|
||||
/// Where it points its launcher at rest (pitch, radians).
|
||||
rest_pitch: f64,
|
||||
/// Which classes of target it engages unless told otherwise.
|
||||
pub roles: u8,
|
||||
}
|
||||
|
||||
impl SamKind {
|
||||
pub const ALL: [SamKind; 5] = [SamKind::Patriot, SamKind::Gepard, SamKind::Pantsir, SamKind::S400, SamKind::S500];
|
||||
pub fn spec(self) -> &'static SamSpec {
|
||||
match self {
|
||||
SamKind::Patriot => &SamSpec {
|
||||
name: "Patriot",
|
||||
key: "patriot",
|
||||
missile: Some(MissileLoad { kind: MissileKind::Pac3, magazine: 4, min: 10.0, max: 300.0, interval: 16 }),
|
||||
gun: None,
|
||||
reload: 220,
|
||||
scale: 1.3,
|
||||
rest_pitch: 0.66,
|
||||
roles: 30,
|
||||
},
|
||||
SamKind::Gepard => &SamSpec {
|
||||
name: "Гепард",
|
||||
key: "gepard",
|
||||
missile: None,
|
||||
gun: Some(GunSpec { range: 90.0, track: 130.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,
|
||||
},
|
||||
SamKind::Pantsir => &SamSpec {
|
||||
name: "Панцирь-С1",
|
||||
key: "pantsir",
|
||||
missile: Some(MissileLoad { kind: MissileKind::M57e6, magazine: 12, min: 25.0, max: 150.0, interval: 14 }),
|
||||
gun: Some(GunSpec { range: 60.0, track: 90.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,
|
||||
},
|
||||
SamKind::S400 => &SamSpec {
|
||||
name: "С-400",
|
||||
key: "s400",
|
||||
missile: Some(MissileLoad { kind: MissileKind::M48n6, magazine: 4, min: 30.0, max: 480.0, interval: 20 }),
|
||||
gun: None,
|
||||
reload: 420,
|
||||
scale: 1.5,
|
||||
rest_pitch: HALF_PI - 0.02,
|
||||
roles: 30,
|
||||
},
|
||||
SamKind::S500 => &SamSpec {
|
||||
name: "С-500",
|
||||
key: "s500",
|
||||
missile: Some(MissileLoad { kind: MissileKind::M77n6, magazine: 4, min: 40.0, max: 700.0, interval: 20 }),
|
||||
gun: None,
|
||||
reload: 520,
|
||||
scale: 1.6,
|
||||
rest_pitch: HALF_PI - 0.02,
|
||||
roles: 24,
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn name(self) -> &'static str {
|
||||
self.spec().name
|
||||
}
|
||||
pub fn key(self) -> &'static str {
|
||||
self.spec().key
|
||||
}
|
||||
pub fn from_key(key: &str) -> Option<Self> {
|
||||
Self::ALL.into_iter().find(|k| k.key() == key)
|
||||
}
|
||||
pub fn range(self) -> f64 {
|
||||
let s = self.spec();
|
||||
s.missile.as_ref().map_or(0.0, |m| m.max).max(s.gun.as_ref().map_or(0.0, |g| g.range))
|
||||
}
|
||||
}
|
||||
|
||||
/// What a system is doing, for the monitor.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum SamState {
|
||||
@@ -898,6 +876,12 @@ pub enum SamState {
|
||||
Reload,
|
||||
Hold,
|
||||
Destroyed,
|
||||
/// A move order: packing up, on the march, deploying again.
|
||||
Packing,
|
||||
Moving,
|
||||
Deploying,
|
||||
/// Out of missiles, waiting for a reload truck (logistics on: a depot stands).
|
||||
Waiting,
|
||||
}
|
||||
|
||||
impl SamState {
|
||||
@@ -912,6 +896,10 @@ impl SamState {
|
||||
SamState::Reload => "ПЕРЕЗАРЯДКА",
|
||||
SamState::Hold => "ОГОНЬ ЗАПРЕЩЁН",
|
||||
SamState::Destroyed => "УНИЧТОЖЕН",
|
||||
SamState::Packing => "СВЁРТЫВАНИЕ",
|
||||
SamState::Moving => "НА МАРШЕ",
|
||||
SamState::Deploying => "РАЗВЁРТЫВАНИЕ",
|
||||
SamState::Waiting => "ЖДЁТ БК",
|
||||
}
|
||||
}
|
||||
pub fn color(self) -> u32 {
|
||||
@@ -924,6 +912,8 @@ impl SamState {
|
||||
SamState::Reload => 0x80A0FF,
|
||||
SamState::Hold => 0xA0A0A0,
|
||||
SamState::Destroyed => 0xFF2020,
|
||||
SamState::Packing | SamState::Moving | SamState::Deploying => 0xC0A0FF,
|
||||
SamState::Waiting => 0xFF9040,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,8 +949,28 @@ pub struct Sam {
|
||||
pub health: f64,
|
||||
/// Ticks its wreck still burns.
|
||||
burn: u32,
|
||||
/// The drones its radar holds this tick.
|
||||
/// The drones it has tracks of this tick: its own radar's and the network's.
|
||||
pub seen: Vec<u32>,
|
||||
/// The drones its own radar (or optics) holds: guns need these.
|
||||
pub own: Vec<u32>,
|
||||
/// What it just shot down, where, and till when its camera stays on it.
|
||||
pub kill_view: Option<(u32, DVec3, u64)>,
|
||||
/// A move order's destination, and ticks left packing up or deploying.
|
||||
pub dest: Option<DVec3>,
|
||||
march: u32,
|
||||
/// A depot's reloads in stock; a reload truck's on board.
|
||||
pub cargo: u32,
|
||||
/// A reload truck's job: the system it is driving to, or a depot.
|
||||
job: Option<u32>,
|
||||
/// Out of ammunition, waiting for a reload (logistics on).
|
||||
pub empty: bool,
|
||||
/// An EW station: jamming, and its transmitter's heat (0 cold, 1 forced off).
|
||||
pub ew_on: bool,
|
||||
pub heat: f64,
|
||||
/// Overheated: off until it cools.
|
||||
pub ew_lock: bool,
|
||||
/// Ticks till a gun fires its next illumination flare.
|
||||
flare_cd: u32,
|
||||
}
|
||||
|
||||
impl Sam {
|
||||
@@ -991,6 +1001,21 @@ impl Sam {
|
||||
health: 1.0,
|
||||
burn: 0,
|
||||
seen: Vec::new(),
|
||||
own: Vec::new(),
|
||||
kill_view: None,
|
||||
dest: None,
|
||||
march: 0,
|
||||
cargo: match kind {
|
||||
SamKind::Depot => 30,
|
||||
SamKind::Tzm => 2,
|
||||
_ => 0,
|
||||
},
|
||||
job: None,
|
||||
empty: false,
|
||||
ew_on: false,
|
||||
heat: 0.0,
|
||||
ew_lock: false,
|
||||
flare_cd: 0,
|
||||
}
|
||||
}
|
||||
pub fn alive(&self) -> bool {
|
||||
@@ -1000,67 +1025,29 @@ impl Sam {
|
||||
fn fitness(&self) -> f64 {
|
||||
0.45 + 0.55 * self.health.clamp(0.0, 1.0)
|
||||
}
|
||||
fn base(&self) -> DQuat {
|
||||
DQuat::from_rotation_y(self.heading)
|
||||
}
|
||||
fn scale(&self) -> f64 {
|
||||
self.kind.spec().scale
|
||||
}
|
||||
/// Where shells and missiles leave: the centre of the turret or the
|
||||
/// launcher's pivot, and the rotation they leave with.
|
||||
fn mount(&self) -> (DVec3, DQuat) {
|
||||
let s = self.scale();
|
||||
let base = self.base();
|
||||
match self.kind {
|
||||
SamKind::Patriot => (
|
||||
self.pos + base * DVec3::new(0.0, 0.69 * s, 1.2 * s),
|
||||
DQuat::from_rotation_y(self.yaw) * DQuat::from_rotation_x(self.pitch),
|
||||
),
|
||||
SamKind::Gepard => (
|
||||
self.pos + DVec3::new(0.0, 1.55 * s, 0.0) + base * DVec3::new(0.0, 0.0, 0.25 * s),
|
||||
DQuat::from_rotation_y(self.yaw) * DQuat::from_rotation_x(self.pitch),
|
||||
),
|
||||
SamKind::Pantsir => (
|
||||
self.pos + DVec3::new(0.0, 1.35 * s, 0.0) + base * DVec3::new(0.0, 0.0, 0.69 * s),
|
||||
DQuat::from_rotation_y(self.yaw) * DQuat::from_rotation_x(self.pitch),
|
||||
),
|
||||
SamKind::S400 | SamKind::S500 => (
|
||||
self.pos + base * DVec3::new(0.0, 0.7 * s, 1.45 * s),
|
||||
base * DQuat::from_rotation_x(self.pitch),
|
||||
),
|
||||
}
|
||||
}
|
||||
/// Where missile `n` of the magazine leaves (world), and which way.
|
||||
fn muzzle(&self, n: u32) -> (DVec3, DVec3) {
|
||||
let (at, rot) = self.mount();
|
||||
let s = self.scale();
|
||||
let local = match self.kind {
|
||||
SamKind::Patriot => DVec3::new(
|
||||
if n % 2 == 0 { -0.42 } else { 0.42 },
|
||||
0.625 + if n % 4 < 2 { 0.28 } else { -0.28 },
|
||||
-2.8,
|
||||
),
|
||||
SamKind::Pantsir => {
|
||||
let side = if n % 2 == 0 { -0.94 } else { 0.94 };
|
||||
let row = (n / 2) % 6;
|
||||
DVec3::new(side + ((row % 3) as f64 - 1.0) * 0.15, 0.1 + if row < 3 { 0.15 } else { -0.15 }, -1.5)
|
||||
}
|
||||
SamKind::S400 => {
|
||||
let i = n % 4;
|
||||
// From the pivot (rear bottom of the pack) to the top of tube i.
|
||||
DVec3::new(if i % 2 == 0 { -0.25 } else { 0.25 }, if i < 2 { 0.22 } else { 0.72 }, -3.0)
|
||||
}
|
||||
SamKind::S500 => DVec3::new(if n % 2 == 0 { -0.33 } else { 0.33 }, 0.3, -3.0),
|
||||
SamKind::Gepard => DVec3::ZERO,
|
||||
};
|
||||
(at + rot * local * s, rot * DVec3::NEG_Z)
|
||||
}
|
||||
pub fn status(&self) -> String {
|
||||
let spec = self.kind.spec();
|
||||
let mut text = spec.name.to_string();
|
||||
if !self.alive() {
|
||||
return text + " УНИЧТОЖЕН";
|
||||
}
|
||||
if self.moving() {
|
||||
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::Ew => {
|
||||
let state = if self.ew_on { "ГЛУШИТ" } else if self.ew_lock { "остывает" } else { "готова" };
|
||||
return format!("{text}: {state}, нагрев {:.0}%", self.heat * 100.0);
|
||||
}
|
||||
SamKind::Radar => return format!("{text}: целей {}", self.own.len()),
|
||||
SamKind::Light => return format!("{text}{}", if self.target.is_some() { ": в луче цель" } else { "" }),
|
||||
_ => {}
|
||||
}
|
||||
if self.empty && self.missiles == 0 {
|
||||
text += " ЖДЁТ БК";
|
||||
}
|
||||
if self.health < 1.0 {
|
||||
text += &format!(" ({:.0}%)", self.health * 100.0);
|
||||
}
|
||||
@@ -1129,6 +1116,13 @@ pub struct Sim {
|
||||
rec: Option<record::Recording>,
|
||||
pub replays: Vec<record::Recording>,
|
||||
recordings: u32,
|
||||
/// What the enemy has learned.
|
||||
intel: ew::Intel,
|
||||
/// Night: guns need light (the mod sets it from the game's clock).
|
||||
pub night: bool,
|
||||
/// Illumination flares in the sky, and the drones lit this tick.
|
||||
pub flares: Vec<lights::Flare>,
|
||||
lit: Vec<u32>,
|
||||
}
|
||||
|
||||
/// Ids: objects get multiples of 16, their parts the low bits.
|
||||
@@ -1162,6 +1156,10 @@ impl Sim {
|
||||
rec: None,
|
||||
replays: Vec::new(),
|
||||
recordings: 0,
|
||||
intel: ew::Intel::default(),
|
||||
night: false,
|
||||
flares: Vec::new(),
|
||||
lit: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn id(&mut self) -> u32 {
|
||||
@@ -1172,6 +1170,12 @@ impl Sim {
|
||||
/// What a system's camera follows: its guns' or missiles' target, else what its missiles in
|
||||
/// flight are after.
|
||||
pub fn watched(&self, sam: &Sam) -> Option<&Drone> {
|
||||
// A kill stays in view a few seconds: the wreck falling, before the next target.
|
||||
if let Some((id, _, until)) = sam.kill_view
|
||||
&& self.tick < until
|
||||
{
|
||||
return self.drones.iter().find(|d| d.id == id);
|
||||
}
|
||||
let id = sam
|
||||
.gun_target
|
||||
.or(sam.target)
|
||||
@@ -1179,6 +1183,22 @@ impl Sim {
|
||||
self.drones.iter().find(|d| d.id == id)
|
||||
}
|
||||
|
||||
/// Where a system's camera looks: what it watches, or where its last kill was while that
|
||||
/// stays in view (the wreck may be gone).
|
||||
pub fn focus(&self, sam: &Sam) -> Option<DVec3> {
|
||||
if let Some((_, at, until)) = sam.kill_view
|
||||
&& self.tick < until
|
||||
{
|
||||
return Some(self.watched(sam).map_or(at, |d| d.pos));
|
||||
}
|
||||
self.watched(sam).map(|d| d.pos)
|
||||
}
|
||||
|
||||
/// Whether a system's camera is showing its kill.
|
||||
pub fn showing_kill(&self, sam: &Sam) -> bool {
|
||||
sam.kill_view.is_some_and(|(_, _, until)| self.tick < until)
|
||||
}
|
||||
|
||||
pub fn nearest_target(&self, to: DVec3) -> Option<DVec3> {
|
||||
self.targets.iter().copied().min_by(|a, b| a.distance_squared(to).total_cmp(&b.distance_squared(to)))
|
||||
}
|
||||
@@ -1208,6 +1228,8 @@ impl Sim {
|
||||
engaged: 0,
|
||||
route: Vec::new(),
|
||||
low: false,
|
||||
jammed: false,
|
||||
sector: 0,
|
||||
});
|
||||
self.launched += 1;
|
||||
}
|
||||
@@ -1253,10 +1275,15 @@ impl Sim {
|
||||
pub fn step(&mut self, ground: &mut Ground, player: Option<DVec3>, out: &mut Out) {
|
||||
self.tick += 1;
|
||||
self.plan_step(out);
|
||||
self.adapt();
|
||||
self.release_queue();
|
||||
self.step_drones(ground, player, out);
|
||||
self.step_later(ground, player, out);
|
||||
self.step_moves(ground, out);
|
||||
self.step_supply(out);
|
||||
self.step_ew(out);
|
||||
self.update_sight(ground);
|
||||
self.step_lights(out);
|
||||
self.step_sams(out);
|
||||
self.step_missiles(out);
|
||||
self.step_fx(ground, out);
|
||||
@@ -1384,6 +1411,10 @@ impl Sim {
|
||||
if d.flight == Flight::Cruise && d.route.len() <= 1 && horizontal(d.target - d.pos).length() < spec.dive {
|
||||
d.flight = Flight::Dive;
|
||||
}
|
||||
// An interceptor coming: drones drop to the treetops (radars lose them sooner).
|
||||
if d.engaged > 0 && matches!(spec.class, Class::Drone | Class::Jet) {
|
||||
d.low = true;
|
||||
}
|
||||
// A planned route: the next point, then the next.
|
||||
// Reached when near (as near as it can turn), or passed: behind it and close.
|
||||
let radius = (spec.speed / spec.turn.max(0.01)).max(24.0);
|
||||
@@ -1453,6 +1484,9 @@ impl Sim {
|
||||
self.log(pos, record::EventKind::Split { drone });
|
||||
}
|
||||
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);
|
||||
}
|
||||
let power = match tnt {
|
||||
0 => 0,
|
||||
1 | 2 => 1,
|
||||
@@ -1483,6 +1517,8 @@ impl Sim {
|
||||
engaged: 0,
|
||||
route: Vec::new(),
|
||||
low: false,
|
||||
jammed: false,
|
||||
sector: 0,
|
||||
});
|
||||
}
|
||||
for d in &self.drones {
|
||||
@@ -1597,6 +1633,12 @@ impl Sim {
|
||||
if !self.drones[idx].alive() {
|
||||
return;
|
||||
}
|
||||
let sector = self.drones[idx].sector as usize;
|
||||
self.learn(sector, true);
|
||||
let until = self.tick + 70;
|
||||
if let Some(s) = self.sams.iter_mut().find(|s| s.id == by) {
|
||||
s.kill_view = Some((self.drones[idx].id, self.drones[idx].pos, until));
|
||||
}
|
||||
if let Some((i, s)) = self.sams.iter().enumerate().find(|(_, s)| s.id == by) {
|
||||
let (drone, kind, pos) = (self.drones[idx].id, self.drones[idx].kind, self.drones[idx].pos);
|
||||
let kill = record::EventKind::Kill { drone, kind, sam: by, sam_kind: s.kind, sam_index: i, missile };
|
||||
@@ -1630,13 +1672,14 @@ impl Sim {
|
||||
fn step_sams(&mut self, out: &mut Out) {
|
||||
if self.hold {
|
||||
for i in 0..self.sams.len() {
|
||||
if self.sams[i].alive() {
|
||||
if self.sams[i].alive() && !self.sams[i].moving() {
|
||||
self.sams[i].state = SamState::Hold;
|
||||
}
|
||||
self.draw_sam(i, out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let logistics = self.logistics();
|
||||
let mut launches: Vec<(MissileKind, DVec3, DVec3, u32, u32)> = Vec::new();
|
||||
let mut gun_hits: Vec<(u32, u32)> = Vec::new();
|
||||
let mut tracers: Vec<(u32, DVec3, DVec3, u32)> = Vec::new();
|
||||
@@ -1666,10 +1709,21 @@ impl Sim {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.sams[i].moving() {
|
||||
continue;
|
||||
}
|
||||
if !self.sams[i].kind.armed() {
|
||||
let sam = &mut self.sams[i];
|
||||
sam.state = SamState::Search;
|
||||
sam.target = None;
|
||||
sam.gun_target = None;
|
||||
continue;
|
||||
}
|
||||
let pos = self.sams[i].pos;
|
||||
let me = self.sams[i].id;
|
||||
let roles = self.sams[i].roles;
|
||||
let seen = self.sams[i].seen.clone();
|
||||
let own = self.sams[i].own.clone();
|
||||
let allowed = |d: &Drone| d.alive() && roles & d.kind.spec().class.bit() != 0 && seen.contains(&d.id);
|
||||
// Interceptors a target has or will have: in flight, and other systems' launchers on it.
|
||||
let claimed = |d: &Drone| {
|
||||
@@ -1699,7 +1753,7 @@ impl Sim {
|
||||
self.sams[i].gun_target = self
|
||||
.drones
|
||||
.iter()
|
||||
.filter(|d| allowed(d) && d.pos.distance(pos) < gun.track)
|
||||
.filter(|d| allowed(d) && own.contains(&d.id) && d.pos.distance(pos) < gun.track)
|
||||
.map(|d| {
|
||||
let busy = 1.0 + 2.0 * guns_on(d) + if d.engaged > 0 { 1.5 } else { 0.0 };
|
||||
(d.pos.distance(pos) * busy, d.id)
|
||||
@@ -1713,10 +1767,16 @@ impl Sim {
|
||||
sam.launched = sam.launched.saturating_sub(1);
|
||||
let Some(aim) = aim else {
|
||||
// Out of missiles and nothing to shoot: reload now, guns or not.
|
||||
if spec.missile.is_some() && sam.missiles == 0 && sam.reloading == 0 {
|
||||
sam.reloading = spec.reload;
|
||||
if spec.missile.is_some() && sam.missiles == 0 && sam.reloading == 0 && !sam.empty {
|
||||
if logistics { sam.empty = true } else { sam.reloading = spec.reload }
|
||||
}
|
||||
sam.state = if sam.reloading > 0 { SamState::Reload } else { SamState::Search };
|
||||
sam.state = if sam.reloading > 0 {
|
||||
SamState::Reload
|
||||
} else if sam.empty {
|
||||
SamState::Waiting
|
||||
} else {
|
||||
SamState::Search
|
||||
};
|
||||
sam.yaw = slew(sam.yaw, sam.heading, 0.03);
|
||||
sam.pitch += (spec.rest_pitch - sam.pitch) * 0.05;
|
||||
continue;
|
||||
@@ -1729,9 +1789,12 @@ impl Sim {
|
||||
let want_pitch = (to.y / to.length().max(0.01)).asin().clamp(-0.1, 1.45);
|
||||
let (yaw_rate, pitch_rate) = spec.gun.as_ref().map_or((0.05, 0.05), |g| g.slew);
|
||||
let (yaw_rate, pitch_rate) = (yaw_rate * fit, pitch_rate * fit);
|
||||
match sam.kind {
|
||||
SamKind::S400 | SamKind::S500 => {} // vertical launch: no aiming
|
||||
SamKind::Patriot => sam.yaw = slew(sam.yaw, want_yaw, 0.12 * fit),
|
||||
let cells = matches!(spec.muzzle, Muzzle::Cells);
|
||||
match spec.family {
|
||||
// Vertical launch: no aiming (the Tor's turret turns its radar to the target).
|
||||
Family::Vertical if cells => sam.yaw = slew(sam.yaw, want_yaw, 0.15 * fit),
|
||||
Family::Vertical => {}
|
||||
Family::Trainable => sam.yaw = slew(sam.yaw, want_yaw, 0.12 * fit),
|
||||
_ => {
|
||||
sam.yaw = slew(sam.yaw, want_yaw, yaw_rate);
|
||||
sam.pitch = slew(sam.pitch, want_pitch, pitch_rate);
|
||||
@@ -1739,9 +1802,11 @@ impl Sim {
|
||||
}
|
||||
let aim_error = angle_diff(sam.yaw, want_yaw).abs()
|
||||
+ if spec.gun.is_some() { (want_pitch - sam.pitch).abs() } else { 0.0 };
|
||||
let vertical = matches!(sam.kind, SamKind::S400 | SamKind::S500);
|
||||
let vertical = spec.family == Family::Vertical;
|
||||
sam.state = if sam.reloading > 0 {
|
||||
SamState::Reload
|
||||
} else if sam.empty && sam.missiles == 0 && sam.bursts == 0 {
|
||||
SamState::Waiting
|
||||
} else if sam.launched > 0 {
|
||||
SamState::Launch
|
||||
} else if sam.flash > 0 {
|
||||
@@ -1763,7 +1828,7 @@ impl Sim {
|
||||
sam.flash = 2;
|
||||
sam.recoil = 1.0;
|
||||
if sam.bursts == 0 && sam.missiles == 0 {
|
||||
sam.reloading = spec.reload;
|
||||
if logistics { sam.empty = true } else { sam.reloading = spec.reload }
|
||||
}
|
||||
let (_, rot) = sam.mount();
|
||||
let s = spec.scale;
|
||||
@@ -1787,7 +1852,9 @@ impl Sim {
|
||||
let event = if sam.kind == SamKind::Pantsir { "airdef:gun.pantsir" } else { "airdef:gun.gepard" };
|
||||
out.sound(event, mount, 1.0, 0.95 + self.rng.f() as f32 * 0.1);
|
||||
}
|
||||
let p = gun.p * fit / t.kind.spec().stealth * (1.0 - dist / (gun.range * 1.3)).max(0.35);
|
||||
// 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 };
|
||||
let p = gun.p * fit * dark / t.kind.spec().stealth * (1.0 - dist / (gun.range * 1.3)).max(0.35);
|
||||
if self.rng.chance(p) {
|
||||
gun_hits.push((gt, sam.id));
|
||||
} else {
|
||||
@@ -1799,13 +1866,13 @@ impl Sim {
|
||||
// Missiles.
|
||||
if let (Some(load), Some(mt)) = (&spec.missile, sam.target) {
|
||||
let ready = sam.cooldown == 0 && sam.reloading == 0 && sam.missiles > 0;
|
||||
let aligned = match sam.kind {
|
||||
SamKind::Patriot => {
|
||||
let aligned = match spec.family {
|
||||
Family::Trainable => {
|
||||
let t = self.drones.iter().find(|d| d.id == mt);
|
||||
// The missile turns after its boost: the launcher needn't be exact.
|
||||
t.is_some_and(|t| angle_diff(sam.yaw, heading_of(t.pos - mount)).abs() < 0.45)
|
||||
}
|
||||
SamKind::Pantsir => aim.id == mt && aim_error < 0.2,
|
||||
Family::Turret => aim.id == mt && aim_error < 0.2,
|
||||
_ => true,
|
||||
};
|
||||
if ready && aligned {
|
||||
@@ -1819,12 +1886,16 @@ impl Sim {
|
||||
sam.fired += 1;
|
||||
sam.cooldown = load.interval;
|
||||
if sam.missiles == 0 && (spec.gun.is_none() || sam.bursts == 0) {
|
||||
sam.reloading = spec.reload;
|
||||
if logistics { sam.empty = true } else { sam.reloading = spec.reload }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (kind, muzzle, dir, target, sam) in launches {
|
||||
// A launch gives the system away.
|
||||
if let Some(s) = self.sams.iter().find(|s| s.id == sam) {
|
||||
self.intel.revealed.push((sam, s.pos, self.tick));
|
||||
}
|
||||
let spec = kind.spec();
|
||||
let id = self.id();
|
||||
let v0 = if spec.cold > 0 { 0.7 } else { 0.8 };
|
||||
@@ -1858,69 +1929,6 @@ impl Sim {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sam(&mut self, i: usize, out: &mut Out) {
|
||||
if !self.sams[i].alive() {
|
||||
self.draw_wreck(i, out);
|
||||
return;
|
||||
}
|
||||
let sam = &self.sams[i];
|
||||
let base = sam.base();
|
||||
let s = sam.scale();
|
||||
let up = DVec3::Y * 0.5 * s;
|
||||
let id = sam.id;
|
||||
let (mount, rot) = sam.mount();
|
||||
let turret = DQuat::from_rotation_y(sam.yaw);
|
||||
match sam.kind {
|
||||
SamKind::Patriot => {
|
||||
out.model(id, "airdef:patriot_trailer", sam.pos + up, base, s, false);
|
||||
out.model(id + 1, "airdef:patriot_truck", sam.pos + up + base * DVec3::new(0.0, 0.0, -3.0 * s), base, s, false);
|
||||
// The launcher's pivot is its back bottom edge (0, -0.5, 1.375).
|
||||
out.model(id + 2, "airdef:patriot_launcher", mount - rot * DVec3::new(0.0, -0.5, 1.375) * s, rot, s, false);
|
||||
// Its radar stands right behind the trailer, facing the same way.
|
||||
out.model(id + 3, "airdef:patriot_radar", sam.pos + up + base * DVec3::new(0.0, 0.0, 3.6 * s), base, s, false);
|
||||
}
|
||||
SamKind::Gepard => {
|
||||
out.model(id, "airdef:gepard_hull", sam.pos + up, base, s, false);
|
||||
out.model(id + 1, "airdef:gepard_turret", mount + DVec3::Y * 0.12 * s, turret, s, false);
|
||||
let recoil = rot * DVec3::new(0.0, 0.0, 0.15 * sam.recoil * s);
|
||||
out.model(id + 2, "airdef:gepard_guns", mount + recoil, rot, s, false);
|
||||
out.model(id + 3, "airdef:gepard_dish", mount + turret * DVec3::new(0.0, 0.95 * s, 0.6 * s), DQuat::from_rotation_y(sam.dish), s, false);
|
||||
}
|
||||
SamKind::Pantsir => {
|
||||
out.model(id, "airdef:pantsir_truck", sam.pos + up, base, s, false);
|
||||
out.model(id + 1, "airdef:pantsir_turret", mount - DVec3::Y * 0.2 * s, turret, s, false);
|
||||
let recoil = rot * DVec3::new(0.0, 0.0, 0.1 * sam.recoil * s);
|
||||
out.model(id + 2, "airdef:pantsir_pack", mount + DVec3::Y * 0.1 * s + recoil, rot, s, false);
|
||||
out.model(id + 3, "airdef:pantsir_dish", mount + turret * DVec3::new(0.0, 0.85 * s, 0.3 * s), DQuat::from_rotation_y(sam.dish) * DQuat::from_rotation_x(0.3), s, false);
|
||||
}
|
||||
SamKind::S400 | SamKind::S500 => {
|
||||
let (chassis, tubes) = if sam.kind == SamKind::S400 {
|
||||
("airdef:s400_chassis", "airdef:s400_tubes")
|
||||
} else {
|
||||
("airdef:s500_chassis", "airdef:s500_tubes")
|
||||
};
|
||||
out.model(id, chassis, sam.pos + up, base, s, false);
|
||||
// The pack's pivot is its back bottom edge (0, -0.5, 1.5).
|
||||
out.model(id + 1, tubes, mount - rot * DVec3::new(0.0, -0.5, 1.5) * s, rot, s, false);
|
||||
// The battery's search radar behind it, turning.
|
||||
let radar = sam.pos + DVec3::Y * 0.5 * s + base * DVec3::new(0.0, 0.0, 4.2 * s);
|
||||
out.model(id + 2, "airdef:radar_trailer", radar, base, s, false);
|
||||
out.model(id + 3, "airdef:radar_panel", radar + DVec3::Y * 0.9 * s, DQuat::from_rotation_y(sam.dish * 0.6) * DQuat::from_rotation_x(-0.3), s * 0.9, false);
|
||||
}
|
||||
}
|
||||
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)],
|
||||
SamKind::Gepard => &[DVec3::new(-0.92, -0.11, -1.7), DVec3::new(0.92, -0.11, -1.7)],
|
||||
_ => &[],
|
||||
};
|
||||
for (k, b) in barrels.iter().enumerate() {
|
||||
let r = rot * DQuat::from_rotation_z(self.rng.f() * TAU);
|
||||
out.model(id + 4 + k as u32, "airdef:flash", mount + rot * *b * s, r, 0.9, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------ interceptors
|
||||
|
||||
fn step_missiles(&mut self, out: &mut Out) {
|
||||
@@ -1971,13 +1979,21 @@ impl Sim {
|
||||
done.push((m.id, None, false, m.sam));
|
||||
continue;
|
||||
}
|
||||
if lit {
|
||||
if lit && spec.flame == 0.0 {
|
||||
if m.age % 20 == 1 {
|
||||
out.sound("airdef:drone.lancet", m.pos, 0.8, 1.6);
|
||||
}
|
||||
} else if lit {
|
||||
if (m.age - spec.cold) % 46 == 1 {
|
||||
let pitch = match m.kind {
|
||||
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,
|
||||
};
|
||||
out.sound("airdef:missile.flight", m.pos, 1.0, pitch);
|
||||
}
|
||||
@@ -2017,7 +2033,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 {
|
||||
if m.age > spec.cold && 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.rng.f());
|
||||
out.model(m.id + 1, "airdef:flame", m.pos + rot * DVec3::new(0.0, 0.0, back), rot, size, true);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//! Move orders: a unit packs up (its launcher down, radar silent), drives
|
||||
//! over the land to the point it was given, and deploys there; it fights
|
||||
//! again only once deployed. The reload trucks drive the same way.
|
||||
use super::*;
|
||||
|
||||
/// Blocks a tick a vehicle drives (about 25 km/h).
|
||||
const SPEED: f64 = 0.35;
|
||||
|
||||
impl SamKind {
|
||||
/// Ticks to pack up for the march, and to deploy again.
|
||||
pub fn march_ticks(self) -> u32 {
|
||||
match self.spec().family {
|
||||
Family::Vertical => 200,
|
||||
Family::Trainable => 120,
|
||||
Family::Static => 160,
|
||||
Family::Turret | Family::Gun => 40,
|
||||
Family::Light => 40,
|
||||
Family::Truck => 10,
|
||||
}
|
||||
}
|
||||
/// A depot doesn't drive.
|
||||
pub fn mobile(self) -> bool {
|
||||
self != SamKind::Depot
|
||||
}
|
||||
}
|
||||
|
||||
impl Sam {
|
||||
/// Packing up, driving or deploying: not fighting.
|
||||
pub fn moving(&self) -> bool {
|
||||
matches!(self.state, SamState::Packing | SamState::Moving | SamState::Deploying)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sim {
|
||||
/// Orders unit `i` to drive to `to`. False for one that can't (a depot, a wreck).
|
||||
pub fn order_move(&mut self, i: usize, to: DVec3) -> bool {
|
||||
let Some(sam) = self.sams.get_mut(i) else { return false };
|
||||
if !sam.alive() || !sam.kind.mobile() {
|
||||
return false;
|
||||
}
|
||||
sam.dest = Some(to);
|
||||
sam.target = None;
|
||||
sam.gun_target = None;
|
||||
if sam.state != SamState::Moving {
|
||||
sam.state = SamState::Packing;
|
||||
sam.march = sam.kind.march_ticks();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Packing, driving and deploying, once a tick.
|
||||
pub(super) fn step_moves(&mut self, ground: &mut Ground, out: &mut Out) {
|
||||
let now = self.tick;
|
||||
for sam in &mut self.sams {
|
||||
if !sam.alive() {
|
||||
continue;
|
||||
}
|
||||
let spec = sam.kind.spec();
|
||||
match sam.state {
|
||||
SamState::Packing => {
|
||||
// The launcher comes down for the march.
|
||||
sam.pitch += (0.0 - sam.pitch) * 0.06;
|
||||
sam.yaw = slew(sam.yaw, sam.heading, 0.04);
|
||||
sam.march = sam.march.saturating_sub(1);
|
||||
if sam.march == 0 {
|
||||
sam.state = SamState::Moving;
|
||||
}
|
||||
}
|
||||
SamState::Moving => {
|
||||
let Some(to) = sam.dest else {
|
||||
sam.state = SamState::Deploying;
|
||||
sam.march = sam.kind.march_ticks();
|
||||
continue;
|
||||
};
|
||||
let flat = horizontal(to - sam.pos);
|
||||
if flat.length() < SPEED * 2.0 {
|
||||
sam.pos = DVec3::new(to.x, sam.pos.y, to.z);
|
||||
sam.dest = None;
|
||||
sam.state = SamState::Deploying;
|
||||
sam.march = sam.kind.march_ticks();
|
||||
out.sound("minecraft:block.piston.contract", sam.pos, 2.0, 0.6);
|
||||
continue;
|
||||
}
|
||||
// Turn towards it, then drive: slower while turning, over the land's height.
|
||||
sam.heading = slew(sam.heading, heading_of(flat), 0.06);
|
||||
let ahead = DQuat::from_rotation_y(sam.heading) * DVec3::NEG_Z;
|
||||
let turning = angle_diff(sam.heading, heading_of(flat)).abs();
|
||||
let step = SPEED * (1.0 - turning / std::f64::consts::PI).max(0.2);
|
||||
let next = sam.pos + ahead * step;
|
||||
let y = ground.at(next, now).unwrap_or(sam.pos.y);
|
||||
sam.pos = DVec3::new(next.x, sam.pos.y + (y - sam.pos.y).clamp(-0.5, 0.5), next.z);
|
||||
sam.yaw = sam.heading;
|
||||
if now % 4 == 0 {
|
||||
out.particles("minecraft:campfire_cosy_smoke", sam.pos - ahead * 2.0 * spec.scale, 0.3, DVec3::new(0.0, 0.03, 0.0), 0.01, 1);
|
||||
}
|
||||
if now % 30 == 0 {
|
||||
out.sound("minecraft:entity.minecart.riding", sam.pos, 0.6, 0.5);
|
||||
}
|
||||
}
|
||||
SamState::Deploying => {
|
||||
sam.pitch += (spec.rest_pitch - sam.pitch) * 0.05;
|
||||
sam.march = sam.march.saturating_sub(1);
|
||||
if sam.march == 0 {
|
||||
sam.state = SamState::Search;
|
||||
sam.pitch = spec.rest_pitch;
|
||||
sam.yaw = sam.heading;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,9 +199,10 @@ impl Sim {
|
||||
if total == 0 {
|
||||
return 0;
|
||||
}
|
||||
let bearing = self.rng.f() * TAU;
|
||||
let second = bearing + std::f64::consts::PI * (0.6 + self.rng.f() * 0.6);
|
||||
let smart = order.aim == Aim::Smart;
|
||||
// A smart enemy comes from where its raids got through best.
|
||||
let bearing = if smart { self.best_bearing() } else { self.rng.f() * TAU };
|
||||
let second = bearing + std::f64::consts::PI * (0.6 + self.rng.f() * 0.6);
|
||||
// One grid over the whole raid; each kind's prices are worked out once, routes are shared
|
||||
// by the drones of a kind going from the same bearing to the same target.
|
||||
let span = order.dist + 450.0;
|
||||
@@ -222,7 +223,8 @@ impl Sim {
|
||||
let dist = spec.ballistic.map_or(order.dist, |far| far.max(order.dist)) + self.rng.f() * 50.0;
|
||||
let height = if spec.ballistic.is_some() { 10.0 } else if spec.cruise > 0.0 { spec.cruise } else { 25.0 };
|
||||
let from = centre + DVec3::new(a.sin() * dist, height + self.rng.s() * 6.0, a.cos() * dist);
|
||||
let d = self.make_drone(kind, from, horizontal(aim - from), aim);
|
||||
let mut d = self.make_drone(kind, from, horizontal(aim - from), aim);
|
||||
d.sector = ew::sector(a) as u8;
|
||||
let routed = smart && spec.ballistic.is_none() && !spec.decoy && !at_sam.is_some_and(|_| kind == DroneKind::Lancet);
|
||||
let key = routed.then(|| (kind, (a.rem_euclid(TAU) / 0.35) as i32, (target.x / 24.0) as i32, (target.z / 24.0) as i32));
|
||||
// Decoys come 15 s ahead, the rest together; "waves" one kind after another.
|
||||
@@ -344,6 +346,8 @@ impl Sim {
|
||||
engaged: 0,
|
||||
route: Vec::new(),
|
||||
low: false,
|
||||
jammed: false,
|
||||
sector: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,9 +369,19 @@ impl Sim {
|
||||
if aim == Aim::ObjectsAndSams {
|
||||
return if roll < 0.33 { pick(&alive, r).map_or((object, None), |(p, id)| (p, Some(id))) } else { (object, None) };
|
||||
}
|
||||
// Smart: each kind at what it is good against.
|
||||
let guns: Vec<&Sam> = alive.iter().copied().filter(|s| matches!(s.kind, SamKind::Gepard | SamKind::Pantsir)).collect();
|
||||
let long: Vec<&Sam> = alive.iter().copied().filter(|s| matches!(s.kind, SamKind::S400 | SamKind::S500 | SamKind::Patriot)).collect();
|
||||
// Smart: each kind at what it is good against; what gave itself away first (a jamming
|
||||
// station, a radar, a system that fired).
|
||||
let known = self.known_units();
|
||||
let known_ew: Vec<&Sam> = alive.iter().copied().filter(|s| s.kind == SamKind::Ew && known.iter().any(|k| k.0 == s.id)).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));
|
||||
}
|
||||
// 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() <= 200.0).collect();
|
||||
let long: Vec<&Sam> =
|
||||
alive.iter().copied().filter(|s| s.kind.range() >= 300.0 || s.kind == SamKind::Radar).collect();
|
||||
let choice = match kind {
|
||||
DroneKind::Lancet if roll < 0.75 => pick(&guns, r).or_else(|| pick(&alive, r)),
|
||||
DroneKind::Kh22 | DroneKind::Zircon if roll < 0.45 => pick(&long, r),
|
||||
@@ -386,6 +400,21 @@ impl Sim {
|
||||
let height = spec.clearance.max(6.0);
|
||||
let n = grid.size;
|
||||
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 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 {
|
||||
prices[(i * n + j) as usize] += 12.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for s in self.sams.iter().filter(|s| s.alive() && s.roles & spec.class.bit() != 0) {
|
||||
let sp = s.kind.spec();
|
||||
let (lo, hi) = sp.missile.as_ref().map_or((0.0, 0.0), |m| (m.min, m.max));
|
||||
@@ -518,7 +547,7 @@ mod big {
|
||||
fn sixty_one_systems_and_a_big_smart_raid() {
|
||||
let mut sim = Sim::new(11);
|
||||
for i in 0..61 {
|
||||
let kind = SamKind::ALL[i % 5];
|
||||
let kind = SamKind::ARMED[i % 5];
|
||||
let id = sim.id();
|
||||
let p = DVec3::new(((i % 8) as f64 - 4.0) * 40.0, 70.0, ((i / 8) as f64 - 4.0) * 40.0);
|
||||
sim.sams.push(Sam::new(id, kind, p, 0.0));
|
||||
|
||||
@@ -237,7 +237,7 @@ 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 {
|
||||
if t - tr.first as f64 > spec.cold as f64 && 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);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ use super::*;
|
||||
/// Rays round a radar, and blocks between the land's samples along them.
|
||||
const RAYS: usize = 48;
|
||||
const STEP: f64 = 16.0;
|
||||
/// The antenna's height over the vehicle's ground.
|
||||
const MAST: f64 = 5.0;
|
||||
/// How far units share their tracks (the network's data links).
|
||||
pub(super) const LINK: f64 = 1500.0;
|
||||
/// Radio horizon: `HORIZON * (√mast + √height)` blocks.
|
||||
const HORIZON: f64 = 35.0;
|
||||
/// Anything this near is seen (and heard), whatever hides it.
|
||||
@@ -58,16 +58,20 @@ impl Horizon {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a system's radar is.
|
||||
/// Where a unit's radar antenna is.
|
||||
pub(super) fn antenna(sam: &Sam) -> DVec3 {
|
||||
sam.pos + DVec3::Y * MAST
|
||||
sam.pos + DVec3::Y * sam.kind.spec().mast
|
||||
}
|
||||
|
||||
/// How far a system's search radar reaches for this kind at `height` over the land:
|
||||
/// its own reach (worse when damaged) for the target's size, under the radio horizon.
|
||||
pub(super) fn reach(sam: &Sam, kind: DroneKind, height: f64) -> f64 {
|
||||
let radar = sam.kind.range() * 1.3 * sam.fitness() * kind.rcs().powf(0.25);
|
||||
let horizon = HORIZON * (MAST.sqrt() + height.max(0.0).sqrt());
|
||||
let spec = sam.kind.spec();
|
||||
if spec.sensor <= 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
let radar = spec.sensor * sam.fitness() * kind.rcs().powf(0.25);
|
||||
let horizon = HORIZON * (spec.mast.sqrt() + height.max(0.0).sqrt());
|
||||
radar.min(horizon).max(NEAR)
|
||||
}
|
||||
|
||||
@@ -77,11 +81,12 @@ impl Sim {
|
||||
let now = self.tick;
|
||||
// One horizon every few ticks: the land answers come in over time.
|
||||
if !self.sams.is_empty() && now % 3 == 0 {
|
||||
let alive: Vec<usize> = (0..self.sams.len()).filter(|&i| self.sams[i].alive()).collect();
|
||||
if !alive.is_empty() {
|
||||
let i = alive[(now / 3) as usize % alive.len()];
|
||||
let radars: Vec<usize> =
|
||||
(0..self.sams.len()).filter(|&i| self.sams[i].alive() && self.sams[i].kind.spec().sensor > 0.0).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.range() * 1.3, ground, now);
|
||||
let h = Horizon::build(antenna(sam), sam.kind.spec().sensor, ground, now);
|
||||
self.horizons.insert(sam.id, h);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +96,8 @@ impl Sim {
|
||||
self.drones.iter().map(|d| ground.at(d.pos, now).map(|g| d.pos.y - g)).collect();
|
||||
for sam in &mut self.sams {
|
||||
sam.seen.clear();
|
||||
if !sam.alive() {
|
||||
sam.own.clear();
|
||||
if !sam.alive() || sam.kind.spec().sensor <= 0.0 || sam.moving() {
|
||||
continue;
|
||||
}
|
||||
let at = antenna(sam);
|
||||
@@ -108,9 +114,25 @@ impl Sim {
|
||||
if dist > NEAR && horizon.is_some_and(|hz| hz.hides(at, d.pos)) {
|
||||
continue;
|
||||
}
|
||||
sam.seen.push(d.id);
|
||||
sam.own.push(d.id);
|
||||
}
|
||||
}
|
||||
// The network: every unit gets the tracks of the radars within data-link range.
|
||||
let tracks: Vec<(DVec3, Vec<u32>)> =
|
||||
self.sams.iter().filter(|s| !s.own.is_empty()).map(|s| (s.pos, s.own.clone())).collect();
|
||||
for sam in &mut self.sams {
|
||||
if !sam.alive() || sam.moving() || !sam.kind.armed() {
|
||||
sam.seen = sam.own.clone();
|
||||
continue;
|
||||
}
|
||||
let mut seen = sam.own.clone();
|
||||
for (at, ids) in &tracks {
|
||||
if at.distance(sam.pos) <= LINK {
|
||||
seen.extend(ids.iter().copied().filter(|id| !seen.contains(id)).collect::<Vec<_>>());
|
||||
}
|
||||
}
|
||||
sam.seen = seen;
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether any system's radar holds this drone (the player's radar shows only those).
|
||||
|
||||
@@ -0,0 +1,690 @@
|
||||
//! Every unit the player deploys, as data: the air-defence systems (their
|
||||
//! weapons, radar and how their vehicles are put together and drawn) and the
|
||||
//! units that serve them — long-range radars, ammunition depots, reload
|
||||
//! trucks, EW stations and searchlights. They share a position, health,
|
||||
//! wrecks, saving and move orders; only what they carry differs.
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum SamKind {
|
||||
Patriot,
|
||||
Gepard,
|
||||
Pantsir,
|
||||
S400,
|
||||
S500,
|
||||
IrisT,
|
||||
Nasams,
|
||||
Buk,
|
||||
Tor,
|
||||
P1sun,
|
||||
/// A long-range search radar: no weapons, it feeds the network.
|
||||
Radar,
|
||||
/// An ammunition depot: the reload trucks fill up there.
|
||||
Depot,
|
||||
/// A transport-loader: drives missiles from a depot to the systems.
|
||||
Tzm,
|
||||
/// An electronic-warfare station: jams the drones' navigation while switched on.
|
||||
Ew,
|
||||
/// A searchlight: lights up targets at night for the guns.
|
||||
Light,
|
||||
}
|
||||
|
||||
/// How a vehicle is put together: how its launcher or turret moves and is drawn.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Family {
|
||||
/// A launcher on a trailer that trains on the target (Patriot, NASAMS, P1-SUN).
|
||||
Trainable,
|
||||
/// A gun turret with a radar dish (Gepard).
|
||||
Gun,
|
||||
/// A turret with an elevating pack (Pantsir, Buk).
|
||||
Turret,
|
||||
/// Vertical launch from a raised pack (S-400, S-500, IRIS-T) or a turning turret (Tor).
|
||||
Vertical,
|
||||
/// Nothing that aims: a radar, a depot, an EW station.
|
||||
Static,
|
||||
/// A truck (the transport-loader).
|
||||
Truck,
|
||||
/// A searchlight.
|
||||
Light,
|
||||
}
|
||||
|
||||
/// Where missile `n` leaves the launcher, as the launcher's model lays its tubes out.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Muzzle {
|
||||
None,
|
||||
Patriot,
|
||||
Pantsir,
|
||||
/// `cols × rows` tubes `size` model units wide, from the pack's pivot to its far end.
|
||||
Grid(u32, u32, f64),
|
||||
/// Missiles on rails, at these x (model units).
|
||||
Rails(&'static [f64]),
|
||||
/// The Tor's eight vertical cells on top of its turret.
|
||||
Cells,
|
||||
}
|
||||
|
||||
/// The models a unit is drawn with.
|
||||
pub struct Parts {
|
||||
/// The chassis, trailer or base.
|
||||
pub body: &'static str,
|
||||
/// The launcher, the tubes or the turret.
|
||||
pub launcher: Option<&'static str>,
|
||||
/// A turret's elevating pack or guns.
|
||||
pub pack: Option<&'static str>,
|
||||
/// A dish on the turret: model, height and forward offset (× scale), tilt.
|
||||
pub dish: Option<(&'static str, f64, f64, f64)>,
|
||||
/// Other fixed models: model, height, backward offset (× scale).
|
||||
pub fixed: &'static [(&'static str, f64, f64)],
|
||||
/// A turning antenna: model, height, backward offset, size, tilt.
|
||||
pub spinner: Option<(&'static str, f64, f64, f64, f64)>,
|
||||
}
|
||||
|
||||
pub(super) struct MissileLoad {
|
||||
pub kind: MissileKind,
|
||||
pub magazine: u32,
|
||||
pub min: f64,
|
||||
pub max: f64,
|
||||
/// Ticks between launches.
|
||||
pub interval: u32,
|
||||
}
|
||||
|
||||
pub(super) struct GunSpec {
|
||||
pub range: f64,
|
||||
/// Starts tracking from here.
|
||||
pub track: f64,
|
||||
/// Chance per burst at point blank.
|
||||
pub p: f64,
|
||||
pub bursts: u32,
|
||||
/// Shell speed for the lead, blocks a tick.
|
||||
pub shell: f64,
|
||||
pub slew: (f64, f64),
|
||||
}
|
||||
|
||||
pub struct SamSpec {
|
||||
pub name: &'static str,
|
||||
pub key: &'static str,
|
||||
pub(super) missile: Option<MissileLoad>,
|
||||
pub(super) gun: Option<GunSpec>,
|
||||
pub(super) reload: u32,
|
||||
pub(super) scale: f64,
|
||||
/// Where it points its launcher at rest (pitch, radians).
|
||||
pub(super) rest_pitch: f64,
|
||||
/// Which classes of target it engages unless told otherwise.
|
||||
pub roles: u8,
|
||||
pub family: Family,
|
||||
pub parts: Parts,
|
||||
/// The launcher's or turret's centre over the vehicle's centre: up and forward, × scale.
|
||||
pub(super) mount: (f64, f64),
|
||||
/// The launcher model's pivot from its centre (blocks).
|
||||
pub(super) pivot: DVec3,
|
||||
pub(super) muzzle: Muzzle,
|
||||
/// Its own search radar's reach (0: none, it fires on the network's tracks), and the antenna's height.
|
||||
pub sensor: f64,
|
||||
pub mast: f64,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
];
|
||||
/// The ones that shoot (the roles menu lists these).
|
||||
pub const ARMED: [SamKind; 10] = [
|
||||
SamKind::Patriot,
|
||||
SamKind::Gepard,
|
||||
SamKind::Pantsir,
|
||||
SamKind::S400,
|
||||
SamKind::S500,
|
||||
SamKind::IrisT,
|
||||
SamKind::Nasams,
|
||||
SamKind::Buk,
|
||||
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: 300.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: 390.0,
|
||||
mast: 5.0,
|
||||
};
|
||||
&S
|
||||
}
|
||||
SamKind::Gepard => {
|
||||
const S: SamSpec = SamSpec {
|
||||
name: "Гепард",
|
||||
key: "gepard",
|
||||
missile: None,
|
||||
gun: Some(GunSpec { range: 90.0, track: 130.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: 170.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: 150.0, interval: 14 }),
|
||||
gun: Some(GunSpec { range: 60.0, track: 90.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: 195.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: 480.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: 624.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: 700.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: 910.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: 400.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: 520.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: 300.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: 390.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: 450.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: 585.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: 160.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: 210.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: 450.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: 1700.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
|
||||
}
|
||||
pub fn key(self) -> &'static str {
|
||||
self.spec().key
|
||||
}
|
||||
pub fn from_key(key: &str) -> Option<Self> {
|
||||
Self::ALL.into_iter().find(|k| k.key() == key)
|
||||
}
|
||||
/// How far it reaches: its missiles or guns; 0 for a unit with neither.
|
||||
pub fn range(self) -> f64 {
|
||||
let s = self.spec();
|
||||
s.missile.as_ref().map_or(0.0, |m| m.max).max(s.gun.as_ref().map_or(0.0, |g| g.range))
|
||||
}
|
||||
pub fn armed(self) -> bool {
|
||||
let s = self.spec();
|
||||
s.missile.is_some() || s.gun.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sam {
|
||||
pub(super) fn base(&self) -> DQuat {
|
||||
DQuat::from_rotation_y(self.heading)
|
||||
}
|
||||
pub(super) fn scale(&self) -> f64 {
|
||||
self.kind.spec().scale
|
||||
}
|
||||
/// Where shells and missiles leave: the centre of the turret or the launcher's pivot, and the
|
||||
/// rotation they leave with.
|
||||
pub(super) fn mount(&self) -> (DVec3, DQuat) {
|
||||
let spec = self.kind.spec();
|
||||
let s = spec.scale;
|
||||
let base = self.base();
|
||||
let (up, fwd) = spec.mount;
|
||||
let aim = DQuat::from_rotation_y(self.yaw) * DQuat::from_rotation_x(self.pitch);
|
||||
match spec.family {
|
||||
Family::Trainable => (self.pos + base * DVec3::new(0.0, up * s, fwd * s), aim),
|
||||
Family::Gun | Family::Turret | Family::Light => {
|
||||
(self.pos + DVec3::new(0.0, up * s, 0.0) + base * DVec3::new(0.0, 0.0, fwd * s), aim)
|
||||
}
|
||||
Family::Vertical if matches!(spec.muzzle, Muzzle::Cells) => {
|
||||
(self.pos + DVec3::new(0.0, up * s, 0.0), DQuat::from_rotation_y(self.yaw))
|
||||
}
|
||||
Family::Vertical => (self.pos + base * DVec3::new(0.0, up * s, fwd * s), base * DQuat::from_rotation_x(self.pitch)),
|
||||
Family::Static | Family::Truck => (self.pos + DVec3::Y * s, base),
|
||||
}
|
||||
}
|
||||
/// Where missile `n` of the magazine leaves (world), and which way.
|
||||
pub(super) fn muzzle(&self, n: u32) -> (DVec3, DVec3) {
|
||||
let (at, rot) = self.mount();
|
||||
let s = self.scale();
|
||||
let local = match self.kind.spec().muzzle {
|
||||
Muzzle::Patriot => DVec3::new(
|
||||
if n % 2 == 0 { -0.42 } else { 0.42 },
|
||||
0.625 + if n % 4 < 2 { 0.28 } else { -0.28 },
|
||||
-2.8,
|
||||
),
|
||||
Muzzle::Pantsir => {
|
||||
let side = if n % 2 == 0 { -0.94 } else { 0.94 };
|
||||
let row = (n / 2) % 6;
|
||||
DVec3::new(side + ((row % 3) as f64 - 1.0) * 0.15, 0.1 + if row < 3 { 0.15 } else { -0.15 }, -1.5)
|
||||
}
|
||||
Muzzle::Grid(cols, rows, size) => {
|
||||
// From the pivot (rear bottom of the pack) to the far end of tube n.
|
||||
let (i, j) = (n % cols, (n / cols) % rows);
|
||||
DVec3::new(
|
||||
(i as f64 - (cols - 1) as f64 / 2.0) * (size + 1.0) / 16.0,
|
||||
(j as f64 * (size + 1.0) + size / 2.0) / 16.0,
|
||||
-3.0,
|
||||
)
|
||||
}
|
||||
Muzzle::Rails(xs) => {
|
||||
let x = xs[n as usize % xs.len()];
|
||||
DVec3::new((x - 8.0) / 16.0, 0.1 - 2.5 / 16.0, -1.5)
|
||||
}
|
||||
Muzzle::Cells => {
|
||||
let (i, j) = (n % 4, (n / 4) % 2);
|
||||
DVec3::new((i as f64 * 5.0 + 1.0 - 8.0) / 16.0, 0.6, (j as f64 * 8.0 + 7.0 - 8.0) / 16.0)
|
||||
}
|
||||
Muzzle::None => DVec3::ZERO,
|
||||
};
|
||||
// Cells launch straight up whichever way the turret faces.
|
||||
let dir = if matches!(self.kind.spec().muzzle, Muzzle::Cells) { DVec3::Y } else { rot * DVec3::NEG_Z };
|
||||
(at + rot * local * s, dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sim {
|
||||
/// Draws unit `i`: its vehicle, its launcher or turret where it points, its antennas turning.
|
||||
pub(super) fn draw_sam(&mut self, i: usize, out: &mut Out) {
|
||||
if !self.sams[i].alive() {
|
||||
self.draw_wreck(i, out);
|
||||
return;
|
||||
}
|
||||
let sam = &self.sams[i];
|
||||
let spec = sam.kind.spec();
|
||||
let parts = &spec.parts;
|
||||
let base = sam.base();
|
||||
let s = spec.scale;
|
||||
let up = DVec3::Y * 0.5 * s;
|
||||
let id = sam.id;
|
||||
let (mount, rot) = sam.mount();
|
||||
let turret = DQuat::from_rotation_y(sam.yaw);
|
||||
// A unit on the move is packed: launcher down, turret ahead.
|
||||
out.model(id, parts.body, sam.pos + up, base, s, false);
|
||||
let mut k = 1;
|
||||
let mut part = |out: &mut Out, model: &'static str, at: DVec3, r: DQuat, size: f64| {
|
||||
out.model(id + k, model, at, r, size, false);
|
||||
k += 1;
|
||||
};
|
||||
match spec.family {
|
||||
Family::Trainable => {
|
||||
if let Some(l) = parts.launcher {
|
||||
part(out, l, mount - rot * spec.pivot * s, rot, s);
|
||||
}
|
||||
}
|
||||
Family::Vertical if !matches!(spec.muzzle, Muzzle::Cells) => {
|
||||
if let Some(l) = parts.launcher {
|
||||
part(out, l, mount - rot * spec.pivot * s, rot, s);
|
||||
}
|
||||
}
|
||||
Family::Vertical => {
|
||||
if let Some(l) = parts.launcher {
|
||||
part(out, l, mount, turret, s);
|
||||
}
|
||||
}
|
||||
Family::Gun => {
|
||||
if let Some(l) = parts.launcher {
|
||||
part(out, l, mount + DVec3::Y * 0.12 * s, turret, s);
|
||||
}
|
||||
let recoil = rot * DVec3::new(0.0, 0.0, 0.15 * sam.recoil * s);
|
||||
if let Some(p) = parts.pack {
|
||||
part(out, p, mount + recoil, rot, s);
|
||||
}
|
||||
}
|
||||
Family::Turret => {
|
||||
if let Some(l) = parts.launcher {
|
||||
part(out, l, mount - DVec3::Y * 0.2 * s, turret, s);
|
||||
}
|
||||
let recoil = rot * DVec3::new(0.0, 0.0, 0.1 * sam.recoil * s);
|
||||
if let Some(p) = parts.pack {
|
||||
part(out, p, mount + DVec3::Y * 0.1 * s + recoil, rot, s);
|
||||
}
|
||||
}
|
||||
Family::Light => {
|
||||
if let Some(l) = parts.launcher {
|
||||
part(out, l, mount, rot, s);
|
||||
}
|
||||
}
|
||||
Family::Static | Family::Truck => {}
|
||||
}
|
||||
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);
|
||||
}
|
||||
for &(model, h, back) in parts.fixed {
|
||||
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);
|
||||
}
|
||||
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)],
|
||||
SamKind::Gepard => &[DVec3::new(-0.92, -0.11, -1.7), DVec3::new(0.92, -0.11, -1.7)],
|
||||
_ => &[],
|
||||
};
|
||||
for (n, b) in barrels.iter().enumerate() {
|
||||
let r = rot * DQuat::from_rotation_z(self.rng.f() * TAU);
|
||||
out.model(id + 12 + n as u32, "airdef:flash", mount + rot * *b * s, r, 0.9, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
From 3de7a6875140a107e28563cc44489b55f5794517 Mon Sep 17 00:00:00 2001
|
||||
From: chexit <chexit@duck.com>
|
||||
Date: Wed, 28 Oct 2026 12:37:45 +0300
|
||||
Subject: [PATCH 01/10] Mod API (AirDef fork): world models, client particles,
|
||||
Subject: [PATCH 01/12] Mod API (AirDef fork): world models, client particles,
|
||||
sounds, ground heights
|
||||
|
||||
render.set-models draws block models with an interpolated pose per id;
|
||||
|
||||