Одержимость питомцев: роли медведя, волка, собаки и крысы; ноша стервятника; череп при обыске

- Могильная крыса: своя модель без волчьей шерсти; стоит на земле (центр капсулы);
  E - осмотр контейнеров без права взять (окно игры, рюкзак скрыт, отказ «инвентарь
  заполнен», контейнер не разрушается); ПКМ - проход сквозь постройки, полупрозрачная.
- Стервятник: курица в когтях - слепок с обвисшими крыльями и шеей, без хитбокса.
- Зомбособака: нюх - все существа рядом на компасе и карте.
- Медведь: толстая шкура (вдвое меньше урона), рёв-провокация, удар лапой по блокам.
- Лютоволк: бросок на цель с опрокидыванием, след крови раненых, сумка на 3 ячейки
  (лут, выгрузка у тела, выпадает мешком при гибели и отзыве; отдельный файл сейва).
- Череп (останки): 2% при обычном обыске красного трупа вместо браслета.
- Прицел книги не рисуется в одержимости; подсказки вселения на 13 языках.
- Замер EntityLeakProbe: удалённые, но живые в памяти сущности - в лог.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-09 00:58:40 +03:00
co-authored by Claude Opus 5.5
parent beab1640b0
commit 31aa99860c
25 changed files with 2461 additions and 210 deletions
+317 -30
View File
@@ -86,6 +86,29 @@ namespace NecromancerTome
/// <summary>Звук попадания рывка - ванильный удар рукой зомби по телу (sounds.xml).</summary>
public const string FlyDashHitSound = "organichitorganic";
static EntityAlive _flyDash;
/// <summary>Ноша стервятника (указание 08.10): рывок на курицу или кролика (EntityAnimalRabbit - только
/// они; злая курица - другой класс) не клюёт, а хватает. Несёт под лапами, его ИИ стоит; повторная ЛКМ -
/// отпускает (падает сам; у земли - стоит на ней). Курицу так приносят в загон, где игрок её ловит
/// для курятника (wildChicken).</summary>
static EntityAlive _carry;
/// <summary>«Зоркий глаз» (указание 08.10): пока держишь ПКМ, камера уходит к голове, а угол обзора сужается в ZoomFactor раз
/// за ZoomTime, как у прицела; мышь во столько же раз медленнее. Свой угол игра ставит только на
/// своём приближении (vp_FPCamera), поэтому исходный запоминаем и возвращаем сами.</summary>
public const float ZoomFactor = 8f; // x8 - указание 08.10 (было x4)
public const float ZoomTime = 0.2f;
/// <summary>Камера в приближении - на столько впереди головы, м (вне собственного клюва).</summary>
public const float ZoomEyeForward = 0.3f;
static bool _flyZoomHeld;
static float _zoom;
static float _zoomBaseFov = -1f;
static Camera _zoomCam;
/// <summary>Угол обзора «зоркого глаза», -1 - нет. Ставит Patch_EntityPlayerLocal_LateUpdate_Possession:
/// игра каждый кадр задаёт свой угол в EntityPlayerLocal.LateUpdate (тест 08.10: «x8 не работает» -
/// угол из PlaceCamera затирался).</summary>
public static float ZoomFov = -1f;
/// <summary>Зазор между когтями стервятника и спиной ноши, м; минус - когти впиваются в спину
/// (тест 08.10: «курица висит под стервятником, а не в его когтях»).</summary>
public const float CarryGap = -0.06f;
/// <summary>Цель рывка под прицелом сейчас (каждый кадр) - обычный прицел игры краснеет (AimTint).</summary>
public static EntityAlive FlyAim;
static float _flyDashUntil;
@@ -190,6 +213,12 @@ namespace NecromancerTome
static GameObject _hud;
/// <summary>Метка одержимого на карте (указание 06.10, как у змеи) - NecroMapMarker.</summary>
static NecroMapMarker.Marker _mapNav;
/// <summary>Разведка стервятника (указание 08.10: «карта следует за ним как за игроком»; только он, не
/// другие одержимые): туман карты открывается вокруг него так же, как вокруг игрока (EntityPlayer.OnUpdateLive: при смене чанка
/// ChunkObserver.mapDatabase.Add - 4 чанка во все стороны), а карта открывается на нём
/// (Patch_XUiC_MapArea_*_Possession). Это обычная карта игрока - сохраняется как у него.</summary>
static Vector3i _mapChunk;
static bool _mapChunkSet;
public static void Init()
{
@@ -204,6 +233,11 @@ namespace NecromancerTome
public static bool IsPossessed(Entity _e) => Active && _e != null && Zombie != null && _e.entityId == Zombie.entityId;
/// <summary>Несёт ли стервятник это существо (его ИИ стоит).</summary>
public static bool IsCarried(Entity _e) => _carry != null && _e != null && _e.entityId == _carry.entityId;
static bool Carryable(EntityAlive _e) => _e is EntityAnimalRabbit;
/// <summary>Только зомби-гуманоиды (решение 03.10: «одержимая собака или стервятник - такое себе»).</summary>
public static bool CanPossess(EntityAlive _e) => _e is EntityZombie && !_e.IsDead() && HandHolds.IsZombie(_e);
@@ -273,7 +307,8 @@ namespace NecromancerTome
ReadRanged(_zombie);
Hint(_player0, _zombie);
NecroMapMarker.Hide(ref _mapNav); // старая ссылка после закрытия мира - снимется, только если ещё наша
_mapNav = NecroMapMarker.Show(_zombie, NecroMapMarker.PossessedSprite);
_mapNav = NecroMapMarker.Show(_zombie, NecroMapMarker.PossessedClass);
_mapChunkSet = false;
if (Flying)
{
_zombie.motion = Vector3.zero;
@@ -289,6 +324,10 @@ namespace NecromancerTome
return;
}
Active = false;
RatLoot.CloseIfViewing();
RatPhase.Stop(GameManager.Instance?.World, Zombie, "выход из одержимости");
DogSense.Clear();
WolfControl.Clear();
NecroMapMarker.Hide(ref _mapNav);
bool pet = IsPet;
IsPet = false;
@@ -302,7 +341,10 @@ namespace NecromancerTome
_flyDash = null;
FlyAim = null;
AimTint(false);
DropCarry("выход из стервятника");
ZoomReset();
StopEat(z, null);
VultureNeck.Reset();
if (_flySitting)
{
_flySitting = false;
@@ -395,6 +437,15 @@ namespace NecromancerTome
static void OnUnityUpdate(ref ModEvents.SUnityUpdateData _data)
{
World world = GameManager.Instance?.World;
// Замер утечки сущностей (09.10) - не связан с одержимостью, просто ежекадровый крючок мода.
if (world == null)
{
EntityLeakProbe.Reset();
}
else
{
EntityLeakProbe.Tick();
}
if (world == null)
{
if (Active || Zombie != null || _frozen.Count > 0)
@@ -412,6 +463,16 @@ namespace NecromancerTome
_frozenSpeed.Clear();
_flyDash = null;
FlyAim = null;
_carry = null;
CarryVisual.Clear();
RatPhase.On = false;
DogSense.Clear();
WolfControl.Clear();
WolfBag.Forget();
_zoomCam = null;
_zoomBaseFov = -1f;
_zoom = 0f;
ZoomFov = -1f;
_eating = false;
_eatCorpse = null;
}
@@ -434,6 +495,18 @@ namespace NecromancerTome
Exit("одержимый погиб");
return;
}
if (Flying)
{
RevealMap(world, z); // разведка - только стервятник (указание 08.10)
}
// Подсказка тела (тест 08.10: «стоял перед дверью - подсказка осталась в стервятнике»): её гасит
// PlayerMoveController.Update, а он на время одержимости выключен. Прочие сообщения (голод и т.п.) -
// другие окна, их не трогаем.
LocalPlayerUI ui = _player.PlayerUI;
if (ui != null && ui.windowManager.IsWindowOpen(XUiC_InteractionPrompt.ID))
{
XUiC_InteractionPrompt.SetText(ui, null);
}
PlayerActionsLocal input = _player.playerInput;
bool gui = _player.PlayerUI != null && _player.PlayerUI.windowManager.IsModalWindowOpen();
if (input != null && !gui && (input.Reload.WasPressed || input.PermanentActions.Reload.WasPressed))
@@ -461,20 +534,8 @@ namespace NecromancerTome
_crouchToggled = !_crouchToggled;
}
Crouching = input != null && !gui && (input.Crouch.IsPressed || _crouchToggled);
if (detonate && IsPet)
{
// E у питомца - отзыв (как E с книгой в руке); выход из тела - внутри RecallPet.
EntityPlayerLocal owner = _player;
if (Patch_ItemActionSpawnEntity_Spawn_PetLimit.RecallPossessedPet(owner, z))
{
// Звук отзыва - как у E с книгой (soundWarn книги в руке; вселялись с ней же).
if (owner.inventory?.holdingItem?.Actions?.Length > 0 && owner.inventory.holdingItem.Actions[0] is ItemActionSpawnEntity spawn)
{
owner.PlayOneShot(spawn.soundWarn);
}
return;
}
}
// E у питомца больше не отзыв (указание 08.10: «R - всегда вернуться в тело у всех одержимых,
// E - для других целей»). Отозвать питомца - E с книгой в руке, уже из тела.
if (detonate && z is EntityZombieCop cop && !cop.isPrimed)
{
Debug.Log("[NecromancerTome] Possession: подрыв " + z.EntityName);
@@ -490,7 +551,15 @@ namespace NecromancerTome
_flyUp = input != null && !gui && input.Jump.IsPressed;
_flyDown = input != null && !gui && (input.Crouch.IsPressed || _crouchToggled);
Crouching = false;
FlyAim = _flyDash == null ? FlyDashTarget(world, z) : null;
if (attack && _carry != null)
{
// Повторная ЛКМ с ношей - отпустить (не клевок и не новый рывок).
DropCarry("ЛКМ");
attack = false;
_nextAttack = Time.time + FlyAttackDelay;
}
HoldCarry(z);
FlyAim = _flyDash == null && _carry == null ? FlyDashTarget(world, z) : null;
AimTint(FlyAim != null);
if (attack && Time.time >= _nextAttack && FlyAim is EntityAlive dashTo)
{
@@ -519,16 +588,20 @@ namespace NecromancerTome
_nextAttack = Time.time + FlyAttackDelay;
_meleeLookUntil = Time.time + FlyAttackDelay;
}
// ПКМ - клич-приманка (указание 06.10): крик крикуньи, только голосом паникующей курицы - зомби
// в ScreamRange идут на него; с воздуха уводит толпу от базы или от тела.
if (ranged)
// F - клич-приманка (указание 06.10; кнопка - 08.10, была ПКМ): крик крикуньи, только голосом
// паникующей курицы - зомби в ScreamRange идут на него; с воздуха уводит толпу от базы или от тела.
// F у игрока - фонарик, но его читает только выключенный PlayerMoveController.Update.
bool scream = input != null && !gui && (input.PermanentActions.ToggleFlashlight.WasPressed || input.ToggleFlashlight.WasPressed);
if (scream)
{
Scream(world, z);
}
// F - питаться падалью (указание 06.10: «сесть на труп - по F стервятник питается»). F у игрока -
// фонарик, но его читает только PlayerMoveController.Update, а он на время одержимости выключен.
bool eat = input != null && !gui && (input.PermanentActions.ToggleFlashlight.WasPressed || input.ToggleFlashlight.WasPressed);
Eat(world, z, eat, attack || ranged);
// ПКМ (держать) - «зоркий глаз» (указание 08.10: «приближение как у прицела»), в PlaceCamera.
_flyZoomHeld = rangedHeld;
// E - питаться падалью (указание 06.10, кнопка - 08.10: «пусть ест по кнопке E» - у игрока E это
// взаимодействие, лут; было F). detonate - это и есть нажатие E.
bool eat = detonate;
Eat(world, z, eat, attack || scream);
if (_meleeLookUntil > 0f && Time.time >= _meleeLookUntil)
{
_meleeLookUntil = 0f;
@@ -559,6 +632,32 @@ namespace NecromancerTome
z.Jumping = true;
z.SetJumpDistance(Mathf.Max(1.5f, z.jumpMaxDistance), 1.2f);
}
if (IsPet && RatModel.Is(z))
{
// E - осмотреть контейнер без права взять (указание 08.10: крыса - разведчик).
RatLoot.Tick(world, _player, z, detonate);
// ПКМ (держать) - сквозь постройки, без полёта (BACKLOG, «Могильная крыса» п.4).
RatPhase.Tick(world, z, rangedHeld);
}
if (IsPet && WolfControl.Is(z))
{
// ПКМ - бросок на цель, след крови раненых (решено 09.10).
WolfControl.Tick(world, _player, z, ranged, ref _bodyYaw);
// E: у тела - выгрузить сумку в рюкзак, у контейнера - лутать в сумку (3 ячейки).
if (!(detonate && WolfBag.TryUnload(_player, z)))
{
RatLoot.Tick(world, _player, z, detonate, true);
}
}
if (ranged && IsPet && BearControl.Is(z))
{
BearControl.Roar(world, z); // ПКМ - рёв-провокация (решено 09.10)
}
if (IsPet && DogSense.Is(z))
{
// Нюх (указание 09.10): все существа рядом с собакой - на компасе и карте.
DogSense.Tick(world, z, Time.deltaTime);
}
if (ranged && IsScreamer(z))
{
Scream(world, z);
@@ -569,6 +668,10 @@ namespace NecromancerTome
if (z.Attack(false))
{
z.Attack(true);
if (IsPet && BearControl.Is(z))
{
BearControl.Smash(world, _player, z); // удар лапой и по блоку (решено 09.10)
}
float timeout = Mathf.Max(0.3f, z.GetAttackTimeoutTicks() / 20f);
_nextAttack = Time.time + timeout;
_meleeLookUntil = Time.time + Mathf.Max(0.6f, timeout);
@@ -975,6 +1078,22 @@ namespace NecromancerTome
_v.motion = Vector3.zero;
_v.SetLookPosition(head);
_v.emodel?.avatarController?.StartAnimationAttack();
if (Carryable(t))
{
// Курица или кролик - не клевок, а захват: ИИ встаёт (updateTasks пропущен), путь сброшен.
_carry = t;
t.SetAttackTarget(null, 0);
t.SetRevengeTarget(null);
PathFinderThread.Instance.RemovePathsFor(t.entityId);
t.navigator?.clearPath();
t.moveHelper?.Stop();
t.SetRotation(new Vector3(0f, _bodyYaw, 0f));
t.transform.rotation = Quaternion.Euler(0f, _bodyYaw, 0f);
CarryVisual.Begin(t);
HoldCarry(_v);
Debug.Log("[NecromancerTome] Possession: стервятник схватил " + t.EntityName + " (" + t.entityId + ")");
return;
}
string snd = _v.GetSoundAttack();
if (!string.IsNullOrEmpty(snd))
{
@@ -1001,6 +1120,45 @@ namespace NecromancerTome
(t.IsDead() ? ", убит" : ", здоровье " + t.Health));
}
/// <summary>Ноша - под лапами стервятника, каждый кадр: верх слепка ноши на CarryGap ниже его ног
/// (CarryVisual). Ниже уже земля (схватил у земли, сел) - ноша стоит на земле под ним, но не отпущена.</summary>
static void HoldCarry(EntityAlive _v)
{
EntityAlive c = _carry;
if (c == null)
{
return;
}
if (c.IsDead() || c.IsMarkedForUnload())
{
_carry = null;
CarryVisual.End(c);
Debug.Log("[NecromancerTome] Possession: ноша погибла");
return;
}
CarryVisual.Place(_v, c, CarryGap);
c.SetRotation(new Vector3(0f, _bodyYaw, 0f));
c.motion = Vector3.zero;
c.fallDistance = 0f;
}
/// <summary>Отпустить ношу: ИИ снова идёт, падает она сама (с высоты - может разбиться).</summary>
static void DropCarry(string _why)
{
EntityAlive c = _carry;
if (c == null)
{
return;
}
_carry = null;
CarryVisual.End(c);
if (!c.IsDead())
{
c.motion = Vector3.zero;
}
Debug.Log("[NecromancerTome] Possession: стервятник отпустил " + c.EntityName + " - " + _why);
}
/// <summary>Цель рывка: ближайшая к стервятнику по лучу прицела, не дальше FlyDashRange от него и
/// не дальше TargetRadius змеи от луча. Свои (питомцы, игроки, зачарованные) - не цель.</summary>
static EntityAlive FlyDashTarget(World _world, EntityAlive _v)
@@ -1116,10 +1274,6 @@ namespace NecromancerTome
static void Hint(EntityPlayerLocal _p, EntityAlive _z)
{
string text = Localization.Get("necroPossessHintBase");
if (IsPet)
{
text += ", " + Localization.Get("necroPetRecallHint");
}
if (Flying)
{
text += ", " + Localization.Get("necroPossessHintFly");
@@ -1128,14 +1282,36 @@ namespace NecromancerTome
{
text += ", " + Localization.Get("necroPossessHintRanged");
}
if (IsScreamer(_z) || Flying)
if (IsScreamer(_z))
{
text += ", " + Localization.Get("necroPossessHintScream");
}
if (Flying)
{
text += ", " + Localization.Get("necroPossessHintZoom") + ", " + Localization.Get("necroPossessHintScreamF");
}
if (Flying)
{
text += ", " + Localization.Get("necroPossessHintEat");
}
if (IsPet && RatModel.Is(_z))
{
text += ", " + Localization.Get("necroPossessHintLook") + ", " + Localization.Get("necroPossessHintPhase");
}
if (IsPet && DogSense.Is(_z))
{
text += ", " + Localization.Get("necroPossessHintSmell");
}
if (IsPet && WolfControl.Is(_z))
{
text += ", " + Localization.Get("necroPossessHintPounce") + ", " + Localization.Get("necroPossessHintBlood") + ", " +
Localization.Get("necroPossessHintWolfBag");
}
if (IsPet && BearControl.Is(_z))
{
text += ", " + Localization.Get("necroPossessHintRoar") + ", " + Localization.Get("necroPossessHintSmash") + ", " +
Localization.Get("necroPossessHintSkin");
}
if (_z.jumpMaxDistance >= 5f)
{
text += ", " + Localization.Get("necroPossessHintLeap");
@@ -1144,6 +1320,11 @@ namespace NecromancerTome
{
text += ", " + Localization.Get("necroPossessHintDetonate");
}
if (!IsPet)
{
// Решение 03.10, подтверждено 09.10: после выхода бывший одержимый в леденящем ужасе бежит от тела.
text += ", " + Localization.Get("necroPossessHintAfterFear");
}
GameManager.ShowTooltip(_p, text);
}
@@ -1355,6 +1536,10 @@ namespace NecromancerTome
PlayerMoveController pmc = PlayerMoveController.Instance;
Vector2 sens = pmc != null ? pmc.mouseLookSensitivity : Vector2.one * 5f;
int invert = pmc != null && pmc.invertMouse != 0 ? pmc.invertMouse : 1;
if (_zoomCam != null && _zoomBaseFov > 0f)
{
sens *= _zoomCam.fieldOfView / _zoomBaseFov; // в «зорком глазе» мышь медленнее, как в прицеле
}
_yaw += Input.GetAxis("Mouse X") * sens.x;
_pitch = Mathf.Clamp(_pitch + Input.GetAxis("Mouse Y") * sens.y * invert, -70f, 70f);
}
@@ -1404,7 +1589,7 @@ namespace NecromancerTome
/// <summary>Разрешить зомби подход к цели класса одержимого: EntityZombie, у питомцев - их класс
/// (EntityZombieDog, EntityVulture).</summary>
static void AllowZombieTarget(EntityAlive _e, EntityAlive _target)
internal static void AllowZombieTarget(EntityAlive _e, EntityAlive _target)
{
System.Type type = _target is EntityZombie ? typeof(EntityZombie) : _target.GetType();
List<EAIApproachAndAttackTarget> tasks = _e.aiManager.GetTasks<EAIApproachAndAttackTarget>();
@@ -1426,6 +1611,54 @@ namespace NecromancerTome
}
}
/// <summary>Открыть карту вокруг одержимого - как игра открывает её вокруг игрока, при смене чанка.</summary>
static void RevealMap(World _world, EntityAlive _z)
{
IMapChunkDatabase map = _player.ChunkObserver?.mapDatabase;
if (map == null || !_z.IsSpawned() || _mapChunkSet && _z.chunkPosAddedEntityTo == _mapChunk)
{
return;
}
_mapChunk = _z.chunkPosAddedEntityTo;
_mapChunkSet = true;
map.Add(_mapChunk, _world);
}
/// <summary>Шаг «зоркого глаза» за кадр; отпустил - угол плавно возвращается и восстанавливается точно.</summary>
static void Zoom(bool _held)
{
Camera cam = _player != null ? _player.playerCamera : null;
if (_held && cam != null && _zoomBaseFov < 0f)
{
_zoomCam = cam;
_zoomBaseFov = cam.fieldOfView;
}
if (_zoomBaseFov < 0f)
{
return;
}
_zoom = Mathf.MoveTowards(_zoom, _held ? 1f : 0f, Time.deltaTime / ZoomTime);
if (_zoom <= 0f)
{
ZoomReset();
return;
}
ZoomFov = Mathf.Lerp(_zoomBaseFov, _zoomBaseFov / ZoomFactor, Mathf.SmoothStep(0f, 1f, _zoom));
}
static void ZoomReset()
{
if (_zoomCam != null && _zoomBaseFov > 0f)
{
_zoomCam.fieldOfView = _zoomBaseFov;
}
_zoomCam = null;
_zoomBaseFov = -1f;
_zoom = 0f;
ZoomFov = -1f;
_flyZoomHeld = false;
}
/// <summary>Из постфикса vp_FPCamera.LateUpdate: камера позади и выше головы зомби, не сквозь стены.</summary>
public static void PlaceCamera(Transform _camera)
{
@@ -1434,6 +1667,12 @@ namespace NecromancerTome
{
return;
}
if (Flying)
{
// Шея - до камеры: та берёт положение головы.
VultureNeck.Apply(z, _flySitting, !_eating);
}
Zoom(Flying && _flyZoomHeld);
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);
@@ -1443,6 +1682,12 @@ namespace NecromancerTome
{
eye = anchor + to.normalized * Mathf.Max(0.2f, hit.distance - 0.2f);
}
if (_zoom > 0f)
{
// В «зорком глазе» камера уходит к голове (чуть впереди неё): из-за спины при x8 весь кадр
// закрыла бы сама птица.
eye = Vector3.Lerp(eye, anchor + rot * Vector3.forward * ZoomEyeForward, Mathf.SmoothStep(0f, 1f, _zoom));
}
_camera.SetPositionAndRotation(eye, rot);
if (!_loggedCamera)
{
@@ -1458,7 +1703,7 @@ namespace NecromancerTome
{
public static bool Prefix(EntityAlive __instance)
{
return !Possession.IsPossessed(__instance);
return !Possession.IsPossessed(__instance) && !Possession.IsCarried(__instance);
}
}
@@ -1478,6 +1723,48 @@ namespace NecromancerTome
}
}
/// <summary>Карта в одержимом стервятнике открывается на нём, а не на теле (указание 08.10: разведка -
/// только у стервятника).</summary>
[HarmonyPatch(typeof(XUiC_MapArea), nameof(XUiC_MapArea.OnOpen))]
public static class Patch_XUiC_MapArea_OnOpen_Possession
{
public static void Postfix(XUiC_MapArea __instance)
{
if (Possession.Active && Possession.Flying && Possession.Zombie != null)
{
__instance.PositionMapAt(Possession.Zombie.GetPosition());
}
}
}
/// <summary>Кнопка «к игроку» на карте при одержимости - к одержимому.</summary>
[HarmonyPatch(typeof(XUiC_MapArea), nameof(XUiC_MapArea.onPlayerIconPressed))]
public static class Patch_XUiC_MapArea_onPlayerIconPressed_Possession
{
public static bool Prefix(XUiC_MapArea __instance)
{
if (!Possession.Active || !Possession.Flying || Possession.Zombie == null)
{
return true;
}
__instance.PositionMapAt(Possession.Zombie.GetPosition());
return false;
}
}
/// <summary>«Зоркий глаз» стервятника: свой угол обзора - после того, как игра поставила свой.</summary>
[HarmonyPatch(typeof(EntityPlayerLocal), "LateUpdate")]
public static class Patch_EntityPlayerLocal_LateUpdate_Possession
{
public static void Postfix(EntityPlayerLocal __instance)
{
if (Possession.ZoomFov > 0f && __instance == Possession.Player && __instance.playerCamera != null)
{
__instance.playerCamera.fieldOfView = Possession.ZoomFov;
}
}
}
/// <summary>Тело оцепенело: ввод игрока не идёт (как у тела в рэгдолле - stopMoving и выход).</summary>
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
public static class Patch_PlayerMoveController_Update_Possession