diff --git a/Config/Localization.csv b/Config/Localization.csv
index e9d0242..de2b146 100644
--- a/Config/Localization.csv
+++ b/Config/Localization.csv
@@ -221,6 +221,7 @@ necroHoldNone,items,Item,,,,Bare hand,,Bloße Hand,Mano desnuda,Main nue,Mano nu
necroHoldDeny,items,Item,,,,Horns,,Pommesgabel,Cuernos,Cornes,Corna,メロイックサイン,악마의 뿔,Rogi,Chifres,Коза,Boynuzlar,恶魔角,惡魔角
necroHoldPifPaf,items,Item,,,,Bang-bang,,Peng-peng,Pum-pum,Pan-pan,Bang-bang,バンバン,빵야빵야,Pif-paf,Pou-pou,Пиф-паф,Bang-bang,砰砰,砰砰
necroPossessHintBase,items,Item,,,,"LMB - strike, R - leave the body",,"LMT - Schlag, R - Körper verlassen","LMB: golpe, R: salir del cuerpo","Clic gauche : frapper, R : quitter le corps","LMB - colpo, R - lascia il corpo",左クリック: 攻撃、R: 体から出る,"좌클릭 - 공격, R - 몸에서 나가기","LPM - cios, R - opuść ciało","LMB - golpe, R - sair do corpo","ЛКМ - удар, R - выйти из тела","Sol tık - vuruş, R - bedenden çık",左键 - 攻击,R - 离开身体,左鍵 - 攻擊,R - 離開身體
+necroPossessHintView,items,Item,,,,~ - first-person view,,~ - Ich-Perspektive,~: vista en primera persona,~ : vue à la première personne,~ - visuale in prima persona,~: 一人称視点,~ - 1인칭 시점,~ - widok z pierwszej osoby,~ - visão em primeira pessoa,~ (Ё) - вид от первого лица,~ - birinci şahıs görünümü,~ - 第一人称视角,~ - 第一人稱視角
necroPossessHintRanged,items,Item,,,,RMB - ranged attack,,RMT - Fernangriff,RMB: ataque a distancia,Clic droit : attaque à distance,RMB - attacco a distanza,右クリック: 遠距離攻撃,우클릭 - 원거리 공격,PPM - atak dystansowy,RMB - ataque à distância,ПКМ - дальняя атака,Sağ tık - menzilli saldırı,右键 - 远程攻击,右鍵 - 遠程攻擊
necroPossessHintDetonate,items,Item,,,,E - detonate,,E - Sprengen,E: detonar,E : exploser,E - detonare,E: 起爆,E - 폭발,E - detonacja,E - detonar,E - подрыв,E - patlat,E - 引爆,E - 引爆
necroPossessHintScream,items,Item,,,,E - scream (summons a horde under Deviation),,E - Schrei (ruft eine Horde unter Abweichung),E: grito (invoca una horda bajo desviación),E : cri (appelle une horde sous déviation),E - urlo (evoca un'orda sotto deviazione),E: 叫び(逸脱したホードを呼ぶ),E - 비명 (일탈한 무리를 부름),E - krzyk (wzywa hordę pod dewiacją),E - grito (invoca uma horda sob desvio),E - крик (созвать орду под девиацией),E - çığlık (sapkın bir sürü çağırır),E - 尖叫(召唤逸脱的尸潮),E - 尖叫(召喚逸脫的屍潮)
diff --git a/HarmonySrc/Possession.cs b/HarmonySrc/Possession.cs
index c78bd54..d2e03c4 100644
--- a/HarmonySrc/Possession.cs
+++ b/HarmonySrc/Possession.cs
@@ -143,6 +143,14 @@ namespace NecromancerTome
/// Камера - позади и выше головы зомби.
public const float CamBack = 2.6f;
+ /// Вид от первого лица (указание 13.10): клавиша ~/ё - как переключение вида у персонажа в игре
+ /// (QuickMenu, по умолчанию Backquote; и CameraChange геймпада), каждое вселение начинается от третьего лица. Камера - в голове, голова сжата
+ /// (HideHead), положение глаз сглажено (FpSmooth) - голова зомби на ходу мотается.
+ public static bool FirstPerson;
+ public const float FpForward = 0.12f, FpSmooth = 12f;
+ static Vector3 _fpEye;
+ static Transform _hiddenHead;
+ static Vector3 _hiddenHeadScale;
public const float CamUp = 0.6f;
/// Как далеко зомби замечают одержимого: зрение зомби - 30 м (entityclasses.xml SightRange).
public const float TauntRange = 30f;
@@ -296,6 +304,7 @@ namespace NecromancerTome
_nextTaunt = 0f;
_nextScream = 0f;
_loggedCamera = false;
+ FirstPerson = false;
// Пояс одержимому питомцу ни к чему (указание 06.10); открыться заново не даёт
// Patch_GUIWindowManager_openInternal_SnakeToolbelt, после выхода игра откроет его сама.
if (_pet)
@@ -344,6 +353,8 @@ namespace NecromancerTome
return;
}
Active = false;
+ FirstPerson = false;
+ HideHead(null);
RatLoot.CloseIfViewing();
WolfBag.CloseWindow();
RatPhase.Stop(GameManager.Instance?.World, Zombie, "выход из одержимости");
@@ -562,6 +573,16 @@ namespace NecromancerTome
// кнопка геймпада (тест 03.10: «подрыва нету»).
detonate = input.Activate.WasPressed || input.PermanentActions.Activate.WasPressed;
}
+ if (input != null && !gui && (input.CameraChange.WasPressed || input.QuickMenu.WasPressed || input.PermanentActions.QuickMenu.WasPressed))
+ {
+ FirstPerson = !FirstPerson;
+ _fpEye = Vector3.zero;
+ if (!FirstPerson)
+ {
+ HideHead(null);
+ }
+ Debug.Log("[NecromancerTome] Possession: вид от " + (FirstPerson ? "первого" : "третьего") + " лица");
+ }
if (input != null && !gui && input.ToggleCrouch.WasPressed)
{
_crouchToggled = !_crouchToggled;
@@ -1449,7 +1470,7 @@ namespace NecromancerTome
/// Подсказка при вселении: что умеет этот зомби.
static void Hint(EntityPlayerLocal _p, EntityAlive _z)
{
- string text = Localization.Get("necroPossessHintBase");
+ string text = Localization.Get("necroPossessHintBase") + ", " + Localization.Get("necroPossessHintView");
if (Flying)
{
text += ", " + Localization.Get("necroPossessHintFly");
@@ -1570,7 +1591,7 @@ namespace NecromancerTome
for (int i = 0; i < n; i++)
{
RaycastHit hit = _aimHits[i];
- if (hit.distance <= CamBack + 0.5f || hit.distance >= best ||
+ if (hit.distance <= (FirstPerson ? 0.1f : CamBack + 0.5f) || hit.distance >= best ||
self != null && (hit.transform == self || hit.transform.IsChildOf(self)))
{
continue;
@@ -2003,6 +2024,28 @@ namespace NecromancerTome
_flyZoomHeld = false;
}
+ /// Голова одержимого сжата почти в точку, пока вид от первого лица, - иначе камера внутри черепа
+ /// (у зомби модель цельная, отдельных рук, как у персонажа, нет). Ставится каждый кадр после аниматора;
+ /// null - вернуть прежний масштаб.
+ static void HideHead(Transform _head)
+ {
+ if (_hiddenHead != null && _hiddenHead != _head)
+ {
+ _hiddenHead.localScale = _hiddenHeadScale;
+ _hiddenHead = null;
+ }
+ if (_head == null)
+ {
+ return;
+ }
+ if (_hiddenHead == null)
+ {
+ _hiddenHead = _head;
+ _hiddenHeadScale = _head.localScale;
+ }
+ _head.localScale = Vector3.one * 0.001f;
+ }
+
/// Из постфикса vp_FPCamera.LateUpdate: камера позади и выше головы зомби, не сквозь стены.
public static void PlaceCamera(Transform _camera)
{
@@ -2020,6 +2063,18 @@ namespace NecromancerTome
Transform head = z.emodel != null ? z.emodel.GetHeadTransform() : null;
Vector3 anchor = head != null ? head.position : z.position - Origin.position + Vector3.up * 1.6f;
Quaternion rot = Quaternion.Euler(-_pitch, _yaw, 0f);
+ if (FirstPerson)
+ {
+ // Положение головы - уже после аниматора (мы в LateUpdate камеры), до сжатия: сжатие масштаба
+ // самой кости её положения не меняет.
+ Vector3 want = anchor + rot * Vector3.forward * FpForward;
+ _fpEye = _fpEye == Vector3.zero || (want - _fpEye).sqrMagnitude > 4f
+ ? want
+ : Vector3.Lerp(_fpEye, want, 1f - Mathf.Exp(-FpSmooth * Time.deltaTime));
+ HideHead(head);
+ _camera.SetPositionAndRotation(_fpEye, rot);
+ return;
+ }
Vector3 eye = anchor + Vector3.up * CamUp - rot * Vector3.forward * CamBack;
Vector3 to = eye - anchor;
if (Physics.Raycast(anchor, to.normalized, out RaycastHit hit, to.magnitude + 0.2f, SnakeControl.WallMask, QueryTriggerInteraction.Ignore))
diff --git a/NecromancerHarmony.dll b/NecromancerHarmony.dll
index 81b2746..f8f0108 100644
Binary files a/NecromancerHarmony.dll and b/NecromancerHarmony.dll differ
diff --git a/NecromancerHarmony.pdb b/NecromancerHarmony.pdb
index f464227..d9ea6a3 100644
Binary files a/NecromancerHarmony.pdb and b/NecromancerHarmony.pdb differ