Одержимость питомцев: роли медведя, волка, собаки и крысы; ноша стервятника; череп при обыске
- Могильная крыса: своя модель без волчьей шерсти; стоит на земле (центр капсулы); E - осмотр контейнеров без права взять (окно игры, рюкзак скрыт, отказ «инвентарь заполнен», контейнер не разрушается); ПКМ - проход сквозь постройки, полупрозрачная. - Стервятник: курица в когтях - слепок с обвисшими крыльями и шеей, без хитбокса. - Зомбособака: нюх - все существа рядом на компасе и карте. - Медведь: толстая шкура (вдвое меньше урона), рёв-провокация, удар лапой по блокам. - Лютоволк: бросок на цель с опрокидыванием, след крови раненых, сумка на 3 ячейки (лут, выгрузка у тела, выпадает мешком при гибели и отзыве; отдельный файл сейва). - Череп (останки): 2% при обычном обыске красного трупа вместо браслета. - Прицел книги не рисуется в одержимости; подсказки вселения на 13 языках. - Замер EntityLeakProbe: удалённые, но живые в памяти сущности - в лог. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
beab1640b0
commit
31aa99860c
+317
-30
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user