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; } } }