Руки одержимого: кулак в момент урона зомби, сжатие под угол рук 45°, скрыты только ячейки пояса персонажа

- Удар рук откладывается на замеренную задержку урона зомби (ItemActionMelee.OnHoldingUpdate) минус ход кулака.
- Руки сжимаются по экранным осям как при FOV 45 (как у персонажа), сдвиг - родной -1.7.
- От первого лица прячется только windowToolbelt: полоски персонажа (та же группа окон) остаются.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-13 16:26:18 +03:00
co-authored by Claude Opus 5.5
parent 7b63f91a3b
commit f03c018204
3 changed files with 109 additions and 8 deletions
+89 -2
View File
@@ -49,7 +49,15 @@ namespace NecromancerTome
};
/// <summary>Сдвиг рук от камеры (подбор necroarms pos). У персонажа игра опускает руки на 1.7 м: начало их
/// скелета - у ног (vp_FPWeapon PositionOffset 0,-1.7,0.02 для SDCS).</summary>
public static Vector3 Offset = new Vector3(0f, -1.55f, 0.05f); // подобрано в игре 13.10 (у персонажа -1.7, но руки рисуются с FOV 45)
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>Через сколько после сигнала удара кулак рук доходит до цели (подбор necroarms impact).</summary>
public static float FpImpact = 0.15f;
static float _punchAt, _attackAt, _hitDelay = 0.45f;
/// <summary>Родитель: true - сама камера, false - её UFPSRoot (как у персонажа).</summary>
public static bool ParentCamera = true;
@@ -266,6 +274,55 @@ namespace NecromancerTome
}
}
/// <summary>Удар зомби начат: урон пройдёт, когда его замах дойдёт до точки удара (ItemActionMelee.OnHoldingUpdate,
/// у разных зомби по-разному). Кулак рук запускаем с таким опозданием, чтобы пришёл в тот же миг (тест 13.10:
/// «сначала анимация, потом пауза и удар»); _hitDelay - замер прошлых ударов этого зомби.</summary>
public static void QueuePunch()
{
if (_rig == null)
{
return;
}
_attackAt = Time.time;
_punchAt = Time.time + Mathf.Max(0f, _hitDelay - FpImpact);
}
/// <summary>Урон зомби прошёл - замер задержки для следующих ударов.</summary>
public static void OnZombieHit()
{
if (_attackAt <= 0f)
{
return;
}
float d = Time.time - _attackAt;
_attackAt = 0f;
if (d > 0.02f && d < 2f)
{
_hitDelay = d;
}
}
/// <summary>Каждый кадр вида от первого лица (Possession.PlaceCamera): отложенный удар и сжатие под угол рук.</summary>
public static void Tick(Camera _cam)
{
if (_rig == null)
{
return;
}
if (_punchAt > 0f && Time.time >= _punchAt)
{
_punchAt = 0f;
Punch();
}
float k = 1f;
if (_cam != null && ArmsFov > 1f)
{
k = Mathf.Tan(_cam.fieldOfView * 0.5f * Mathf.Deg2Rad) / Mathf.Tan(ArmsFov * 0.5f * Mathf.Deg2Rad);
}
_rig.transform.localScale = new Vector3(k, k, 1f);
_rig.transform.localPosition = new Vector3(Offset.x * k, Offset.y * k, Offset.z);
}
public static string Trigger(string _name)
{
Animator a = Anim;
@@ -382,7 +439,7 @@ namespace NecromancerTome
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>]";
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 <угол> | impact <с>]";
void Out(string _s)
{
@@ -418,6 +475,16 @@ namespace NecromancerTome
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 == "impact" && _params.Count >= 2 && float.TryParse(_params[1].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float imp))
{
FpArms.FpImpact = imp;
Out("кулак доходит за " + imp.ToString(CultureInfo.InvariantCulture) + " с");
return;
}
else if (cmd == "anim")
{
Out(FpArms.DumpAnim());
@@ -468,4 +535,24 @@ namespace NecromancerTome
[HarmonyPrefix, HarmonyPatch(nameof(AnimatorMeleeAttackState.OnStateExit))]
public static bool Exit(Animator animator) => !Ours(animator);
}
/// <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();
}
}
}
}