97 lines
3.7 KiB
C++
97 lines
3.7 KiB
C++
|
|
#include "sim/target.h"
|
|||
|
|
|
|||
|
|
namespace
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
// Кучки: 5 на арене (рядом со стартом), 3 в северной комнате,
|
|||
|
|
// 3 в западной, 1 одиночная в восточной. Всего 12.
|
|||
|
|
//
|
|||
|
|
// Северная кучка стоит НАПРОТИВ прохода x=30..31: из коридора до неё есть
|
|||
|
|
// чистая линия. Это тот самый тест-кейс из спека — в узком проходе правило
|
|||
|
|
// «не стрелять сквозь своих» упирается в геометрию, и отряд обязан вытянуться
|
|||
|
|
// в колонну: стреляет передний, задние молчат.
|
|||
|
|
//
|
|||
|
|
// Кучка обязана быть В ПРЕДЕЛАХ ДАЛЬНОСТИ от середины прохода (y≈14), иначе
|
|||
|
|
// стрелять из коридора не по чему и тест-кейс проверяет пустоту. При weaponRange
|
|||
|
|
// 7.2 ближняя мишень стоит в ~6.5 юнита: запас есть, но небольшой — если менять
|
|||
|
|
// дальность, эти координаты надо пересчитывать вместе с ней.
|
|||
|
|
const float kTargetSpots[NUM_TARGETS][2] = {
|
|||
|
|
{30.5f, 20.5f}, {31.5f, 22.5f}, {29.5f, 23.5f}, {32.5f, 21.5f}, {30.5f, 24.5f},
|
|||
|
|
{29.5f, 8.5f}, {31.5f, 7.5f}, {27.5f, 10.5f},
|
|||
|
|
{ 7.5f, 40.5f}, { 9.5f, 42.5f}, { 6.5f, 43.5f},
|
|||
|
|
{43.5f, 35.5f},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Сопротивление отбросу по слотам. Не случайное: в каждой кучке есть кто-то
|
|||
|
|
// тяжёлый, кого клинком не сдвинуть, и кто-то лёгкий, кто улетает от первого
|
|||
|
|
// же удара. Иначе «отброс» и «сопротивление» — два числа, которых не видно.
|
|||
|
|
const float kTargetKnockResist[NUM_TARGETS] = {
|
|||
|
|
0.00f, 0.65f, 0.15f, 0.40f, 0.00f,
|
|||
|
|
0.55f, 0.10f, 0.30f,
|
|||
|
|
0.20f, 0.70f, 0.05f,
|
|||
|
|
0.85f,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
} // namespace
|
|||
|
|
|
|||
|
|
void SpawnTargets(ecs::World& world)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < NUM_TARGETS; ++i)
|
|||
|
|
{
|
|||
|
|
Target t;
|
|||
|
|
t.self = world.Create();
|
|||
|
|
t.pos = {kTargetSpots[i][0], kTargetSpots[i][1]};
|
|||
|
|
t.home = t.pos;
|
|||
|
|
t.vel = {};
|
|||
|
|
t.hp = g_tune.targetHP;
|
|||
|
|
t.maxHp = g_tune.targetHP;
|
|||
|
|
t.radius = g_tune.targetRadius;
|
|||
|
|
t.respawnTimer = 0.0f;
|
|||
|
|
t.alive = true;
|
|||
|
|
t.knockResist = kTargetKnockResist[i];
|
|||
|
|
world.Add<Target>(t.self, t);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void KnockTarget(Target& t, Vec2 dir, float speed)
|
|||
|
|
{
|
|||
|
|
t.vel += dir * (speed * t.KnockScale());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void UpdateTargets(std::vector<Target>& targets, float dt)
|
|||
|
|
{
|
|||
|
|
// Отдача гасится и мишень возвращается на своё место: расстановка уровня
|
|||
|
|
// захардкожена, и «уползание» мишеней ломало бы все замеры разом.
|
|||
|
|
const float damp = std::exp(-g_tune.targetKnockDamp * dt);
|
|||
|
|
const float pull = 1.0f - std::exp(-g_tune.targetReturnRate * dt);
|
|||
|
|
|
|||
|
|
for (Target& t : targets)
|
|||
|
|
{
|
|||
|
|
t.radius = g_tune.targetRadius;
|
|||
|
|
t.maxHp = g_tune.targetHP;
|
|||
|
|
|
|||
|
|
t.pos += t.vel * dt;
|
|||
|
|
t.vel *= damp;
|
|||
|
|
t.pos += (t.home - t.pos) * pull;
|
|||
|
|
|
|||
|
|
if (t.alive) continue;
|
|||
|
|
|
|||
|
|
t.respawnTimer -= dt;
|
|||
|
|
if (t.respawnTimer <= 0.0f)
|
|||
|
|
{
|
|||
|
|
t.alive = true;
|
|||
|
|
t.hp = g_tune.targetHP;
|
|||
|
|
t.pos = t.home;
|
|||
|
|
t.vel = {};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int CountAliveTargets(const std::vector<Target>& targets)
|
|||
|
|
{
|
|||
|
|
int n = 0;
|
|||
|
|
for (const Target& t : targets)
|
|||
|
|
if (t.alive) ++n;
|
|||
|
|
return n;
|
|||
|
|
}
|