270 lines
11 KiB
C#
270 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using HarmonyLib;
|
|
using UnityEngine;
|
|
using UnityEngine.Scripting;
|
|
|
|
namespace NecromancerTome
|
|
{
|
|
/// <summary>
|
|
/// Осмотр рук от первого лица со стороны (16.10: «зафиксировать командой позу игрока и осматривать его камерой... хват
|
|
/// от первого лица не позволяет видеть всех нюансов»). Руки первого лица (baseRigFP) - отдельная модель под камерой
|
|
/// игрока, в виде от третьего лица их нет, а у тела свои анимации. Поэтому вид остаётся от первого лица, а поверх
|
|
/// рисует своя камера, которая облетает руки. Её место задано в осях камеры игрока: повернёшь голову - вид на руки
|
|
/// не меняется. Поза замирает (снимок всех костей, как у хватов мода).
|
|
/// Управление - цифровой блок: 4/6 вокруг, 8/2 выше/ниже, +/- ближе/дальше, 5 - сначала; или командой necroview.
|
|
/// </summary>
|
|
public static class NecroView
|
|
{
|
|
public static bool On;
|
|
public static float Yaw, Pitch, Dist = 0.6f;
|
|
/// <summary>Точка, вокруг которой летает камера (между кистями), - в осях камеры игрока.</summary>
|
|
static Vector3 _target;
|
|
static Camera _cam;
|
|
const float TurnSpeed = 60f, ZoomSpeed = 0.4f;
|
|
/// <summary>Мышь облетает руки, персонаж стоит (16.10: «я поворачиваю мышь, а туловище проворачивается следом, и я
|
|
/// вижу тело только с одной позиции»). Поворот игрока каждый кадр возвращается к снимку при включении.</summary>
|
|
public static bool Mouse = true;
|
|
public static float MouseSpeed = 3f;
|
|
static Vector3 _lockRot;
|
|
static Quaternion _lockCam;
|
|
static bool _mouseBroken;
|
|
|
|
/// <summary>«Выйти из тела» (16.10: «временно мог выйти из тела, когда попрошу? Чтобы мог ходить вокруг тела и
|
|
/// осматривать»): камера летает свободно - WASD, мышь, пробел/Ctrl - выше/ниже, Shift - быстрее; тело стоит
|
|
/// (PlayerMoveController.Update пропускается, как при одержимости), поза рук замерла. Выход - End или necroview off.</summary>
|
|
public static bool Free;
|
|
static Vector3 _freePos;
|
|
static float _freeYaw, _freePitch;
|
|
const float FlySpeed = 1.5f;
|
|
|
|
public static string StartFree()
|
|
{
|
|
string r = Start(0f, 0f, 0.6f);
|
|
Transform pc = HandPose.Camera();
|
|
if (!On || pc == null)
|
|
{
|
|
return r;
|
|
}
|
|
Free = true;
|
|
_freePos = pc.position;
|
|
Vector3 e = pc.eulerAngles;
|
|
_freeYaw = e.y;
|
|
_freePitch = e.x > 180f ? e.x - 360f : e.x;
|
|
return "вне тела: WASD, мышь, пробел/Ctrl - выше/ниже, Shift - быстрее, End - вернуться";
|
|
}
|
|
|
|
static void UpdateFree(float _dt)
|
|
{
|
|
PlayerMoveController pmc = PlayerMoveController.Instance;
|
|
Vector2 sens = pmc != null ? pmc.mouseLookSensitivity : Vector2.one * 5f;
|
|
if (!_mouseBroken)
|
|
{
|
|
try
|
|
{
|
|
_freeYaw += Input.GetAxis("Mouse X") * sens.x;
|
|
_freePitch = Mathf.Clamp(_freePitch - Input.GetAxis("Mouse Y") * sens.y, -89f, 89f);
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
_mouseBroken = true;
|
|
Debug.LogWarning("[NecromancerTome] necroview: мышь не читается (" + e.Message + ")");
|
|
}
|
|
}
|
|
Quaternion rot = Quaternion.Euler(_freePitch, _freeYaw, 0f);
|
|
Vector3 move = Vector3.zero;
|
|
if (Input.GetKey(KeyCode.W)) move += Vector3.forward;
|
|
if (Input.GetKey(KeyCode.S)) move += Vector3.back;
|
|
if (Input.GetKey(KeyCode.A)) move += Vector3.left;
|
|
if (Input.GetKey(KeyCode.D)) move += Vector3.right;
|
|
Vector3 world = rot * move;
|
|
if (Input.GetKey(KeyCode.Space)) world += Vector3.up;
|
|
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.C)) world += Vector3.down;
|
|
float speed = FlySpeed * (Input.GetKey(KeyCode.LeftShift) ? 4f : 1f);
|
|
_freePos += world * speed * _dt;
|
|
_cam.transform.SetPositionAndRotation(_freePos, rot);
|
|
}
|
|
|
|
public static string Start(float _yaw, float _pitch, float _dist)
|
|
{
|
|
Transform pc = HandPose.Camera();
|
|
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
|
if (pc == null || pl?.playerCamera == null)
|
|
{
|
|
return "нет камеры игрока";
|
|
}
|
|
Transform l = HandPose.Bone("LeftHand"), r = HandPose.Bone("RightHand");
|
|
Vector3 mid = l != null && r != null ? (l.position + r.position) * 0.5f : pc.position + pc.forward * 0.4f;
|
|
_target = pc.InverseTransformPoint(mid);
|
|
Yaw = _yaw;
|
|
Pitch = _pitch;
|
|
Dist = _dist;
|
|
if (_cam == null)
|
|
{
|
|
GameObject go = new GameObject("NecroViewCam");
|
|
Object.DontDestroyOnLoad(go);
|
|
_cam = go.AddComponent<Camera>();
|
|
}
|
|
_cam.CopyFrom(pl.playerCamera);
|
|
// Сразу над камерой игрока, но ПОД камерами интерфейса (16.10: с +50 рисовала поверх меню - «мне из игры не выйти даже»).
|
|
float above = float.MaxValue;
|
|
foreach (Camera c in Camera.allCameras)
|
|
{
|
|
if (c != _cam && c.depth > pl.playerCamera.depth && c.depth < above)
|
|
{
|
|
above = c.depth;
|
|
}
|
|
}
|
|
_cam.depth = above < float.MaxValue ? (pl.playerCamera.depth + above) * 0.5f : pl.playerCamera.depth + 1f;
|
|
_cam.nearClipPlane = 0.01f;
|
|
_cam.gameObject.SetActive(true);
|
|
HandPose.HoldStill = true;
|
|
_lockRot = pl.rotation;
|
|
_lockCam = pc.localRotation;
|
|
On = true;
|
|
return "осмотр включён: вокруг " + Yaw + ", высота " + Pitch + ", расстояние " + Dist;
|
|
}
|
|
|
|
public static void Stop()
|
|
{
|
|
On = false;
|
|
Free = false;
|
|
HandPose.HoldStill = false;
|
|
if (_cam != null)
|
|
{
|
|
Object.Destroy(_cam.gameObject);
|
|
_cam = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>Из HandPoseDriver.LateUpdate - после позы.</summary>
|
|
public static void Update()
|
|
{
|
|
if (!On)
|
|
{
|
|
return;
|
|
}
|
|
Transform pc = HandPose.Camera();
|
|
if (pc == null || _cam == null)
|
|
{
|
|
Stop();
|
|
return;
|
|
}
|
|
float dt = Time.unscaledDeltaTime;
|
|
if (Free)
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.End))
|
|
{
|
|
Stop();
|
|
return;
|
|
}
|
|
UpdateFree(dt);
|
|
return;
|
|
}
|
|
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
|
if (Mouse && pl != null && !_mouseBroken)
|
|
{
|
|
try
|
|
{
|
|
Yaw += Input.GetAxis("Mouse X") * MouseSpeed;
|
|
Pitch = Mathf.Clamp(Pitch - Input.GetAxis("Mouse Y") * MouseSpeed, -85f, 85f);
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
_mouseBroken = true; // нет оси мыши в старом вводе - остаётся цифровой блок
|
|
Debug.LogWarning("[NecromancerTome] necroview: мышь не читается (" + e.Message + ")");
|
|
}
|
|
pl.rotation = _lockRot;
|
|
pc.localRotation = _lockCam;
|
|
}
|
|
if (Input.GetKey(KeyCode.Keypad4)) Yaw -= TurnSpeed * dt;
|
|
if (Input.GetKey(KeyCode.Keypad6)) Yaw += TurnSpeed * dt;
|
|
if (Input.GetKey(KeyCode.Keypad8)) Pitch = Mathf.Min(85f, Pitch + TurnSpeed * dt);
|
|
if (Input.GetKey(KeyCode.Keypad2)) Pitch = Mathf.Max(-85f, Pitch - TurnSpeed * dt);
|
|
if (Input.GetKey(KeyCode.KeypadPlus)) Dist = Mathf.Max(0.1f, Dist - ZoomSpeed * dt);
|
|
if (Input.GetKey(KeyCode.KeypadMinus)) Dist = Mathf.Min(3f, Dist + ZoomSpeed * dt);
|
|
if (Input.GetKeyDown(KeyCode.Keypad5))
|
|
{
|
|
Yaw = 0f;
|
|
Pitch = 0f;
|
|
Dist = 0.6f;
|
|
}
|
|
// Yaw 0, Pitch 0 - смотрим на руки со стороны глаз игрока (сзади), 180 - спереди.
|
|
Quaternion local = Quaternion.Euler(Pitch, Yaw, 0f);
|
|
Vector3 offset = local * new Vector3(0f, 0f, -Dist);
|
|
Vector3 t = pc.TransformPoint(_target);
|
|
_cam.transform.SetPositionAndRotation(t + pc.rotation * offset, pc.rotation * local);
|
|
}
|
|
|
|
public static string F(float _v) => _v.ToString("0.#", CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
/// <summary>necroview on [вокруг высота расстояние] | off | set вокруг высота расстояние | show</summary>
|
|
[Preserve]
|
|
public class ConsoleCmdNecroView : ConsoleCmdAbstract
|
|
{
|
|
public override string[] getCommands() => new[] { "necroview" };
|
|
public override string getDescription() => "NecromancerTome: осмотр рук со стороны - on [вокруг высота расстояние]|off|set вокруг высота расстояние|free (выйти из тела, End - назад)|mouse [off]|show; мышь или цифровой блок 4/6 8/2 +/- 5";
|
|
|
|
static bool Num(List<string> _p, int _i, out float _v)
|
|
{
|
|
_v = 0f;
|
|
return _p.Count > _i && float.TryParse(_p[_i].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out _v);
|
|
}
|
|
|
|
void Out(string _s)
|
|
{
|
|
SdtdConsole.Instance.Output(_s);
|
|
Debug.Log("[NecromancerTome] necroview: " + _s);
|
|
}
|
|
|
|
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
|
{
|
|
string cmd = _params.Count > 0 ? _params[0] : "";
|
|
switch (cmd)
|
|
{
|
|
case "on":
|
|
Out(NecroView.Start(Num(_params, 1, out float y) ? y : 150f, Num(_params, 2, out float p) ? p : 10f, Num(_params, 3, out float d) ? d : 0.6f));
|
|
return;
|
|
case "off":
|
|
NecroView.Stop();
|
|
Out("осмотр выключен");
|
|
return;
|
|
case "set" when NecroView.On && Num(_params, 1, out float y2) && Num(_params, 2, out float p2) && Num(_params, 3, out float d2):
|
|
NecroView.Yaw = y2;
|
|
NecroView.Pitch = p2;
|
|
NecroView.Dist = d2;
|
|
break;
|
|
case "free":
|
|
Out(NecroView.StartFree());
|
|
return;
|
|
case "mouse":
|
|
NecroView.Mouse = !(_params.Count > 1 && _params[1] == "off");
|
|
Out("мышь " + (NecroView.Mouse ? "облетает руки" : "крутит персонажа, облёт - цифровой блок"));
|
|
return;
|
|
case "show":
|
|
break;
|
|
default:
|
|
Out(getDescription());
|
|
return;
|
|
}
|
|
Out(NecroView.On ? "вокруг " + NecroView.F(NecroView.Yaw) + ", высота " + NecroView.F(NecroView.Pitch) + ", расстояние " + NecroView.F(NecroView.Dist) : "осмотр выключен");
|
|
}
|
|
}
|
|
|
|
/// <summary>Вне тела (NecroView.Free) тело не ходит и не крутится - как при одержимости.</summary>
|
|
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
|
|
public static class Patch_PlayerMoveController_Update_NecroView
|
|
{
|
|
public static bool Prefix(PlayerMoveController __instance)
|
|
{
|
|
if (!NecroView.Free)
|
|
{
|
|
return true;
|
|
}
|
|
__instance.stopMoving();
|
|
return false;
|
|
}
|
|
}
|
|
}
|