#pragma once // ----------------------------------------------------------------------------- // Ядро прототипа (спек 6.3–6.5): проверка линии огня, выбор цели, // решатель огневых позиций с гистерезисом. // // Всё здесь — чистые функции над состоянием мира. Модуль ai/ НЕ знает // ни про рендер, ни про изометрию, ни про ввод. // ----------------------------------------------------------------------------- #include #include "ai/lane_registry.h" #include "sim/agent.h" #include "sim/target.h" #include "engine/tilemap.h" struct SolverContext { const Tilemap* map = nullptr; const Agent* agents = nullptr; int agentCount = 0; const std::vector* targets = nullptr; const LaneRegistry* lanes = nullptr; Vec2 anchor{}; int assignedTargetId = -1; // ручной режим (6.8); -1 = авто }; // --- приоритет цели ---------------------------------------------------------- // Есть ли живая ТВАРЬ (не обстановка) в радиусе propYieldRadius от точки. // // Правило приоритета разрушаемой обстановки, одно на оба класса оружия: пока // такая тварь есть, ящик, бочка и шкафчик целью не становятся ни у стрелка // (firing_solver.cpp), ни у клинка (melee.cpp). Один общий предикат тут не // украшение — разъехавшись, стрелок и клинок начали бы спорить, что важнее. // // Радиус СВОЙ, а не дальность ствола: с дальностью правило читалось бы как // «не достаю — стреляю по ящику», и подошедшую тварь боец встречал бы на // перезарядке. Стены не спасают намеренно: тварь за углом в пяти шагах — всё // ещё повод не заниматься мебелью. bool EnemyNearby(Vec2 from, const SolverContext& ctx); // --- 6.3 --------------------------------------------------------------------- // Линия от дула стрелка (в точке from, ствол смотрит на цель) до targetPos. // Порядок проверок как в спеке: свои -> геометрия -> дистанция. bool LaneClearFrom(Vec2 from, int shooterIndex, Vec2 targetPos, const SolverContext& ctx); // Успеет ли союзник влезть под пулю за время её полёта. // // 6.3 проверяет линию в МОМЕНТ выстрела, а 6.9 делает пулю настоящим снарядом: // до 0.4 с полёта, за которые союзник проходит больше метра. Реактивного // расступания (6.6) тут не хватает — союзник может быть чист в момент нажатия // и войти в коридор уже потом. Поэтому спуск дополнительно проверяет // сближение пули и союзника как двух равномерно движущихся точек. // Само правило 6.3 не ослабляется: см. docs/08-decisions.md. bool AllyInterceptsShot(Vec2 muzzle, Vec2 targetPos, int shooterIndex, const SolverContext& ctx); // --- выбор цели (6.4 / 6.8) -------------------------------------------------- // Заполняет currentTargetId, hasLane, priorityLane и диагностику для оверлея. void EvaluateAgentLane(Agent& a, int index, const SolverContext& ctx); // Ближайшая живая мишень в радиусе searchRadius (или -1). int NearestAliveTarget(Vec2 from, const SolverContext& ctx, float searchRadius); // --- 6.5 --------------------------------------------------------------------- struct SolveResult { Vec2 candidates[SOLVER_CANDIDATES]{}; float scores[SOLVER_CANDIDATES]{}; bool valid[SOLVER_CANDIDATES]{}; int count = 0; int bestIndex = -1; float bestScore = 0.0f; float currentScore = 0.0f; bool committed = false; int bestTargetId = -1; }; float ScoreCandidate(Vec2 cand, const Agent& a, int index, const SolverContext& ctx, int bestTargetId); // true, если решатель отработал (нашёл bestTarget и оценил кандидатов). bool SolveFiringPosition(Agent& a, int index, const SolverContext& ctx, SolveResult& out);