Files
AlexCubeandClaude Opus 5.5 2cae2377e2 Руки одержимого: урон по анимации удара рук, как у персонажа; в покое руки выше, на удар - ниже
- ЛКМ от первого лица - удар рук; через RaycastTime их состояния удара (как impactStart у персонажа) зомби бьёт сразу (ForceImpact в IsAttackImpact, задержка луча снята). Замер задержки урона убран.
- Положение: в покое 0,-1.55,0.05 без сжатия; на удар - плавно к -1.7 со сжатием под FOV 45.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:41:57 +03:00

580 lines
25 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using HarmonyLib;
using System.Globalization;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Руки одержимого в виде от первого лица (указание 13.10: «делаем так, как у игрока»). У персонажа игры две
/// модели: полное тело (его видят другие и он сам со стороны) и отдельные руки для первого лица (baseRigFP,
/// SDCSUtils.CreateVizFP) - рисуются только у себя. Здесь собираем такие же руки по тем же шагам игры:
/// скелет baseRigFPPrefab с аниматором FPPlayerController, голая кожа (basePartsFP), без одежды и волос
/// (указание: «одежду пока не показываем»). Пол - по зомби (IsMale): BaseMale или BaseFemale из
/// archetypes.xml. Кожа серая (указание: «руки вообще серые - типа зомби их так видит») - цвет _Tint у
/// материала кожи, как игра красит тон кожи.
///
/// Руки - под UFPSRoot камеры игрока, как у персонажа; камеру одержимости мы ставим в голову зомби, руки
/// едут вместе с ней. Слой HoldingItem - его камера игрока рисует только при ShowHoldingItemLayer(true).
///
/// Только гуманоиды обычной комплекции (Allows), указание 13.10.
///
/// ПАМЯТЬ. Одна модель рук на время вида от первого лица; Hide выгружает её (SDCSUtils.UnloadViz - материалы
/// и ссылки на ассеты освобождаются) - на переключении вида и на выходе из одержимости.
/// </summary>
public static class FpArms
{
/// <summary>Начала имён классов, у кого вид от первого лица (варианты Feral/Radiated/Charged/Infernal - тоже).</summary>
static readonly string[] Allowed =
{
"zombieArlene", "zombieBiker", "zombieBoe", "zombieBowler", "zombieBurnt", "zombieBusinessMan", "zombieDarlene",
"zombieInmate", "zombieJanitor", "zombieJoe", "zombieLab", "zombieLumberjack", "zombieMaleHazmat", "zombieMarlene",
"zombieMoe", "zombieNurse", "zombiePartyGirl", "zombieSkateboarder", "zombieSoldier", "zombieSteve", "zombieTomClark",
"zombieUtilityWorker", "zombieYo",
};
public static Color Tint = new Color(0.6f, 0.6f, 0.6f, 1f);
/// <summary>Цветная текстура кожи (_Albedo): false - убрана, кожа ровно-серая по _Tint, рельеф - от карты нормалей
/// (указание 13.10: «руки вообще серые»; _Tint только умножает, смуглая кожа от него темнела, а не серела).</summary>
public static bool Albedo;
/// <summary>Сигнал аниматора рук на удар (подбор necroarms punch; у пустых рук игры - триггер атаки).</summary>
public static string PunchTrigger = "WeaponFire";
/// <summary>Параметры аниматора рук, выставленные necroarms set (подбор «пустых рук»), - ставятся и при пересборке.</summary>
static readonly Dictionary<string, float> _animParams = new Dictionary<string, float>
{
// Подобрано в игре 13.10: без них аниматор стоял в позе «держит мешок» (WeaponHoldType 45). 0 - пустые руки
// (HoldType у meleeHandPlayer), клип FP_Unarmed_Hold.
{ "IsAlive", 1f }, { "IsFPV", 1f }, { "WeaponHoldType", 0f },
};
/// <summary>Сдвиг рук от камеры (подбор necroarms pos). У персонажа игра опускает руки на 1.7 м: начало их
/// скелета - у ног (vp_FPWeapon PositionOffset 0,-1.7,0.02 для SDCS).</summary>
public static Vector3 Offset = new Vector3(0f, -1.7f, 0.02f);
/// <summary>Угол обзора, с которым игра рисует руки персонажа (vp_FPWeapon RenderingFieldOfView 45 у SDCS). Наши руки
/// рисует общая камера с широким углом - удар уходил выше и правее прицела (тест 13.10). Тот же вид даёт сжатие
/// рук по экранным осям: масштаб (k, k, 1) от камеры, k = tg(FOV камеры / 2) / tg(ArmsFov / 2) - каждый кадр
/// (FOV меняется при приближении).</summary>
public static float ArmsFov = 45f;
/// <summary>Положение рук в покое (подобрано 13.10, без сжатия): «руки подними как были»; на удар - Offset со
/// сжатием под ArmsFov, «анимацию удара занизь как сейчас». Переход - за BlendTime.</summary>
public static Vector3 IdleOffset = new Vector3(0f, -1.55f, 0.05f);
public const float BlendTime = 0.12f;
/// <summary>Минимальный промежуток между ударами рук.</summary>
public const float SwingTime = 0.6f;
static bool _inAttack;
static float _impactAt, _blend;
/// <summary>Родитель: true - сама камера, false - её UFPSRoot (как у персонажа).</summary>
public static bool ParentCamera = true;
static GameObject _rig;
static SDCSUtils.TransformCatalog _bones;
static bool _layerWasShown;
public static bool Active => _rig != null;
/// <summary>Пол зомби. IsMale у зомби в entityclasses.xml не задан (все «мужчины» по умолчанию, тест 13.10:
/// Арлин получила мужские руки) - женщину выдают звуки zombiefemale*.</summary>
public static bool IsFemale(EntityAlive _z)
{
if (_z == null || !EntityClass.list.TryGetValue(_z.entityClass, out EntityClass cls) || cls?.Properties == null)
{
return false;
}
string s = cls.Properties.GetString("SoundAlert");
return s != null && s.ToLowerInvariant().Contains("female");
}
public static bool Allows(EntityAlive _z)
{
if (_z == null || !EntityClass.list.TryGetValue(_z.entityClass, out EntityClass cls) || cls?.entityClassName == null)
{
return false;
}
string name = cls.entityClassName;
if (name.Contains("Crawler"))
{
return false;
}
foreach (string a in Allowed)
{
if (name.StartsWith(a))
{
return true;
}
}
return false;
}
public static void Show(EntityPlayerLocal _player, EntityAlive _z)
{
if (_rig != null || _player == null || _z == null)
{
return;
}
Archetype arch = Archetype.GetArchetype(IsFemale(_z) ? "BaseFemale" : "BaseMale");
Transform cam = _player.cameraTransform;
if (arch == null || cam == null)
{
Debug.LogWarning("[NecromancerTome] FpArms: нет архетипа или камеры - рук не будет");
return;
}
Transform parent = ParentCamera ? cam : cam.FindInChildren("UFPSRoot") ?? cam;
Archetype wasArch = SDCSUtils.tmpArchetype;
SDCSUtils.AssetRefs wasOwner = SDCSUtils.tmpAssetsOwner;
try
{
SDCSUtils.tmpArchetype = arch;
SDCSUtils.setupRig(ref _rig, ref _bones, SDCSUtils.baseRigFPPrefab, parent, SDCSUtils.FPAnimController);
SDCSUtils.tmpAssetsOwner = SDCSUtils.GetAssetOwnerForRig(_rig);
SDCSUtils.setupBase(_rig, _bones, SDCSUtils.basePartsFP, true);
}
finally
{
SDCSUtils.tmpArchetype = wasArch;
SDCSUtils.tmpAssetsOwner = wasOwner;
}
_rig.transform.SetLocalPositionAndRotation(Offset, Quaternion.identity);
// Скрипты префаба (vp_FPWeapon, риг IK и др.) рассчитаны на руки персонажа: включённая копия с ними повесила
// игру (тест 13.10: «прожал ~ и всё зависло», лог - vp_FPWeapon «Could not find CharacterController»).
// Рукам хватает скелета, аниматора и сетки - скрипты снимаем до включения.
// С конца и в два прохода: скрипт, от которого зависит другой ([RequireComponent]), снимется вторым.
int stripped = 0;
for (int pass = 0; pass < 2; pass++)
{
MonoBehaviour[] mbs = _rig.GetComponentsInChildren<MonoBehaviour>(true);
for (int i = mbs.Length - 1; i >= 0; i--)
{
if (mbs[i] != null)
{
Object.DestroyImmediate(mbs[i]);
stripped++;
}
}
}
// Префаб рук выключен: у персонажа его включает BodyAnimator (State = Visible) - тест 13.10: «активны False».
_rig.SetActive(true);
// Аниматор у персонажа включает тот же BodyAnimator (тест 13.10: «рука одна и без анимации»).
Animator anim = _rig.GetComponent<Animator>();
if (anim != null)
{
anim.enabled = true;
anim.cullingMode = AnimatorCullingMode.AlwaysAnimate;
anim.Rebind();
foreach (KeyValuePair<string, float> kv in _animParams)
{
SetParam(anim, kv.Key, kv.Value);
}
}
Debug.Log("[NecromancerTome] FpArms: снято скриптов " + stripped + ", аниматор " + (anim != null ? anim.runtimeAnimatorController?.name : "нет"));
_rig.name = "necroZombieArmsFP";
int layer = LayerMask.NameToLayer("HoldingItem");
int tinted = 0;
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
smr.gameObject.layer = layer;
// Материалы с _Tint игра уже размножила для этой модели (SDCSUtils.Stitch) - красим свои копии.
foreach (Material m in smr.sharedMaterials)
{
if (m != null && m.HasColor("_Tint"))
{
m.SetColor("_Tint", Tint);
if (!Albedo && m.HasProperty("_Albedo"))
{
m.SetTexture("_Albedo", null);
}
tinted++;
}
}
}
// Коллайдеры рук (у женского скелета игра вешает капсулу на таз) - выключены: руки в голове зомби ловили бы
// луч прицела и толкали бы предметы.
foreach (Collider c in _rig.GetComponentsInChildren<Collider>(true))
{
c.enabled = false;
}
foreach (HingeJoint j in _rig.GetComponentsInChildren<HingeJoint>(true))
{
if (j.connectedBody == null)
{
j.gameObject.SetActive(false);
}
}
_layerWasShown = _player.playerCamera != null && (_player.playerCamera.cullingMask & (1 << layer)) != 0;
_player.ShowHoldingItemLayer(true);
Debug.Log("[NecromancerTome] FpArms: руки " + arch.Name + " для " + _z.EntityName + ", серых материалов " + tinted +
", родитель " + parent.name);
}
/// <summary>Состояние рук для necroarms: где они относительно камеры, видны ли, кто рисует их слой.</summary>
public static string Dump()
{
if (_rig == null)
{
return "рук нет (вид от третьего лица или зомби не из списка)";
}
Camera main = Camera.main;
Transform par = _rig.transform.parent;
string s = "активны " + _rig.activeInHierarchy + ", родитель " + (par != null ? par.name + (par.gameObject.activeInHierarchy ? "" : " (ВЫКЛЮЧЕН)") : "нет") +
", сдвиг " + _rig.transform.localPosition.ToString("F2");
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
Vector3 c = main != null ? main.transform.InverseTransformPoint(smr.bounds.center) : smr.bounds.center;
s += "\n " + smr.name + ": вкл " + smr.enabled + "/" + smr.gameObject.activeInHierarchy + ", виден " + smr.isVisible +
", слой " + LayerMask.LayerToName(smr.gameObject.layer) + ", центр от камеры " + c.ToString("F2") + ", размер " + smr.bounds.size.ToString("F2");
}
foreach (Camera c in Camera.allCameras)
{
s += "\n камера " + c.name + (c == main ? " (main)" : "") + ": слой HoldingItem " +
((c.cullingMask & (1 << LayerMask.NameToLayer("HoldingItem"))) != 0 ? "рисует" : "нет") + ", near " + c.nearClipPlane.ToString("F3");
}
return s;
}
static Animator Anim => _rig != null ? _rig.GetComponent<Animator>() : null;
static bool SetParam(Animator _a, string _name, float _v)
{
foreach (AnimatorControllerParameter p in _a.parameters)
{
if (p.name != _name)
{
continue;
}
switch (p.type)
{
case AnimatorControllerParameterType.Float: _a.SetFloat(_name, _v); break;
case AnimatorControllerParameterType.Int: _a.SetInteger(_name, (int)_v); break;
case AnimatorControllerParameterType.Bool: _a.SetBool(_name, _v != 0f); break;
default: _a.SetTrigger(_name); break;
}
return true;
}
return false;
}
/// <summary>necroarms set: параметр аниматора (запоминается для пересборки; у триггера - только срабатывание).</summary>
public static string Set(string _name, float _v)
{
Animator a = Anim;
if (a == null)
{
return "рук нет";
}
if (!SetParam(a, _name, _v))
{
return "нет параметра " + _name;
}
_animParams[_name] = _v;
return _name + " = " + _v.ToString(CultureInfo.InvariantCulture);
}
/// <summary>Удар одержимого - руки бьют кулаком (указание 13.10: хват пустых рук, «там как раз анимация ударов
/// кулаками»).</summary>
public static void Punch()
{
Animator a = Anim;
if (a != null && !string.IsNullOrEmpty(PunchTrigger))
{
a.SetTrigger(PunchTrigger);
}
}
/// <summary>Анимация удара рук вошла в состояние удара (AnimatorMeleeAttackState у наших рук): через RaycastTime -
/// миг удара, как у персонажа (impactStart). Руки на это время уходят в нижнее положение (под прицел).</summary>
public static void OnSwingEnter(float _raycastTime)
{
_inAttack = true;
_impactAt = Time.time + Mathf.Max(0.01f, _raycastTime);
}
public static void OnSwingExit()
{
_inAttack = false;
}
/// <summary>Урон зомби прошёл - вынужденный миг удара снят.</summary>
public static void OnZombieHit()
{
Possession.ForceImpact = false;
}
/// <summary>Каждый кадр вида от первого лица (Possession.PlaceCamera): отложенный удар и сжатие под угол рук.</summary>
public static void Tick(Camera _cam)
{
if (_rig == null)
{
return;
}
if (_impactAt > 0f && Time.time >= _impactAt)
{
_impactAt = 0f;
Possession.FpImpact();
}
float k = 1f;
if (_cam != null && ArmsFov > 1f)
{
k = Mathf.Tan(_cam.fieldOfView * 0.5f * Mathf.Deg2Rad) / Mathf.Tan(ArmsFov * 0.5f * Mathf.Deg2Rad);
}
_blend = Mathf.MoveTowards(_blend, _inAttack ? 1f : 0f, Time.deltaTime / BlendTime);
float kk = Mathf.Lerp(1f, k, _blend);
_rig.transform.localScale = new Vector3(kk, kk, 1f);
_rig.transform.localPosition = Vector3.Lerp(IdleOffset, new Vector3(Offset.x * k, Offset.y * k, Offset.z), _blend);
}
public static string Trigger(string _name)
{
Animator a = Anim;
if (a == null)
{
return "рук нет";
}
a.SetTrigger(_name);
return "триггер " + _name;
}
/// <summary>Аниматор рук: вкл, контроллер, текущие клипы по слоям и все параметры со значениями.</summary>
public static string DumpAnim()
{
Animator a = Anim;
if (a == null)
{
return "рук нет";
}
string s = "аниматор вкл " + a.enabled + ", контроллер " + a.runtimeAnimatorController?.name + ", слоёв " + a.layerCount;
for (int l = 0; l < a.layerCount; l++)
{
string clips = "";
foreach (AnimatorClipInfo ci in a.GetCurrentAnimatorClipInfo(l))
{
clips += " " + ci.clip?.name + "(" + ci.weight.ToString("F2") + ")";
}
s += "\n слой " + l + " " + a.GetLayerName(l) + " вес " + a.GetLayerWeight(l).ToString("F2") + ":" + clips;
}
foreach (AnimatorControllerParameter p in a.parameters)
{
string v = p.type == AnimatorControllerParameterType.Float ? a.GetFloat(p.name).ToString("F2", CultureInfo.InvariantCulture)
: p.type == AnimatorControllerParameterType.Int ? a.GetInteger(p.name).ToString()
: p.type == AnimatorControllerParameterType.Bool ? a.GetBool(p.name).ToString() : "trigger";
s += "\n " + p.name + " = " + v;
}
return s;
}
/// <summary>Материалы рук: шейдер и его цветовые/текстурные свойства (подбор серого).</summary>
public static string DumpMat()
{
if (_rig == null)
{
return "рук нет";
}
string s = "";
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
foreach (Material m in smr.sharedMaterials)
{
if (m == null)
{
continue;
}
s += "\n " + smr.name + " / " + m.name + " / " + m.shader.name + ":";
for (int i = 0; i < m.shader.GetPropertyCount(); i++)
{
UnityEngine.Rendering.ShaderPropertyType t = m.shader.GetPropertyType(i);
string n = m.shader.GetPropertyName(i);
if (t == UnityEngine.Rendering.ShaderPropertyType.Color)
{
s += " " + n + "=" + m.GetColor(n);
}
else if (t == UnityEngine.Rendering.ShaderPropertyType.Texture)
{
s += " " + n + "[" + (m.GetTexture(n) != null ? m.GetTexture(n).name : "-") + "]";
}
else
{
s += " " + n + "=" + m.GetFloat(n).ToString("F2", CultureInfo.InvariantCulture);
}
}
}
}
return s;
}
/// <summary>Пересобрать руки на месте (после necroarms pos/parent).</summary>
public static void Rebuild()
{
if (_rig == null || !Possession.Active || Possession.Zombie == null)
{
return;
}
EntityPlayerLocal p = Possession.Player;
Hide(p);
Show(p, Possession.Zombie);
}
public static void Hide(EntityPlayerLocal _player)
{
if (_rig == null)
{
return;
}
if (!_layerWasShown)
{
_player?.ShowHoldingItemLayer(false);
}
SDCSUtils.UnloadViz(_rig);
_rig = null;
_bones = null;
Debug.Log("[NecromancerTome] FpArms: руки убраны");
}
}
/// <summary>
/// necroarms - руки одержимого от первого лица (13.10): без параметров - состояние; pos x y z - сдвиг от камеры;
/// parent cam|ufps - к чему крепить. Сдвиг и родитель применяются сразу (руки пересобираются).
/// </summary>
[Preserve]
public class ConsoleCmdNecroArms : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necroarms" };
public override string getDescription() => "NecromancerTome: руки одержимого от первого лица - [pos x y z | parent cam|ufps | anim | mat | set <param> <value> | trigger <name> | tint r g b | albedo on|off | punch <trigger> | fov <deg>]";
void Out(string _s)
{
SdtdConsole.Instance.Output(_s);
Debug.Log("[NecromancerTome] necroarms: " + _s);
}
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
{
string cmd = _params.Count > 0 ? _params[0] : "";
if (cmd == "pos" && _params.Count >= 4 &&
float.TryParse(_params[1].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float x) &&
float.TryParse(_params[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float y) &&
float.TryParse(_params[3].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float z))
{
FpArms.Offset = new Vector3(x, y, z);
FpArms.Rebuild();
}
else if (cmd == "parent" && _params.Count >= 2)
{
FpArms.ParentCamera = _params[1] != "ufps";
FpArms.Rebuild();
}
else if (cmd == "albedo" && _params.Count >= 2)
{
FpArms.Albedo = _params[1] == "on";
FpArms.Rebuild();
}
else if (cmd == "punch" && _params.Count >= 2)
{
FpArms.PunchTrigger = _params[1];
FpArms.Punch();
Out("удар - " + FpArms.PunchTrigger);
return;
}
else if (cmd == "fov" && _params.Count >= 2 && float.TryParse(_params[1].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float fov))
{
FpArms.ArmsFov = fov;
}
else if (cmd == "anim")
{
Out(FpArms.DumpAnim());
return;
}
else if (cmd == "mat")
{
Out(FpArms.DumpMat());
return;
}
else if (cmd == "set" && _params.Count >= 3 && float.TryParse(_params[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float v))
{
Out(FpArms.Set(_params[1], v));
return;
}
else if (cmd == "trigger" && _params.Count >= 2)
{
Out(FpArms.Trigger(_params[1]));
return;
}
else if (cmd == "tint" && _params.Count >= 4 &&
float.TryParse(_params[1].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float r) &&
float.TryParse(_params[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float g) &&
float.TryParse(_params[3].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float b))
{
FpArms.Tint = new Color(r, g, b, 1f);
FpArms.Rebuild();
}
Out(FpArms.Dump());
}
}
/// <summary>Скрипт состояния удара в аниматоре рук (AnimatorMeleeAttackState) берёт персонажа через
/// AnimationEventBridge и бьёт предметом в его руке. У рук одержимого моста нет (FpArms снимает скрипты) - без этой
/// заплатки NullReferenceException на каждом ударе (тест 13.10), а с мостом руки зомби били бы предметом тела.
/// У рук персонажа мост есть всегда - их не трогаем.</summary>
[HarmonyPatch(typeof(AnimatorMeleeAttackState))]
public static class Patch_AnimatorMeleeAttackState_FpArms
{
static bool Ours(Animator _a) => _a != null && _a.GetComponent<AnimationEventBridge>() == null;
[HarmonyPrefix, HarmonyPatch(nameof(AnimatorMeleeAttackState.OnStateEnter))]
public static bool Enter(AnimatorMeleeAttackState __instance, Animator animator)
{
if (!Ours(animator))
{
return true;
}
FpArms.OnSwingEnter(__instance.RaycastTime); // миг удара - как у персонажа (13.10)
return false;
}
[HarmonyPrefix, HarmonyPatch(nameof(AnimatorMeleeAttackState.OnStateUpdate))]
public static bool Update(Animator animator) => !Ours(animator);
[HarmonyPrefix, HarmonyPatch(nameof(AnimatorMeleeAttackState.OnStateExit))]
public static bool Exit(Animator animator)
{
if (!Ours(animator))
{
return true;
}
FpArms.OnSwingExit();
return false;
}
}
/// <summary>Миг урона одержимого зомби (ItemActionMelee.OnHoldingUpdate снимает bAttackStarted, когда замах дошёл до
/// точки удара) - FpArms мерит по нему задержку кулака.</summary>
[HarmonyPatch(typeof(ItemActionMelee), nameof(ItemActionMelee.OnHoldingUpdate))]
public static class Patch_ItemActionMelee_OnHoldingUpdate_FpArms
{
public static void Prefix(ItemActionData _actionData, out bool __state)
{
__state = _actionData is ItemActionMelee.InventoryDataMelee d && d.bAttackStarted &&
Possession.IsPossessed(_actionData.invData?.holdingEntity);
}
public static void Postfix(ItemActionData _actionData, bool __state)
{
if (__state && _actionData is ItemActionMelee.InventoryDataMelee d && !d.bAttackStarted)
{
FpArms.OnZombieHit();
}
}
}
/// <summary>Миг удара одержимого от первого лица задают руки (Possession.FpImpact): пока ForceImpact - «замах дошёл».</summary>
[HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.IsAttackImpact))]
public static class Patch_EntityAlive_IsAttackImpact_FpArms
{
public static void Postfix(EntityAlive __instance, ref bool __result)
{
if (Possession.ForceImpact && Possession.IsPossessed(__instance))
{
__result = true;
}
}
}
}