diff --git a/HarmonySrc/FpArms.cs b/HarmonySrc/FpArms.cs
index 2e9aca9..19aa5d7 100644
--- a/HarmonySrc/FpArms.cs
+++ b/HarmonySrc/FpArms.cs
@@ -55,9 +55,14 @@ namespace NecromancerTome
/// рук по экранным осям: масштаб (k, k, 1) от камеры, k = tg(FOV камеры / 2) / tg(ArmsFov / 2) - каждый кадр
/// (FOV меняется при приближении).
public static float ArmsFov = 45f;
- /// Через сколько после сигнала удара кулак рук доходит до цели (подбор necroarms impact).
- public static float FpImpact = 0.15f;
- static float _punchAt, _attackAt, _hitDelay = 0.45f;
+ /// Положение рук в покое (подобрано 13.10, без сжатия): «руки подними как были»; на удар - Offset со
+ /// сжатием под ArmsFov, «анимацию удара занизь как сейчас». Переход - за BlendTime.
+ public static Vector3 IdleOffset = new Vector3(0f, -1.55f, 0.05f);
+ public const float BlendTime = 0.12f;
+ /// Минимальный промежуток между ударами рук.
+ public const float SwingTime = 0.6f;
+ static bool _inAttack;
+ static float _impactAt, _blend;
/// Родитель: true - сама камера, false - её UFPSRoot (как у персонажа).
public static bool ParentCamera = true;
@@ -274,32 +279,23 @@ namespace NecromancerTome
}
}
- /// Удар зомби начат: урон пройдёт, когда его замах дойдёт до точки удара (ItemActionMelee.OnHoldingUpdate,
- /// у разных зомби по-разному). Кулак рук запускаем с таким опозданием, чтобы пришёл в тот же миг (тест 13.10:
- /// «сначала анимация, потом пауза и удар»); _hitDelay - замер прошлых ударов этого зомби.
- public static void QueuePunch()
+ /// Анимация удара рук вошла в состояние удара (AnimatorMeleeAttackState у наших рук): через RaycastTime -
+ /// миг удара, как у персонажа (impactStart). Руки на это время уходят в нижнее положение (под прицел).
+ public static void OnSwingEnter(float _raycastTime)
{
- if (_rig == null)
- {
- return;
- }
- _attackAt = Time.time;
- _punchAt = Time.time + Mathf.Max(0f, _hitDelay - FpImpact);
+ _inAttack = true;
+ _impactAt = Time.time + Mathf.Max(0.01f, _raycastTime);
}
- /// Урон зомби прошёл - замер задержки для следующих ударов.
+ public static void OnSwingExit()
+ {
+ _inAttack = false;
+ }
+
+ /// Урон зомби прошёл - вынужденный миг удара снят.
public static void OnZombieHit()
{
- if (_attackAt <= 0f)
- {
- return;
- }
- float d = Time.time - _attackAt;
- _attackAt = 0f;
- if (d > 0.02f && d < 2f)
- {
- _hitDelay = d;
- }
+ Possession.ForceImpact = false;
}
/// Каждый кадр вида от первого лица (Possession.PlaceCamera): отложенный удар и сжатие под угол рук.
@@ -309,18 +305,20 @@ namespace NecromancerTome
{
return;
}
- if (_punchAt > 0f && Time.time >= _punchAt)
+ if (_impactAt > 0f && Time.time >= _impactAt)
{
- _punchAt = 0f;
- Punch();
+ _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);
}
- _rig.transform.localScale = new Vector3(k, k, 1f);
- _rig.transform.localPosition = new Vector3(Offset.x * k, Offset.y * k, Offset.z);
+ _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)
@@ -439,7 +437,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 | trigger | tint r g b | albedo on|off | punch | fov <угол> | impact <с>]";
+ public override string getDescription() => "NecromancerTome: руки одержимого от первого лица - [pos x y z | parent cam|ufps | anim | mat | set | trigger | tint r g b | albedo on|off | punch | fov ]";
void Out(string _s)
{
@@ -479,12 +477,6 @@ namespace NecromancerTome
{
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());
@@ -527,13 +519,29 @@ namespace NecromancerTome
static bool Ours(Animator _a) => _a != null && _a.GetComponent() == null;
[HarmonyPrefix, HarmonyPatch(nameof(AnimatorMeleeAttackState.OnStateEnter))]
- public static bool Enter(Animator animator) => !Ours(animator);
+ 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) => !Ours(animator);
+ public static bool Exit(Animator animator)
+ {
+ if (!Ours(animator))
+ {
+ return true;
+ }
+ FpArms.OnSwingExit();
+ return false;
+ }
}
/// Миг урона одержимого зомби (ItemActionMelee.OnHoldingUpdate снимает bAttackStarted, когда замах дошёл до
@@ -555,4 +563,17 @@ namespace NecromancerTome
}
}
}
+
+ /// Миг удара одержимого от первого лица задают руки (Possession.FpImpact): пока ForceImpact - «замах дошёл».
+ [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;
+ }
+ }
+ }
}
diff --git a/HarmonySrc/Possession.cs b/HarmonySrc/Possession.cs
index 18f032a..66fc587 100644
--- a/HarmonySrc/Possession.cs
+++ b/HarmonySrc/Possession.cs
@@ -365,6 +365,7 @@ namespace NecromancerTome
return;
}
Active = false;
+ ForceImpact = false;
FirstPerson = false;
HideBody(null);
FpArms.Hide(_player);
@@ -838,14 +839,21 @@ namespace NecromancerTome
// Нюх (указание 09.10): все существа рядом с собакой - на компасе и карте.
DogSense.Tick(world, z, Time.deltaTime);
}
- if (attack && Time.time >= _nextAttack)
+ if (attack && Time.time >= _nextAttack && FpArms.Active)
+ {
+ // От первого лица - как у персонажа (указание 13.10): ЛКМ - удар рук, урон зомби - когда их анимация дошла
+ // до точки удара (FpArms -> FpImpact), а не по его собственному замаху.
+ PossessionPowers.BreakInvis(z, "удар");
+ FpArms.Punch();
+ _nextAttack = Time.time + Mathf.Max(FpArms.SwingTime, z.GetAttackTimeoutTicks() / 20f);
+ }
+ else if (attack && Time.time >= _nextAttack)
{
PossessionPowers.BreakInvis(z, "удар");
z.SetLookPosition(MeleeAim(world, z));
if (z.Attack(false))
{
z.Attack(true);
- FpArms.QueuePunch(); // руки от первого лица бьют вместе с зомби (13.10), кулак - в момент урона
if (IsPet && BearControl.Is(z))
{
BearControl.Smash(world, _player, z); // удар лапой и по блоку (решено 09.10)
@@ -2110,6 +2118,34 @@ namespace NecromancerTome
_flyZoomHeld = false;
}
+ /// Миг удара рук от первого лица (FpArms): зомби бьёт сразу - его удар начат и сразу «дошёл до точки
+ /// удара» (ForceImpact в EntityAlive.IsAttackImpact, задержка луча ItemActionMelee снята). Урон, дальность,
+ /// цель - его, взгляд - на прицел (MeleeAim).
+ public static bool ForceImpact;
+
+ public static void FpImpact()
+ {
+ EntityAlive z = Zombie;
+ if (!Active || z == null || z.IsDead())
+ {
+ return;
+ }
+ z.SetLookPosition(MeleeAim(z.world, z));
+ _meleeLookUntil = Time.time + 0.6f;
+ if (!z.Attack(false))
+ {
+ Debug.Log("[NecromancerTome] Possession: удар рук - зомби не может бить сейчас");
+ return;
+ }
+ z.Attack(true);
+ ForceImpact = true;
+ if (z.inventory?.holdingItemData?.actionData != null && z.inventory.holdingItemData.actionData.Count > 0 &&
+ z.inventory.holdingItemData.actionData[0] is ItemActionMelee.InventoryDataMelee d)
+ {
+ d.lastUseTime -= 10f;
+ }
+ }
+
/// Ячейки пояса персонажа (окно windowToolbelt) - скрыты в виде от первого лица (указание 13.10: «пояс игрока
/// нужно прятать», у зомби свой пояс). Только это окно: группа toolbelt держит и полоски здоровья и сытости персонажа
/// (тест 13.10 - пропали вместе с поясом). Ставится каждый кадр - игра может показать окно снова.