Чертоги разума, хваты пустой руки, одержимость, книга с оглавлением
- Чертоги разума: дух в префабе chertogi под землёй, книга памяти, нож и плакат; кровать префаба - мебель necroPalaceBed (иначе становилась точкой возрождения); охрана выбрасывает и игроков (их нет в EntityAlives). - Хваты пустой руки по R: «отрицание» и «пиф-паф» - поза рук костями поверх аниматора, остальные кости заморожены, ЛКМ - заклинание, прицел краснеет на зомби. - «Отрицание»: зомби в ужасе бежит дальше 40 м и теряет игрока как цель. - «Пиф-паф»: одержимость зомби-гуманоидом - бег, прыжок, удар, дальняя атака, подрыв, крик крикуньи; тело цепенеет, зомби танкуют одержимого; область вокруг тела сжата; полоска здоровья; спавн и прорисовка у одержимого. - Книга с оглавлением (окно necroTome), глава «Некроэнергия». - Прорисовка дверей у змеи и одержимого (CheckLODs от камеры, а не от тела). - Почтовый ящик консоли и команды necrobones/necropose/necroshot для разработки. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c828202c66
commit
66a0dcf403
@@ -0,0 +1,304 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Дух в чертогах разума - камера без сущности (указание 03.10: «тело можно вообще не делать,
|
||||
/// нужен только прицел»). Ходит по полу как человек: столкновения - по тем же слоям, что у змеи
|
||||
/// (SnakeControl.WallMask / TerrainMask), гравитация и ступенька. Взаимодействие одно - книга
|
||||
/// памяти по E; остальное в чертогах неприкосновенно. Камеру ставят патчи камеры режима змеи
|
||||
/// (SnakeControl.cs), звук и мир вокруг считаются от духа через SnakeAudioSwap.
|
||||
///
|
||||
/// СОН (указание: «тёплый фильтр, это как бы сон», эффекты - на моё усмотрение): готовые эффекты
|
||||
/// экрана игры - Hot (тёплый оттенок), Blur (мягкое размытие), Drunk (медленное покачивание).
|
||||
/// Силы крутятся командой necrodream на ходу.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Из патчей камеры режима змеи: глаза духа и поиск книги под прицелом.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Шагаем по лучу на ReachDistance: первая встреченная книга памяти - цель; твёрдый
|
||||
/// блок раньше неё - нет цели (сквозь стены не читаем).</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Прицел духа и подсказка у книги (OnGUI, как прицел змеи).</summary>
|
||||
public class MindHud : MonoBehaviour
|
||||
{
|
||||
static GUIStyle _style;
|
||||
|
||||
public static void Create()
|
||||
{
|
||||
var go = new GameObject("NecroMindHud");
|
||||
DontDestroyOnLoad(go);
|
||||
go.AddComponent<MindHud>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>necrodream hot|blur|drunk N - подобрать силу сонных эффектов на ходу.</summary>
|
||||
[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<string> _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user