using UnityEngine;
namespace NecromancerTome
{
///
/// Дух в чертогах разума - камера без сущности (указание 03.10: «тело можно вообще не делать,
/// нужен только прицел»). Ходит по полу как человек: столкновения - по тем же слоям, что у змеи
/// (SnakeControl.WallMask / TerrainMask), гравитация и ступенька. Взаимодействие одно - книга
/// памяти по E; остальное в чертогах неприкосновенно. Камеру ставят патчи камеры режима змеи
/// (SnakeControl.cs), звук и мир вокруг считаются от духа через SnakeAudioSwap.
///
/// СОН (указание: «тёплый фильтр, это как бы сон», эффекты - на моё усмотрение): готовые эффекты
/// экрана игры - Hot (тёплый оттенок), Blur (мягкое размытие), Drunk (медленное покачивание).
/// Силы крутятся командой necrodream на ходу.
///
public static class MindControl
{
public const float EyeHeight = 1.6f;
public const float Radius = 0.3f;
public const float Height = 1.75f;
public const float WalkSpeed = 2.2f;
public const float RunSpeed = 3.6f;
public const float Gravity = 20f;
public const float JumpSpeed = 6f;
public const float StepUp = 0.6f;
public const float ReachDistance = 3f;
public static float Hot = 0.45f;
public static float Blur = 0.25f;
public static float Drunk = 0.15f;
public static bool Controlling;
static Vector3 _feet;
static Vector3 _spawn;
static float _yaw, _pitch, _vy;
static bool _grounded;
public static bool HasFocus;
static EntityPlayerLocal _player;
public static Vector3 FeetPos => _feet;
public static void Begin(EntityPlayerLocal _p, Vector3 _at, float _atYaw)
{
_player = _p;
_feet = _at;
_spawn = _at;
_yaw = _atYaw;
_pitch = 0f;
_vy = 0f;
_grounded = false;
HasFocus = false;
Controlling = true;
_p.PlayerUI?.windowManager.Close(SnakeControl.ToolbeltWindow);
Dream(_p, true);
Debug.Log("[NecromancerTome] MindControl: дух в чертогах, " + _at);
}
public static void End(EntityPlayerLocal _p)
{
if (!Controlling)
{
return;
}
Controlling = false;
HasFocus = false;
Dream(_p ?? _player, false);
_player = null;
}
public static void Dream(EntityPlayerLocal _p, bool _on)
{
ScreenEffects fx = _p?.ScreenEffectManager;
if (fx == null)
{
return;
}
fx.SetScreenEffect("Hot", _on ? Hot : 0f, _on ? 2f : 0.5f);
fx.SetScreenEffect("Blur", _on ? Blur : 0f, _on ? 2f : 0.5f);
fx.SetScreenEffect("Drunk", _on ? Drunk : 0f, _on ? 2f : 0.5f);
}
static bool Blocked(Vector3 _p)
{
Vector3 a = _p + Vector3.up * (Radius + 0.05f) - Origin.position;
Vector3 b = _p + Vector3.up * (Height - Radius) - Origin.position;
return Physics.CheckCapsule(a, b, Radius, SnakeControl.WallMask, QueryTriggerInteraction.Ignore);
}
static bool TryMove(Vector3 _d)
{
Vector3 c = _feet + _d;
if (!Blocked(c))
{
_feet = c;
return true;
}
// Ступенька: на полблока выше свободно - шагаем вверх.
if (_grounded && !Blocked(c + Vector3.up * StepUp))
{
_feet = c + Vector3.up * StepUp;
return true;
}
return false;
}
public static void Update(EntityPlayerLocal _p)
{
if (!Controlling)
{
return;
}
float dt = Time.deltaTime;
bool gui = _p.PlayerUI != null && _p.PlayerUI.windowManager.IsModalWindowOpen();
PlayerActionsLocal input = _p.playerInput;
Vector2 move = Vector2.zero;
bool run = false, jump = false;
if (!gui && input != null)
{
PlayerMoveController pmc = PlayerMoveController.Instance;
Vector2 sens = pmc != null ? pmc.mouseLookSensitivity : Vector2.one * 5f;
int invert = pmc != null && pmc.invertMouse != 0 ? pmc.invertMouse : 1;
_yaw += Input.GetAxis("Mouse X") * sens.x;
_pitch = Mathf.Clamp(_pitch + Input.GetAxis("Mouse Y") * sens.y * invert, -85f, 85f);
move = new Vector2(input.Move.X, input.Move.Y);
run = input.Run.IsPressed;
jump = input.Jump.WasPressed;
if (HasFocus && (input.Activate.WasPressed || input.PermanentActions.Activate.WasPressed))
{
BlockNecroMemoryBook.Open(_p);
}
}
Quaternion yawRot = Quaternion.Euler(0f, _yaw, 0f);
Vector3 dir = yawRot * new Vector3(move.x, 0f, move.y);
if (dir.sqrMagnitude > 1f)
{
dir.Normalize();
}
if (dir.sqrMagnitude > 0.0001f)
{
Vector3 delta = dir * (run ? RunSpeed : WalkSpeed) * dt;
int steps = Mathf.Max(1, Mathf.CeilToInt(delta.magnitude / 0.1f));
Vector3 d = delta / steps;
for (int i = 0; i < steps; i++)
{
if (!TryMove(d) && !TryMove(new Vector3(d.x, 0f, 0f)) && !TryMove(new Vector3(0f, 0f, d.z)))
{
break;
}
}
}
// Падение и прыжок: земля - луч вниз по слою ландшафта и блоков.
if (_grounded && jump)
{
_vy = JumpSpeed;
_grounded = false;
}
_vy -= Gravity * dt;
float dy = _vy * dt;
if (dy > 0f && Blocked(_feet + Vector3.up * dy))
{
_vy = 0f;
dy = 0f;
}
if (Physics.Raycast(_feet + Vector3.up * 0.5f - Origin.position, Vector3.down, out RaycastHit hit, 0.5f - dy + 0.05f, SnakeControl.TerrainMask, QueryTriggerInteraction.Ignore) && dy <= 0f)
{
_feet.y = hit.point.y + Origin.position.y;
_vy = 0f;
_grounded = true;
}
else
{
_feet.y += dy;
_grounded = false;
}
// Провалился (коллизии свежепоставленных блоков ещё не построены) - назад на точку появления.
if (_feet.y < _spawn.y - 3f)
{
Debug.Log("[NecromancerTome] MindControl: дух провалился - назад на точку появления");
_feet = _spawn + Vector3.up * 0.3f;
_vy = 0f;
}
}
/// Из патчей камеры режима змеи: глаза духа и поиск книги под прицелом.
public static void PlaceCamera(Transform _camera)
{
Vector3 eye = _feet + Vector3.up * EyeHeight - Origin.position;
Quaternion rot = Quaternion.Euler(-_pitch, _yaw, 0f);
_camera.SetPositionAndRotation(eye, rot);
HasFocus = FindBook(_camera.position + Origin.position, _camera.forward);
}
/// Шагаем по лучу на ReachDistance: первая встреченная книга памяти - цель; твёрдый
/// блок раньше неё - нет цели (сквозь стены не читаем).
static bool FindBook(Vector3 _from, Vector3 _dir)
{
World world = GameManager.Instance?.World;
if (world == null)
{
return false;
}
Vector3i last = new Vector3i(int.MinValue, 0, 0);
for (float t = 0.1f; t <= ReachDistance; t += 0.05f)
{
Vector3i b = World.worldToBlockPos(_from + _dir * t);
if (b == last)
{
continue;
}
last = b;
Block block = world.GetBlock(b).Block;
if (block is BlockNecroMemoryBook)
{
return true;
}
if (block != null && block.IsCollideMovement && !block.shape.IsTerrain() && block.shape.IsSolidCube)
{
return false;
}
}
return false;
}
}
/// Прицел духа и подсказка у книги (OnGUI, как прицел змеи).
public class MindHud : MonoBehaviour
{
static GUIStyle _style;
public static void Create()
{
var go = new GameObject("NecroMindHud");
DontDestroyOnLoad(go);
go.AddComponent();
}
void OnGUI()
{
if (!MindControl.Controlling || Event.current.type != EventType.Repaint)
{
return;
}
EntityPlayerLocal player = GameManager.Instance?.World?.GetPrimaryPlayer();
if (player == null || (player.PlayerUI != null && player.PlayerUI.windowManager.IsModalWindowOpen()))
{
return;
}
float s = 4f * Screen.height / 1080f;
Color old = GUI.color;
GUI.color = MindControl.HasFocus ? new Color(1f, 0.85f, 0.5f, 1f) : new Color(1f, 0.95f, 0.85f, 0.7f);
GUI.DrawTexture(new Rect(Screen.width * 0.5f - s * 0.5f, Screen.height * 0.5f - s * 0.5f, s, s), Texture2D.whiteTexture);
if (MindControl.HasFocus)
{
if (_style == null)
{
_style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontStyle = FontStyle.Bold };
}
_style.fontSize = Mathf.RoundToInt(26f * Screen.height / 1080f);
// E в игре привязана к PermanentActions.Activate; у обычного Activate привязок нет - он даёт «Нет».
string key = KeyName(player.playerInput?.PermanentActions?.Activate) ?? KeyName(player.playerInput?.Activate) ?? "E";
string text = string.Format(Localization.Get("necroMemoryBookPrompt"), key);
Rect r = new Rect(0f, Screen.height * 0.5f + 30f * Screen.height / 1080f, Screen.width, 40f * Screen.height / 1080f);
GUI.color = new Color(0f, 0f, 0f, 0.8f);
GUI.Label(new Rect(r.x + 2f, r.y + 2f, r.width, r.height), text, _style);
GUI.color = new Color(1f, 0.9f, 0.7f, 1f);
GUI.Label(r, text, _style);
}
GUI.color = old;
}
private static string KeyName(InControl.PlayerAction action)
{
if (action == null || action.Bindings.Count == 0) return null;
string name = action.GetBindingString(false);
return string.IsNullOrEmpty(name) ? null : name;
}
}
/// necrodream hot|blur|drunk N - подобрать силу сонных эффектов на ходу.
[UnityEngine.Scripting.Preserve]
public class ConsoleCmdNecroDream : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrodream" };
public override string getDescription() => "NecromancerTome: сила эффектов сна - hot|blur|drunk N (0-1)";
public override void Execute(System.Collections.Generic.List _params, CommandSenderInfo _senderInfo)
{
if (_params.Count == 2 && float.TryParse(_params[1], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float v))
{
switch (_params[0])
{
case "hot": MindControl.Hot = v; break;
case "blur": MindControl.Blur = v; break;
case "drunk": MindControl.Drunk = v; break;
}
if (MindControl.Controlling)
{
MindControl.Dream(GameManager.Instance.World.GetPrimaryPlayer(), true);
}
}
SdtdConsole.Instance.Output("сон: hot " + MindControl.Hot + ", blur " + MindControl.Blur + ", drunk " + MindControl.Drunk);
}
}
}