using System.Collections.Generic;
using UnityEngine;
namespace NecromancerTome
{
///
/// Проход одержимой крысы сквозь постройки (BACKLOG, «Могильная крыса» п.4; тест 09.10: «почему не срабатывает
/// проход сквозь текстуры по ПКМ»). Пока держишь ПКМ - как дух змеи, но без полёта:
/// - стены, двери, мебель не держат: поднят Entity.IsStuck, и её собственный шаг (тот же бег по WASD)
/// идёт мимо контроллера столкновений (Entity.ccEntityCollisionStart);
/// - высоту ведём сами: IsStuck снимает и пол, поэтому каждый кадр крыса стоит на поверхности под собой
/// (луч вниз с полуметра - ступенька по пути, как у змеи), а над пустотой падает с ускорением на неё;
/// - грунт (земля, песок, камень) не пускает - как у змеи: шаг в грунт отменяется;
/// - отпустил ПКМ внутри построек - выталкивает на ближайшее свободное место (вбок, вверх, вниз - дороже).
///
public static class RatPhase
{
public static bool On;
static Vector3 _last;
static float _vy;
const float Gravity = 20f;
/// Точка тела крысы для проверки «внутри» и радиус шара свободного места.
const float BodyUp = 0.2f;
const float BodyRadius = 0.12f;
/// С какой высоты над ногами ищем пол: всё ниже - ступенька, на которую она взбегает.
const float StepUp = 0.5f;
static Vector3[] _ejectOffsets;
public static void Tick(World _world, EntityAlive _rat, bool _held)
{
if (_held && !On)
{
On = true;
_vy = 0f;
_last = _rat.position;
_rat.IsStuck = true;
RatModel.SetGhost(_rat, true);
Debug.Log("[NecromancerTome] RatPhase: проход включён");
}
else if (!_held && On)
{
Stop(_world, _rat, "ПКМ отпущена");
return;
}
if (!On)
{
return;
}
_rat.IsStuck = true;
_rat.fallDistance = 0f;
Vector3 p = _rat.position;
if (!_world.IsChunkAreaCollidersLoaded(p))
{
_rat.SetPosition(_last);
return;
}
// В грунт - нельзя: шаг вбок отменяем.
if (SnakeControl.Inside(_world, p + Vector3.up * BodyUp, SnakeControl.TerrainMask, out bool ground) && ground)
{
p.x = _last.x;
p.z = _last.z;
}
// Высота - только наша: собственная гравитация сущности с IsStuck пронесла бы её сквозь пол.
float dt = Time.deltaTime;
p.y = _last.y;
if (SnakeControl.Ground(p + Vector3.up * StepUp, StepUp + 300f, out float floor))
{
if (floor >= p.y - 0.02f)
{
p.y = floor;
_vy = 0f;
}
else
{
_vy -= Gravity * dt;
p.y = Mathf.Max(floor, p.y + _vy * dt);
if (p.y <= floor)
{
_vy = 0f;
}
}
}
Vector3 m = _rat.motion;
m.y = 0f;
_rat.motion = m;
_rat.SetPosition(p);
_last = p;
}
/// Выключить проход; внутри построек - вытолкнуть наружу.
public static void Stop(World _world, EntityAlive _rat, string _why)
{
if (!On)
{
return;
}
On = false;
if (_rat == null)
{
return;
}
_rat.IsStuck = false;
_rat.motion = Vector3.zero;
RatModel.SetGhost(_rat, false);
Vector3 p = _rat.position;
if (_world != null && !Free(_world, p))
{
Vector3? to = Eject(_world, p);
if (to.HasValue)
{
p = to.Value;
Debug.Log("[NecromancerTome] RatPhase: вытолкнута из построек, сдвиг " + (p - _rat.position).magnitude.ToString("F1") + " м");
}
else
{
p = _world.FindSupportingBlockPos(p);
Debug.Log("[NecromancerTome] RatPhase: свободного места рядом нет - на опорный блок");
}
_rat.SetPosition(p);
}
Debug.Log("[NecromancerTome] RatPhase: проход выключен - " + _why);
}
static bool Free(World _world, Vector3 _p)
{
Vector3 body = _p + Vector3.up * BodyUp;
return _world.IsChunkAreaCollidersLoaded(_p) &&
!SnakeControl.Inside(_world, body, SnakeControl.WallMask, out _) &&
!Physics.CheckSphere(body - Origin.position, BodyRadius, SnakeControl.WallMask, QueryTriggerInteraction.Ignore);
}
/// Ближайшее свободное место с опорой: до 4 м вбок, до 1 м вниз, до 4 м вверх; вниз дороже.
static Vector3? Eject(World _world, Vector3 _p)
{
if (_ejectOffsets == null)
{
List list = new List();
for (float r = 0f; r <= 4f; r += 0.25f)
{
int dirs = r == 0f ? 1 : 16;
for (float h = -1f; h <= 4f; h += 0.5f)
{
for (int i = 0; i < dirs; i++)
{
list.Add(Quaternion.Euler(0f, i * 22.5f, 0f) * Vector3.forward * r + Vector3.up * h);
}
}
}
list.Sort((x, y) => (x.magnitude + (x.y < 0f ? -x.y : 0f)).CompareTo(y.magnitude + (y.y < 0f ? -y.y : 0f)));
_ejectOffsets = list.ToArray();
}
foreach (Vector3 o in _ejectOffsets)
{
Vector3 c = _p + o;
if (!Free(_world, c))
{
continue;
}
// Посадить на пол под этим местом; если пол сам внутри построек - место не годится.
if (SnakeControl.Ground(c + Vector3.up * 0.3f, 6f, out float y))
{
c.y = y;
if (!Free(_world, c))
{
continue;
}
}
return c;
}
return null;
}
}
}