Змея: выход из текстур, полёт на ПКМ; одержимый: приседание, удар по прицелу; коза
- Змея: проверка «внутри текстуры» лучами (земля игры - поверхность), каждый кадр выброс на ближайшее свободное место (на земле - из любых текстур, в полёте - из грунта); шаг и прыжок в текстуры не пускают. Полёт пока зажата ПКМ: 1.1 м/с (половина шага зомби), сквозь постройки можно, в грунт нельзя; Пробел вверх, C/Ctrl вниз. E - аварийный выход. Детализация зданий и область тела - как у одержимого. - Одержимый: будит спящих (комнаты зданий и сам вид одержимого), приседание на C и Ctrl, удар по точке под прицелом с наведением на грудь ближнего врага. - Хваты: толчок руки к прицелу на ЛКМ; «пиф-паф» вселяет на пике толчка. «Отрицание» стало «Козой» (13 языков): средний и безымянный поджаты, большой прижат; звук - бросок змеи. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bb7a66dd8e
commit
fdd3782024
+135
-5
@@ -52,6 +52,26 @@ namespace NecromancerTome
|
||||
public const float TauntRange = 30f;
|
||||
public const float TauntPeriod = 1f;
|
||||
public const int TauntTicks = 1200;
|
||||
/// <summary>Спящие просыпаются от одержимого (тест 05.10: «не разбудил спящего»). Игра будит их
|
||||
/// только игроком - увидел или услышал (EAISetNearestEntityAsTarget.FindTargetPlayer), одержимый для
|
||||
/// неё просто зомби. Будим сами: видит одержимого ближе SleeperWakeRange (пригнувшегося - ближе
|
||||
/// SleeperWakeCrouched) или тот вплотную, ближе SleeperHearRange, хоть и за спиной.</summary>
|
||||
public const float SleeperWakeRange = 8f;
|
||||
public const float SleeperWakeCrouched = 3f;
|
||||
public const float SleeperHearRange = 2f;
|
||||
/// <summary>Приседание (указание 05.10): пока зажата кнопка приседания. Высота - доля роста зомби;
|
||||
/// ниже 0.7 игра включает ходьбу пригнувшись (walkType 8), как у её зомби под низким потолком.</summary>
|
||||
public static bool Crouching;
|
||||
/// <summary>Удар по прицелу (тест 05.10: «не попадаю по другим зомби»). Удар зомби - луч ~1.65 м из
|
||||
/// головы по взгляду (ItemActionMelee, EntityHuman.GetLookVector), а взгляд брался из наклона камеры,
|
||||
/// которая сзади и выше: под прицелом враг, а луч из головы уходит ниже и мимо. На время замаха
|
||||
/// взгляд зомби - на точку под прицелом, а если рядом враг почти по прицелу - на его грудь.</summary>
|
||||
public const float MeleeAssistRange = 2.5f;
|
||||
public const float MeleeAssistDeg = 25f;
|
||||
static float _meleeLookUntil;
|
||||
/// <summary>Ctrl в игре - приседание переключателем (ToggleCrouch), C - пока держишь (Crouch).</summary>
|
||||
static bool _crouchToggled;
|
||||
public const float CrouchHeight = 0.6f;
|
||||
/// <summary>Сколько чанков держать вокруг тела, пока игрок в зомби (указание 03.10: «сильно ограничить
|
||||
/// видимость у тела - ему вообще нет смысла что-либо видеть»). Тест 03.10: две полные области обзора
|
||||
/// (тело + одержимый) - память росла с каждым шагом зомби, а выход давал подвисание ~50 с. Трёх чанков
|
||||
@@ -73,6 +93,7 @@ namespace NecromancerTome
|
||||
public const float TurnSpeed = 540f;
|
||||
static float _nextAttack;
|
||||
static float _nextTaunt;
|
||||
static float _sleeperCheck;
|
||||
static ChunkManager.ChunkObserver _observer;
|
||||
static readonly List<Animator> _frozen = new List<Animator>();
|
||||
static readonly List<float> _frozenSpeed = new List<float>();
|
||||
@@ -155,7 +176,11 @@ namespace NecromancerTome
|
||||
return;
|
||||
}
|
||||
Active = false;
|
||||
Crouching = false;
|
||||
_meleeLookUntil = 0f;
|
||||
_crouchToggled = false;
|
||||
EntityAlive z = Zombie;
|
||||
z?.SetLookPosition(Vector3.zero);
|
||||
EntityPlayerLocal player = _player;
|
||||
if (_rState != RState.Idle && z != null && !z.IsDead())
|
||||
{
|
||||
@@ -287,6 +312,11 @@ namespace NecromancerTome
|
||||
// кнопка геймпада (тест 03.10: «подрыва нету»).
|
||||
detonate = input.Activate.WasPressed || input.PermanentActions.Activate.WasPressed;
|
||||
}
|
||||
if (input != null && !gui && input.ToggleCrouch.WasPressed)
|
||||
{
|
||||
_crouchToggled = !_crouchToggled;
|
||||
}
|
||||
Crouching = input != null && !gui && (input.Crouch.IsPressed || _crouchToggled);
|
||||
if (detonate && z is EntityZombieCop cop && !cop.isPrimed)
|
||||
{
|
||||
Debug.Log("[NecromancerTome] Possession: подрыв " + z.EntityName);
|
||||
@@ -316,12 +346,32 @@ namespace NecromancerTome
|
||||
{
|
||||
Scream(world, z);
|
||||
}
|
||||
if (attack && Time.time >= _nextAttack && z.Attack(false))
|
||||
if (attack && Time.time >= _nextAttack)
|
||||
{
|
||||
z.Attack(true);
|
||||
_nextAttack = Time.time + Mathf.Max(0.3f, z.GetAttackTimeoutTicks() / 20f);
|
||||
z.SetLookPosition(MeleeAim(world, z));
|
||||
if (z.Attack(false))
|
||||
{
|
||||
z.Attack(true);
|
||||
float timeout = Mathf.Max(0.3f, z.GetAttackTimeoutTicks() / 20f);
|
||||
_nextAttack = Time.time + timeout;
|
||||
_meleeLookUntil = Time.time + Mathf.Max(0.6f, timeout);
|
||||
}
|
||||
}
|
||||
if (_meleeLookUntil > 0f && _rState == RState.Idle)
|
||||
{
|
||||
if (Time.time < _meleeLookUntil)
|
||||
{
|
||||
z.SetLookPosition(MeleeAim(world, z));
|
||||
}
|
||||
else
|
||||
{
|
||||
_meleeLookUntil = 0f;
|
||||
z.SetLookPosition(Vector3.zero);
|
||||
}
|
||||
}
|
||||
_observer?.SetPosition(z.position);
|
||||
// Спящие в зданиях просыпаются от одержимого, как от змеи (указание 05.10).
|
||||
SnakeControl.Sleepers(world, _player, z.position, ref _sleeperCheck, Time.deltaTime);
|
||||
Taunt(world, z);
|
||||
}
|
||||
|
||||
@@ -371,6 +421,37 @@ namespace NecromancerTome
|
||||
GameManager.ShowTooltip(_p, text);
|
||||
}
|
||||
|
||||
/// <summary>Куда бить: грудь врага ближе MeleeAssistRange к голове и не дальше MeleeAssistDeg от
|
||||
/// направления на прицел (ближайший к прицелу), иначе - точка под прицелом.</summary>
|
||||
static Vector3 MeleeAim(World _world, EntityAlive _z)
|
||||
{
|
||||
Vector3 aim = AimPoint();
|
||||
Vector3 head = _z.getHeadPosition();
|
||||
Vector3 dir = (aim - head).normalized;
|
||||
EntityAlive best = null;
|
||||
float bestAngle = MeleeAssistDeg;
|
||||
for (int i = 0; i < _world.EntityAlives.Count; i++)
|
||||
{
|
||||
EntityAlive e = _world.EntityAlives[i];
|
||||
if (e == null || e == _z || e.IsDead() || e is EntityPlayer || SnakeControl.IsNecroEntity(e))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Vector3 to = e.getChestPosition() - head;
|
||||
if (to.sqrMagnitude > MeleeAssistRange * MeleeAssistRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float angle = Vector3.Angle(dir, to);
|
||||
if (angle < bestAngle)
|
||||
{
|
||||
bestAngle = angle;
|
||||
best = e;
|
||||
}
|
||||
}
|
||||
return best != null ? best.getChestPosition() : aim;
|
||||
}
|
||||
|
||||
/// <summary>Точка под прицелом камеры (в мировых координатах), до 50 м.</summary>
|
||||
static Vector3 AimPoint()
|
||||
{
|
||||
@@ -566,8 +647,26 @@ namespace NecromancerTome
|
||||
{
|
||||
EntityAlive e = _world.EntityAlives[i];
|
||||
if (e == null || e == _z || e.IsDead() || !(e is EntityZombie) || e.GetAttackTarget() == _z ||
|
||||
e.aiManager == null || PetFollowPatch.FindOwnerOfPet(e.entityId) != null ||
|
||||
(e.position - _z.position).sqrMagnitude > TauntRange * TauntRange || !e.CanSee(_z))
|
||||
e.aiManager == null || PetFollowPatch.FindOwnerOfPet(e.entityId) != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float d2 = (e.position - _z.position).sqrMagnitude;
|
||||
if (d2 > TauntRange * TauntRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (e.IsSleeping)
|
||||
{
|
||||
float wake = Crouching ? SleeperWakeCrouched : SleeperWakeRange;
|
||||
if (d2 > SleeperHearRange * SleeperHearRange && (d2 > wake * wake || !e.CanSee(_z)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
e.ConditionalTriggerSleeperWakeUp();
|
||||
Debug.Log("[NecromancerTome] Possession: проснулся " + e.EntityName + " (" + e.entityId + "), " + Mathf.Sqrt(d2).ToString("F1") + " м");
|
||||
}
|
||||
else if (!e.CanSee(_z))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -685,4 +784,35 @@ namespace NecromancerTome
|
||||
GUI.Label(new Rect(r.x, r.y - 26f * scale, r.width, 24f * scale), name + " " + z.Health + " / " + z.GetMaxHealth(), _style);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Приседание одержимого: пока зажата кнопка, рост зомби опускается до CrouchHeight, как игра
|
||||
/// опускает его под низким потолком (EntityAlive.CrouchHeightFixedUpdate, её же шаги). Отпустил - дальше
|
||||
/// считает игра: встаёт, только если сверху свободно. Сама игра пригибает лишь под потолком над головой,
|
||||
/// а проём впереди чует через moveHelper, который у одержимого остановлен.</summary>
|
||||
[HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.CrouchHeightFixedUpdate))]
|
||||
public static class Patch_EntityAlive_CrouchHeightFixedUpdate_Possession
|
||||
{
|
||||
public static bool Prefix(EntityAlive __instance)
|
||||
{
|
||||
if (!Possession.Crouching || !Possession.IsPossessed(__instance) || __instance.crouchType == 0 ||
|
||||
__instance.physicsBaseHeight <= 1.3f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
float h = Utils.FastMoveTowards(__instance.physicsHeight, __instance.physicsBaseHeight * Possession.CrouchHeight, 0.099999994f);
|
||||
__instance.SetHeight(h);
|
||||
if (h <= __instance.physicsBaseHeight * 0.7f)
|
||||
{
|
||||
__instance.crouchBendPerTarget = 0f;
|
||||
if (__instance.walkType != 8 && __instance.walkType != 21)
|
||||
{
|
||||
int was = __instance.walkType;
|
||||
__instance.SetWalkType(8);
|
||||
__instance.walkTypeBeforeCrouch = was;
|
||||
}
|
||||
}
|
||||
__instance.crouchBendPer = Mathf.MoveTowards(__instance.crouchBendPer, __instance.crouchBendPerTarget, 0.099999994f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user