#pragma once // ----------------------------------------------------------------------------- // Каталог оружия: ЧТО такое ствол или клинок, в отрыве от того, кто его держит. // // Оружие — отдельная сущность ECS с компонентом WeaponItem. Боец хранит только // ссылку на неё (Agent::weaponEntity) и рабочее состояние выстрела (патроны, // откат, увод ствола). Поэтому «дать бойцу пистолет» — это подменить компонент // на его сущности-оружии, а не править ни агента, ни правила боя. // // Где живут числа. Общее правило прототипа — все числа в tuning.h. Здесь оно // соблюдено с одной оговоркой, записанной в docs/08-decisions.md: КАРАБИН // (оружие по умолчанию, то самое, на котором меряются критерии приёмки) читает // свои параметры из g_tune, поэтому все прежние ручки оверлея и --set работают // как работали. Остальные стволы — записи каталога: у второго ствола не может // быть «того же самого» weaponRange, иначе он не второй ствол. // // Визуальные параметры атаки лежат ЗДЕСЬ ЖЕ, но в нейтральном виде: длины, // радиусы в пикселях и байты RGB. Ни одного типа рендера в sim/ не появилось — // цвет собирает render/fx.cpp. Так у пистолета своя вспышка и свой трассер, // и при этом правило «sim не знает про рендер» не нарушено. // ----------------------------------------------------------------------------- #include #include "core/math.h" #include "tuning.h" enum class WeaponClass : uint8_t { RANGED = 0, // ствол: линия огня, снаряд, правило 6.3 MELEE = 1, // клинок: сектор замаха, то же правило в угловой форме }; inline const char* WeaponClassName(WeaponClass c) { return (c == WeaponClass::MELEE) ? "MELEE" : "RANGED"; } enum class FirearmId : uint8_t { RIFLE = 0, // штатный карабин отряда: числа из tuning.h PISTOL = 1, // короткоствол: ближе, быстрее, мельче магазин COUNT = 2, }; // --- ствол ------------------------------------------------------------------- struct FirearmDef { const char* name = "rifle"; // баллистика и урон float range = 7.2f; float fireInterval = 0.35f; float damage = 12.0f; float laneHalfWidth = 0.10f; float muzzleOffset = 0.30f; float bulletSpeed = 22.0f; float bulletRadius = 0.04f; float knockback = 0.35f; // импульс мишени от попадания // разброс: стоя, на полном ходу, и увод ствола за выстрел float spreadStandDeg = 0.9f; float spreadMoveDeg = 4.2f; float spreadRecoilDeg = 0.55f; float spreadRecoilMax = 3.0f; float recoilDecay = 3.2f; // 1/сек // перезарядка int magazine = 24; float reloadTime = 1.9f; // визуал атаки (нейтральные числа: пиксели, секунды, байты RGB) float tracerLength = 1.30f; float muzzleRadiusPx = 5.0f; float muzzleTime = 0.07f; float muzzleFlamePx = 0.35f; // длина языка пламени, юнитов мира float flashPower = 0.55f; // как источник света float flashRadius = 4.0f; float shakePerShot = 0.55f; uint8_t tracerR = 255, tracerG = 240, tracerB = 205; uint8_t muzzleR = 255, muzzleG = 225, muzzleB = 150; }; // --- клинок ------------------------------------------------------------------ struct BladeDef { const char* name = "blade"; float reach = 1.40f; float arcDeg = 100.0f; float windup = 0.17f; float strike = 0.13f; float recover = 0.24f; float interval = 0.60f; float damage = 130.0f; float knockback = 2.4f; // импульс мишени, юнитов/сек float lungeSpeed = 3.4f; // рывок в момент удара }; // --- компонент сущности-оружия ------------------------------------------------ struct WeaponItem { WeaponClass cls = WeaponClass::RANGED; FirearmId gunId = FirearmId::RIFLE; FirearmDef gun{}; BladeDef blade{}; }; // --- каталог ------------------------------------------------------------------ // Карабин: параметры берутся из tuning, чтобы прежние ручки и --set остались // рабочими (см. шапку файла). inline FirearmDef RifleDef() { FirearmDef d; d.name = "rifle"; d.range = g_tune.weaponRange; d.fireInterval = g_tune.fireInterval; d.damage = g_tune.damage; d.laneHalfWidth = g_tune.laneHalfWidth; d.muzzleOffset = g_tune.muzzleOffset; d.bulletSpeed = g_tune.bulletSpeed; d.bulletRadius = g_tune.bulletRadius; d.knockback = g_tune.bulletKnock; d.spreadStandDeg = g_tune.spreadStandDeg; d.spreadMoveDeg = g_tune.spreadMoveDeg; d.spreadRecoilDeg = g_tune.spreadRecoilDeg; d.spreadRecoilMax = g_tune.spreadRecoilMax; d.recoilDecay = g_tune.spreadRecoilDecay; d.magazine = int(g_tune.magazineRifle); d.reloadTime = g_tune.reloadRifle; d.tracerLength = g_tune.tracerLength; d.muzzleRadiusPx = g_tune.muzzleRadiusPx; d.muzzleTime = g_tune.muzzleTime; d.flashPower = g_tune.flashPower; d.flashRadius = g_tune.flashRadius; d.shakePerShot = g_tune.shakePerShot; return d; } // Пистолет: бьёт ближе и слабее, но чаще; на ходу «гуляет» заметно сильнее // карабина, зато перезаряжается вдвое быстрее. Вспышка мельче и холоднее, // трассер короче — ствол должен читаться по картинке, а не только по цифрам. inline FirearmDef PistolDef() { FirearmDef d; d.name = "pistol"; d.range = 4.6f; d.fireInterval = 0.22f; d.damage = 8.0f; d.laneHalfWidth = 0.10f; d.muzzleOffset = 0.24f; d.bulletSpeed = 17.0f; d.bulletRadius = 0.04f; d.knockback = 0.22f; d.spreadStandDeg = 1.6f; d.spreadMoveDeg = 6.5f; d.spreadRecoilDeg = 0.9f; d.spreadRecoilMax = 4.5f; d.recoilDecay = 4.5f; d.magazine = 7; d.reloadTime = 1.15f; d.tracerLength = 0.85f; d.muzzleRadiusPx = 3.5f; d.muzzleTime = 0.055f; d.muzzleFlamePx = 0.26f; d.flashPower = 0.42f; d.flashRadius = 3.2f; d.shakePerShot = 0.34f; d.tracerR = 255; d.tracerG = 214; d.tracerB = 170; d.muzzleR = 255; d.muzzleG = 205; d.muzzleB = 130; return d; } inline FirearmDef FirearmDefOf(FirearmId id) { return (id == FirearmId::PISTOL) ? PistolDef() : RifleDef(); } inline const char* FirearmName(FirearmId id) { return (id == FirearmId::PISTOL) ? "PISTOL" : "RIFLE"; } // Клинок пока один, и его числа целиком в tuning. inline BladeDef DefaultBladeDef() { BladeDef d; d.reach = g_tune.meleeReach; d.arcDeg = g_tune.meleeArcDeg; d.windup = g_tune.meleeWindup; d.strike = g_tune.meleeStrike; d.recover = g_tune.meleeRecover; d.interval = g_tune.meleeInterval; d.damage = g_tune.meleeDamage; d.knockback = g_tune.meleeKnockback; d.lungeSpeed = g_tune.meleeLungeSpeed; return d; }