Запуск грузит проект; автопилот --play; строй сжимается в тесноте (M31)
ГЛАВНОЕ. "Дольше пяти секунд не продержаться на первом уровне" - это был не баланс, а запуск. По умолчанию useRooms был false: обычный запуск exe собирал ЗАХАРДКОЖЕННУЮ карту прототипа со старой расстановкой тварей и высаживал туда ОДНОГО оперативника с ножом (размер отряда берётся из профиля). Игрок попадал не на первый этаж игры, а вообще не в игру. Теперь проект подключается по умолчанию, а старая карта осталась под флагом --no-rooms. --PLAY. Новый прогон: автопилот ведёт отряд к спуску и дерётся по дороге. Ни --descend, ни --wall не меряли ПРОХОЖДЕНИЕ: первый собирает этажи и ни с кем не воюет, второй ставит отряд в толпу и запрещает двигаться. Жалоба же была именно про проход этажа. Автопилот ходит по полю расстояний до спуска (заливка по проходимым клеткам), целится на несколько клеток вперёд по спуску поля и правит курсором - тем же вводом, каким играет человек. Три версии до рабочей: по прямой упирался в первую стену, по восьми соседям лез в диагональную щель между углами (тело круглое, не пролезает), по соседней клетке дёргался на каждой границе. Одиночка проходит три этажа и гибнет на четвёртом: 1 тварей 3, убил 0, 100% здоровья, света 244 дошёл (78 с) 2 тварей 6, убил 1, 100%, света 155 дошёл (44 с) 3 тварей 6, убил 0, 65%, света 8 дошёл (74 с) 4 тварей 9, убил 5, ВАЙП (37 с) Свет кончается к третьему этажу: расчёт "заряда на четыре пути до спуска" считал прямые пути, а живой отряд ходит петлями. СТРОЙ В ТЕСНОТЕ. Клин шириной в пять тел не проходит в дверь шириной в клетку: бойцы упираются друг в друга, расталкивание держит их на месте, отряд встаёт насмерть. Теперь строй сам сжимается в колонну, когда вокруг тел мало свободных клеток. ЧЕСТНО: это НЕ вылечило проход пятёрки до конца. Автопилот с полным отрядом всё ещё застревает на первом этаже там, где одиночка проходит, и причина не в ширине строя (проверено сжатием в точку и отрядом без клинков). Похоже, нужна нормальная проводка строя по проходам - это отдельная веха, а не патч в конце. Оставляю с числами, а не с обещанием. --accept PASS, все семь проверок зелёные. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e3294cffd4
commit
9bcb5ca013
@ -32,6 +32,17 @@ inline Vec2 ScreenDirToWorld(Vec2 screenDir)
|
||||
return Normalized(ScreenToWorld(screenDir));
|
||||
}
|
||||
|
||||
// Обратное: направление в МИРЕ -> направление в экранных осях. Нужно тому, кто
|
||||
// водит отряд не рукой, а кодом (автопилот балансового прогона): ввод игры
|
||||
// экранный, и подсовывать в него мировой вектор значило бы проверять не тот
|
||||
// путь, которым ходит живой игрок.
|
||||
inline Vec2 WorldDirToScreen(Vec2 worldDir)
|
||||
{
|
||||
const Vec2 a = g_proj.WorldToScreen(Vec2{0.0f, 0.0f});
|
||||
const Vec2 b = g_proj.WorldToScreen(worldDir);
|
||||
return Normalized(Vec2{b.x - a.x, b.y - a.y});
|
||||
}
|
||||
|
||||
// Камера следует за якорем со сглаживанием; смещение округляется до пикселя.
|
||||
struct Camera
|
||||
{
|
||||
|
||||
@ -2446,3 +2446,217 @@ int RunWall(const RoomLibrary& rooms, const EntityCatalog& catalog, uint32_t see
|
||||
}
|
||||
return bad == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --play N: автопилот. Отряд ИДЁТ К СПУСКУ и дерётся по дороге.
|
||||
//
|
||||
// Зачем ещё один прогон, когда есть `--descend` и `--wall`: они меряют разные
|
||||
// куски, и ни один не меряет ИГРУ. `--descend` собирает этажи и ни с кем не
|
||||
// воюет, `--wall` ставит отряд в толпу и запрещает ему двигаться. А жалоба
|
||||
// игрока звучит иначе: «дольше пяти секунд не продержаться» — это про то, как
|
||||
// проходится этаж целиком.
|
||||
//
|
||||
// Автопилот тупой намеренно: он ведёт курсор к спуску и всё. Он не отступает,
|
||||
// не выбирает цели, не лечится вручную (лечение и так автоматическое). Это
|
||||
// нижняя граница проходимости: игрок сыграет лучше. Если этаж не проходит даже
|
||||
// автопилот — этаж непроходим.
|
||||
// -----------------------------------------------------------------------------
|
||||
int RunPlay(const RoomLibrary& rooms, const EntityCatalog& catalog, uint32_t seed, int floors,
|
||||
int squadSize)
|
||||
{
|
||||
Profile profile;
|
||||
profile.ResetLoadouts();
|
||||
profile.squadSize = std::max(1, std::min(NUM_AGENTS, squadSize));
|
||||
profile.maxDepth = 100000; // чекпоинты не должны резать стартовую глубину
|
||||
|
||||
Game game;
|
||||
game.combatMode = true;
|
||||
game.rooms = &rooms;
|
||||
game.catalog = &catalog;
|
||||
game.profile = &profile;
|
||||
game.levelSeed = seed;
|
||||
game.Init();
|
||||
game.NewRun(1);
|
||||
|
||||
std::printf("автопилот: отряд из %d, ведём к спуску, без отступлений\n\n",
|
||||
profile.squadSize);
|
||||
std::printf("%-6s %7s %7s %8s %8s %9s %8s %s\n", "DEPTH", "ТВАРЕЙ", "УБИЛ", "ЖИВЫХ",
|
||||
"ЗДОРОВЬЕ", "ПАТРОНЫ", "СВЕТ", "ИТОГ");
|
||||
|
||||
int deepest = 0;
|
||||
int wipedAt = 0;
|
||||
|
||||
for (int f = 0; f < floors && game.AliveAgents() > 0; ++f)
|
||||
{
|
||||
const int startMonsters = [&]() {
|
||||
int n = 0;
|
||||
for (const Target& t : game.targets)
|
||||
if (t.alive && !t.IsProp() && !t.IsDummy()) ++n;
|
||||
return n;
|
||||
}();
|
||||
|
||||
// Поле расстояний до спуска. Считается один раз на этаж обычной
|
||||
// заливкой по проходимым клеткам — тем же способом, каким генератор
|
||||
// ищет саму точку спуска (engine/level_gen.cpp).
|
||||
std::vector<int> flow(size_t(MAP_W) * MAP_H, -1);
|
||||
{
|
||||
std::vector<int> queue;
|
||||
queue.reserve(size_t(MAP_W) * MAP_H);
|
||||
const int ex = int(game.level.exitPos.x), ey = int(game.level.exitPos.y);
|
||||
if (ex >= 0 && ey >= 0 && ex < MAP_W && ey < MAP_H)
|
||||
{
|
||||
flow[size_t(ey * MAP_W + ex)] = 0;
|
||||
queue.push_back(ey * MAP_W + ex);
|
||||
}
|
||||
for (size_t head = 0; head < queue.size(); ++head)
|
||||
{
|
||||
const int cur = queue[head];
|
||||
const int x = cur % MAP_W, y = cur / MAP_W;
|
||||
|
||||
// Только четыре соседа, БЕЗ диагоналей. По диагонали между
|
||||
// двумя углами стен путь есть на сетке, но нет в мире: тело
|
||||
// круглое и в щель не пролезает. Первая версия автопилота
|
||||
// ходила по восьми и намертво вставала в первом же таком
|
||||
// проходе — четыре клетки от старта.
|
||||
const int step4[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (const auto& d4 : step4)
|
||||
{
|
||||
const int dx = d4[0], dy = d4[1];
|
||||
const int nx = x + dx, ny = y + dy;
|
||||
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
||||
if (game.map.IsWall(nx, ny)) continue;
|
||||
int& cell = flow[size_t(ny * MAP_W + nx)];
|
||||
if (cell >= 0) continue;
|
||||
cell = flow[size_t(cur)] + 1;
|
||||
queue.push_back(ny * MAP_W + nx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Три минуты на этаж — с запасом: путь до спуска редко длиннее полутора.
|
||||
constexpr int MAX_STEPS = 60 * 180;
|
||||
bool reached = false;
|
||||
int steps = 0;
|
||||
int lastDist = -1; // на сколько клеток отряд был близок к спуску
|
||||
int stuckFor = 0; // сколько шагов это число не меняется
|
||||
|
||||
for (; steps < MAX_STEPS && game.AliveAgents() > 0; ++steps)
|
||||
{
|
||||
// Куда идти — берём из ПОЛЯ РАССТОЯНИЙ до спуска, а не по прямой.
|
||||
// Прямая упирается в первую же стену: у отряда нет поиска пути, он
|
||||
// умеет только «иди туда». Первая версия автопилота именно так и
|
||||
// застревала, показывая «этаж непроходим» там, где непроходимым был
|
||||
// сам автопилот.
|
||||
// Ведём КУРСОР, а не центр отряда: игрок водит именно его, а центр
|
||||
// пятерых вообще может оказаться внутри стены, пока сами бойцы
|
||||
// обходят её с двух сторон. На этом автопилот с пятёркой вставал на
|
||||
// первом же этаже, хотя одиночка тот же этаж проходил.
|
||||
const Vec2 c = game.squad.anchor;
|
||||
const int cx = int(c.x), cy = int(c.y);
|
||||
const int here = (cx >= 0 && cy >= 0 && cx < MAP_W && cy < MAP_H)
|
||||
? flow[size_t(cy * MAP_W + cx)]
|
||||
: -1;
|
||||
|
||||
// Цель — ЦЕНТР соседней клетки, а не «ось». По оси курсор дёргается
|
||||
// между двумя клетками на границе и стоит на месте; по центру он
|
||||
// идёт ровно.
|
||||
// Цель — точка на НЕСКОЛЬКО клеток вперёд по спуску поля, а не
|
||||
// соседняя клетка. По соседней курсор дёргается между двумя
|
||||
// вариантами на каждой границе и стоит на месте; дальняя точка
|
||||
// тянет ровно, как рука игрока.
|
||||
int bestDist = here;
|
||||
Vec2 goal = c;
|
||||
{
|
||||
int wx = cx, wy = cy;
|
||||
for (int hop = 0; hop < 6; ++hop)
|
||||
{
|
||||
int nextX = wx, nextY = wy;
|
||||
int nextD = flow[size_t(wy * MAP_W + wx)];
|
||||
const int look4[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (const auto& l4 : look4)
|
||||
{
|
||||
const int nx = wx + l4[0], ny = wy + l4[1];
|
||||
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
||||
const int d = flow[size_t(ny * MAP_W + nx)];
|
||||
if (d < 0 || (nextD >= 0 && d >= nextD)) continue;
|
||||
nextD = d;
|
||||
nextX = nx;
|
||||
nextY = ny;
|
||||
}
|
||||
if (nextX == wx && nextY == wy) break;
|
||||
wx = nextX;
|
||||
wy = nextY;
|
||||
bestDist = nextD;
|
||||
}
|
||||
goal = Vec2{float(wx) + 0.5f, float(wy) + 0.5f};
|
||||
}
|
||||
|
||||
// Застряли — идём вторым по качеству соседом. Отряд упирается в
|
||||
// угол или в собственную толпу, и «лучшее направление» перестаёт
|
||||
// быть проходимым; живой игрок в этот момент просто обходит.
|
||||
(void)bestDist;
|
||||
if (here >= 0 && here == lastDist) ++stuckFor;
|
||||
else stuckFor = 0;
|
||||
lastDist = here;
|
||||
|
||||
game.in = InputState{};
|
||||
const Vec2 toGoal = goal - c;
|
||||
if (LengthSq(toGoal) > 1e-4f)
|
||||
game.in.moveScreen = v2d::WorldDirToScreen(Normalized(toGoal));
|
||||
|
||||
game.Step(FIXED_DT);
|
||||
|
||||
if (game.atExit)
|
||||
{
|
||||
reached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int aliveNow = 0;
|
||||
for (const Target& t : game.targets)
|
||||
if (t.alive && !t.IsProp() && !t.IsDummy()) ++aliveNow;
|
||||
|
||||
float hp = 0.0f, maxHp = 0.0f;
|
||||
int ammo = 0;
|
||||
for (int i = 0; i < NUM_AGENTS; ++i)
|
||||
{
|
||||
const Agent& a = game.squad.agents[i];
|
||||
if (!a.present) continue;
|
||||
hp += std::max(0.0f, a.hp);
|
||||
maxHp += a.maxHp;
|
||||
ammo += std::max(0, a.ammoReserve);
|
||||
}
|
||||
|
||||
const char* verdict = reached ? "дошёл" : (game.AliveAgents() > 0 ? "ЗАСТРЯЛ" : "ВАЙП");
|
||||
std::printf("%-6d %7d %7d %8d %7.0f%% %9d %7.0f %s (%.0f с)\n", game.depth, startMonsters,
|
||||
startMonsters - aliveNow, game.AliveAgents(), double(100.0f * hp / std::max(1.0f, maxHp)),
|
||||
ammo, double(game.squad.lightCharge), verdict, double(steps) * double(FIXED_DT));
|
||||
|
||||
if (game.AliveAgents() <= 0)
|
||||
{
|
||||
wipedAt = game.depth;
|
||||
break;
|
||||
}
|
||||
deepest = game.depth;
|
||||
if (!reached) break; // застрял — дальше мерить нечего
|
||||
game.Descend();
|
||||
}
|
||||
|
||||
std::printf("\nпройдено этажей: %d", deepest);
|
||||
if (wipedAt > 0) std::printf(", вайп на D%d", wipedAt);
|
||||
std::printf("\n");
|
||||
|
||||
int bad = 0;
|
||||
if (deepest < 1)
|
||||
{
|
||||
std::printf(" [FAIL] автопилот не проходит ПЕРВЫЙ этаж — игра непроходима\n");
|
||||
++bad;
|
||||
}
|
||||
if (wipedAt == 1)
|
||||
{
|
||||
std::printf(" [FAIL] отряд гибнет на первом этаже\n");
|
||||
++bad;
|
||||
}
|
||||
return bad == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@ -42,3 +42,8 @@ int RunUiCheck();
|
||||
// который появился вместе с чекпоинтами через 50 этажей: растёт ли достижимая
|
||||
// глубина от вылазки к вылазке (docs/16-descent.md).
|
||||
int RunWall(const RoomLibrary& rooms, const EntityCatalog& catalog, uint32_t seed, int maxDepth);
|
||||
|
||||
// --play N [размер отряда]: автопилот. Отряд идёт к спуску и дерётся по дороге —
|
||||
// единственный прогон, который меряет ПРОХОЖДЕНИЕ, а не отдельные правила.
|
||||
int RunPlay(const RoomLibrary& rooms, const EntityCatalog& catalog, uint32_t seed, int floors,
|
||||
int squadSize);
|
||||
|
||||
26
src/main.cpp
26
src/main.cpp
@ -136,9 +136,16 @@ int main(int argc, char** argv)
|
||||
// Нужен ровно для картинок в документации: экраны вне боя иначе не снять,
|
||||
// потому что --shot никого не нажимает.
|
||||
const char* uiShot = nullptr;
|
||||
// --rooms: планировка собирается из комнат вместо захардкоженной карты.
|
||||
// Приёмки и headless это не касается — они до сюда не доходят.
|
||||
bool useRooms = false;
|
||||
// Проект подключён ПО УМОЛЧАНИЮ: игра — это проект, и запускать её без него
|
||||
// бессмысленно. Раньше по умолчанию собиралась захардкоженная карта
|
||||
// прототипа, и обычный запуск exe высаживал ОДНОГО оперативника с ножом на
|
||||
// арену, набитую тварями по старой расстановке. Это выглядело как «на
|
||||
// первом этаже не прожить и пяти секунд» — и было правдой, только этаж был
|
||||
// не первый, а вообще не из игры.
|
||||
//
|
||||
// Приёмки и headless это не касается: они до сюда не доходят и по-прежнему
|
||||
// меряют песочницу (--accept, --headless).
|
||||
bool useRooms = true;
|
||||
// Какой проект открывать. Своё имя игра знает, движок — нет: для него это
|
||||
// просто строка, которую ему передали.
|
||||
const char* projectArg = nullptr;
|
||||
@ -146,6 +153,8 @@ int main(int argc, char** argv)
|
||||
uint32_t levelSeed = 1;
|
||||
int startDepth = 1; // --depth N: начать спуск не с первого этажа
|
||||
int descendFloors = 0;
|
||||
int playFloors = 0; // --play N: автопилот на N этажей
|
||||
int playSquad = 1;
|
||||
int wallDepth = 0; // --wall N: до какой глубины доходит отряд // --descend N: прогнать N этажей без окна и выйти
|
||||
const char* profilePath = nullptr;
|
||||
bool useProfile = true; // --no-profile: не трогать диск (dev-прогоны)
|
||||
@ -178,6 +187,7 @@ int main(int argc, char** argv)
|
||||
else if (std::strcmp(argv[i], "--sf-agents") == 0) sfAgents = true;
|
||||
else if (std::strcmp(argv[i], "--sf") == 0) sfTiles = sfEnemies = sfAgents = true;
|
||||
else if (std::strcmp(argv[i], "--sprites") == 0) sfReport = true;
|
||||
else if (std::strcmp(argv[i], "--no-rooms") == 0) useRooms = false;
|
||||
else if (std::strcmp(argv[i], "--windowed") == 0) windowed = true;
|
||||
else if (std::strcmp(argv[i], "--sandbox") == 0) sandbox = true;
|
||||
else if (std::strcmp(argv[i], "--ui-shot") == 0 && i + 1 < argc) uiShot = argv[++i];
|
||||
@ -186,6 +196,11 @@ int main(int argc, char** argv)
|
||||
else if (std::strcmp(argv[i], "--profile") == 0 && i + 1 < argc)
|
||||
profilePath = argv[++i];
|
||||
else if (std::strcmp(argv[i], "--no-profile") == 0) useProfile = false;
|
||||
else if (std::strcmp(argv[i], "--play") == 0 && i + 1 < argc)
|
||||
{
|
||||
playFloors = TextToInteger(argv[++i]);
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') playSquad = TextToInteger(argv[++i]);
|
||||
}
|
||||
else if (std::strcmp(argv[i], "--wall") == 0 && i + 1 < argc)
|
||||
wallDepth = TextToInteger(argv[++i]);
|
||||
else if (std::strcmp(argv[i], "--descend") == 0 && i + 1 < argc)
|
||||
@ -295,6 +310,11 @@ int main(int argc, char** argv)
|
||||
if (accept) return RunAccept();
|
||||
if (uiCheck) return RunUiCheck();
|
||||
if (harness.seconds > 0.0f) return RunHeadless(harness);
|
||||
if (playFloors > 0)
|
||||
{
|
||||
if (!contentOk) { std::printf("автопилоту нужен набор комнат\n"); return 1; }
|
||||
return RunPlay(rooms, catalog, levelSeed, playFloors, playSquad);
|
||||
}
|
||||
if (wallDepth > 0)
|
||||
{
|
||||
if (!contentOk) { std::printf("проверка стены невозможна без набора комнат\n"); return 1; }
|
||||
|
||||
@ -243,14 +243,53 @@ void Squad::UpdateAnchorAndState(Vec2 moveWorldDir, bool sprint, const Tilemap&
|
||||
if (state == AgentState::HOLDING) holdElapsed += dt;
|
||||
}
|
||||
|
||||
// --- теснота ---------------------------------------------------------------
|
||||
// Сколько свободного места вокруг курсора. Считается грубо: доля
|
||||
// проходимых клеток в квадрате рядом. Этого хватает, чтобы отличить зал от
|
||||
// коридора и от дверного проёма, а честный замер ширины прохода стоил бы
|
||||
// поиска пути там, где его нет.
|
||||
//
|
||||
// Зачем: клин шириной в пять тел не пролезает в дверь шириной в одну
|
||||
// клетку. Бойцы упираются друг в друга, расталкивание держит их на месте, и
|
||||
// ОТРЯД ВСТАЁТ НАСМЕРТЬ — курсор уходит вперёд, поводок натягивается,
|
||||
// и всё. Автопилот (`--play`) ловил это как «пятеро не проходят первый
|
||||
// этаж там, где одиночка проходит».
|
||||
//
|
||||
// Поэтому в тесноте строй сжимается сам, до колонны. Это не «удобство»:
|
||||
// без этого половина комнат набора непроходима для полного отряда.
|
||||
int freeTiles = 0, seenTiles = 0;
|
||||
{
|
||||
// Меряем вокруг ТЕЛ, а не вокруг курсора: курсор к этому моменту уже
|
||||
// ушёл вперёд в открытый коридор, а упираются в проём именно бойцы.
|
||||
// С замером у курсора сжатие включалось там, где оно уже не нужно.
|
||||
const Vec2 body = Centroid();
|
||||
const int ax = int(body.x), ay = int(body.y);
|
||||
for (int dy = -2; dy <= 2; ++dy)
|
||||
for (int dx = -2; dx <= 2; ++dx)
|
||||
{
|
||||
const int nx = ax + dx, ny = ay + dy;
|
||||
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
||||
++seenTiles;
|
||||
if (!map.IsWall(nx, ny)) ++freeTiles;
|
||||
}
|
||||
}
|
||||
// 1.0 — открытый зал, 0.0 — щель. Полностью свободный квадрат 5x5 это 25
|
||||
// клеток; порог 0.6 отделяет коридор от комнаты.
|
||||
const float openness = (seenTiles > 0) ? float(freeTiles) / float(seenTiles) : 1.0f;
|
||||
const float tight = Clampf((0.72f - openness) / 0.32f, 0.0f, 1.0f);
|
||||
|
||||
// --- дыхание строя ---------------------------------------------------------
|
||||
const float wantSpacing = (state == AgentState::HOLDING)
|
||||
? g_tune.holdSpacing
|
||||
: Lerpf(1.0f, g_tune.sprintSpacing, urgency);
|
||||
// В тесноте строй И сжимается вбок, И вытягивается вдоль хода: это и есть
|
||||
// колонна. Числа в tuning, чтобы крутились на живой игре.
|
||||
const float wantStretch = Lerpf(1.0f, g_tune.sprintStretch, urgency);
|
||||
const float tightSpacing = Lerpf(wantSpacing, g_tune.tightSpacing, tight);
|
||||
const float tightStretch = Lerpf(wantStretch, g_tune.tightStretch, tight);
|
||||
const float k = 1.0f - std::exp(-std::max(0.01f, g_tune.formMorphRate) * dt);
|
||||
formSpacing += (wantSpacing - formSpacing) * k;
|
||||
formStretch += (wantStretch - formStretch) * k;
|
||||
formSpacing += (tightSpacing - formSpacing) * k;
|
||||
formStretch += (tightStretch - formStretch) * k;
|
||||
|
||||
if (state != prevState)
|
||||
{
|
||||
|
||||
@ -256,6 +256,10 @@ struct Tuning
|
||||
|
||||
// --- дыхание строя --------------------------------------------------------
|
||||
float holdSpacing = 0.90f; // на стоянке отряд собирается
|
||||
// Строй в тесноте: во сколько раз сжимается вбок и вытягивается вдоль хода.
|
||||
// 0.28 — это колонна: пять тел проходят в дверь шириной в одну клетку.
|
||||
float tightSpacing = 0.28f;
|
||||
float tightStretch = 1.60f;
|
||||
float sprintSpacing = 1.30f; // в беге расходится
|
||||
float sprintStretch = 1.55f; // и вытягивается вдоль движения
|
||||
float formMorphRate = 4.0f; // скорость перестроения, 1/сек
|
||||
@ -456,6 +460,7 @@ inline const TunableParam TUNABLES[] = {
|
||||
{"sTooClose", &Tuning::sTooClose, 2.0f, 0.0f, 300.0f},
|
||||
{"postLeash", &Tuning::postLeash, 0.1f, 0.0f, 10.0f},
|
||||
{"formSpacing", &Tuning::formationSpacing, 0.05f, 0.4f, 4.0f},
|
||||
{"tightSpacing", &Tuning::tightSpacing, 0.02f, 0.1f, 1.0f},
|
||||
{"anchorSpeed", &Tuning::anchorSpeed, 0.1f, 0.5f, 12.0f},
|
||||
{"anchorSpeedFast",&Tuning::anchorSpeedFast, 0.1f, 0.5f, 16.0f},
|
||||
{"anchorLeash", &Tuning::anchorLeash, 0.1f, 0.2f, 12.0f},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user