Одержимость: прицел-перекрестие с покраснением, кнопки E/F, лут мешков и багажник волка, медведь на дыбы
- Прицел всех одержимых - всегда перекрестие (свой guiDrawCrosshair: точка бралась из проверки камеры от 3-го лица у тела); краснеет на враге: у стервятника - цель рывка, у наземных - враг на луче прицела до 30 м, не за стеной - Падаль: отхил вдвое меньше (EatHealFrac 0.125); ест на F стервятник, медведь, собака, волк и одержимые зомби - E - действие: стервятник - клич (был F), крыса/волк - обыск, медведь - на дыбы (враги до 3.5 м сбиты с ног и отброшены, откат 6 с) - Рёв медведя: надписи «на медведя идут N» и откат, мимо очереди подсказок - Крыса и волк обыскивают мешки с зомби (EntityLootContainer), волк берёт в сумку - Багажник волка: окно сумки как у браслета, без рюкзака; выброс - у волка - Отзыв волка: мешок на месте и метка «Сумка волка» на карте, снимается с мешком - Тексты на 13 языках Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
31aa99860c
commit
e94e967b8e
+132
-27
@@ -122,14 +122,14 @@ namespace NecromancerTome
|
||||
/// <summary>Толчок вверх при взлёте с земли на пробел, м за тик.</summary>
|
||||
public const float FlyTakeOff = 0.3f;
|
||||
static bool _flySitting;
|
||||
/// <summary>Питание падалью (указание 06.10): сидя у трупа, F - EatTime секунд клюёт его и лечится на
|
||||
/// <summary>Питание падалью (указание 06.10; у медведя и собаки - 09.10): сидя или стоя у трупа, E - EatTime секунд клюёт его и лечится на
|
||||
/// EatHealFrac здоровья. У трупа запас мяса - его полное здоровье при жизни; поедание отнимает
|
||||
/// столько же, сколько лечит (и на полном здоровье тоже). Запас кончился - труп исчезает (указание
|
||||
/// 06.10), вместе с содержимым, если это красный труп-добыча. Сдвинулся, взлетел, ударил - еда
|
||||
/// прервана, вылеченное и съеденное остаются; к трупу можно вернуться.</summary>
|
||||
public const float EatRange = 2.5f;
|
||||
public const float EatTime = 3f;
|
||||
public const float EatHealFrac = 0.25f;
|
||||
public const float EatHealFrac = 0.125f; // было 0.25 - указание 09.10: «лечится слишком быстро, вдвое»
|
||||
static EntityAlive _eatCorpse;
|
||||
static float _eatLeft, _eatHeal, _eatBite;
|
||||
static int _eatCount;
|
||||
@@ -325,6 +325,7 @@ namespace NecromancerTome
|
||||
}
|
||||
Active = false;
|
||||
RatLoot.CloseIfViewing();
|
||||
WolfBag.CloseWindow();
|
||||
RatPhase.Stop(GameManager.Instance?.World, Zombie, "выход из одержимости");
|
||||
DogSense.Clear();
|
||||
WolfControl.Clear();
|
||||
@@ -588,20 +589,23 @@ namespace NecromancerTome
|
||||
_nextAttack = Time.time + FlyAttackDelay;
|
||||
_meleeLookUntil = Time.time + FlyAttackDelay;
|
||||
}
|
||||
// F - клич-приманка (указание 06.10; кнопка - 08.10, была ПКМ): крик крикуньи, только голосом
|
||||
// паникующей курицы - зомби в ScreamRange идут на него; с воздуха уводит толпу от базы или от тела.
|
||||
// F у игрока - фонарик, но его читает только выключенный PlayerMoveController.Update.
|
||||
bool scream = input != null && !gui && (input.PermanentActions.ToggleFlashlight.WasPressed || input.ToggleFlashlight.WasPressed);
|
||||
// E - клич-приманка (указание 06.10; кнопка - 08.10 F, была ПКМ; 09.10 - E: «на E действие, у стервятника крик»):
|
||||
// крик крикуньи, только голосом паникующей курицы - зомби в ScreamRange идут на него; с воздуха уводит
|
||||
// толпу от базы или от тела. detonate - это и есть нажатие E.
|
||||
bool scream = detonate;
|
||||
if (scream)
|
||||
{
|
||||
Scream(world, z);
|
||||
}
|
||||
// ПКМ (держать) - «зоркий глаз» (указание 08.10: «приближение как у прицела»), в PlaceCamera.
|
||||
_flyZoomHeld = rangedHeld;
|
||||
// E - питаться падалью (указание 06.10, кнопка - 08.10: «пусть ест по кнопке E» - у игрока E это
|
||||
// взаимодействие, лут; было F). detonate - это и есть нажатие E.
|
||||
bool eat = detonate;
|
||||
Eat(world, z, eat, attack || scream);
|
||||
// F - питаться падалью (указание 06.10; кнопка 08.10 - E, 09.10 - F у всех одержимых питомцев).
|
||||
// F у игрока - фонарик, но его читает только выключенный PlayerMoveController.Update.
|
||||
bool eat = FKey(input, gui);
|
||||
// Есть можно сидя или просто остановившись рядом (зависнув над падалью): без ввода и почти без скорости.
|
||||
bool still = _flySitting || _flyMove.sqrMagnitude < 0.01f && !_flyUp &&
|
||||
new Vector2(z.motion.x, z.motion.z).sqrMagnitude < FlySitSpeed * FlySitSpeed;
|
||||
Eat(world, z, eat, attack || scream || _flyMove.sqrMagnitude > 0.01f || _flyUp || !_flySitting, still);
|
||||
if (_meleeLookUntil > 0f && Time.time >= _meleeLookUntil)
|
||||
{
|
||||
_meleeLookUntil = 0f;
|
||||
@@ -643,16 +647,29 @@ namespace NecromancerTome
|
||||
{
|
||||
// ПКМ - бросок на цель, след крови раненых (решено 09.10).
|
||||
WolfControl.Tick(world, _player, z, ranged, ref _bodyYaw);
|
||||
// E: у тела - выгрузить сумку в рюкзак, у контейнера - лутать в сумку (3 ячейки).
|
||||
if (!(detonate && WolfBag.TryUnload(_player, z)))
|
||||
// E: на контейнер или мешок с зомби - лутать в сумку (3 ячейки); у тела - выгрузить сумку в рюкзак;
|
||||
// иначе - «багажник», окно сумки (указание 09.10).
|
||||
bool looted = RatLoot.Tick(world, _player, z, detonate, true);
|
||||
if (detonate && !looted && !RatLoot.Viewing && !WolfBag.TryUnload(_player, z))
|
||||
{
|
||||
RatLoot.Tick(world, _player, z, detonate, true);
|
||||
WolfBag.OpenWindow(_player, z);
|
||||
}
|
||||
}
|
||||
if (ranged && IsPet && BearControl.Is(z))
|
||||
{
|
||||
BearControl.Roar(world, z); // ПКМ - рёв-провокация (решено 09.10)
|
||||
}
|
||||
if (detonate && IsPet && BearControl.Is(z))
|
||||
{
|
||||
BearControl.RearUp(world, z); // E - на дыбы, враги рядом падают (указание 09.10)
|
||||
}
|
||||
if (EatsCarrion(z))
|
||||
{
|
||||
// F - есть падаль, как стервятник (указание 09.10: «поедание падали на F одержимым питомцам, на E -
|
||||
// действие»; и одержимым зомби - их ИИ в игре и так ест трупы), стоя на месте рядом с трупом.
|
||||
Eat(world, z, FKey(input, gui), moving || jump || attack || ranged, !moving && z.onGround);
|
||||
}
|
||||
AimTint(GroundAimTarget(world, z) != null);
|
||||
if (IsPet && DogSense.Is(z))
|
||||
{
|
||||
// Нюх (указание 09.10): все существа рядом с собакой - на компасе и карте.
|
||||
@@ -758,11 +775,20 @@ namespace NecromancerTome
|
||||
}
|
||||
}
|
||||
|
||||
static void Eat(World _world, EntityAlive _v, bool _pressed, bool _acted)
|
||||
/// <summary>Едят падаль на F, кроме стервятника (указание 09.10): чужие одержимые зомби и питомцы медведь,
|
||||
/// собака, волк. Крыса - дух, не ест.</summary>
|
||||
static bool EatsCarrion(EntityAlive _z) => !IsPet || BearControl.Is(_z) || DogSense.Is(_z) || WolfControl.Is(_z);
|
||||
|
||||
/// <summary>F на клавиатуре или геймпаде (у игрока - фонарик).</summary>
|
||||
static bool FKey(PlayerActionsLocal _input, bool _gui) =>
|
||||
_input != null && !_gui && (_input.PermanentActions.ToggleFlashlight.WasPressed || _input.ToggleFlashlight.WasPressed);
|
||||
|
||||
/// <summary>_moved - сдвинулся или что-то сделал (еда прерывается), _still - стоит/сидит, можно начать.</summary>
|
||||
static void Eat(World _world, EntityAlive _v, bool _pressed, bool _moved, bool _still)
|
||||
{
|
||||
if (_eating)
|
||||
{
|
||||
bool moved = _flyMove.sqrMagnitude > 0.01f || _flyUp || _acted || !_flySitting;
|
||||
bool moved = _moved;
|
||||
bool gone = _eatCorpse != null ? _eatCorpse.IsMarkedForUnload() : _world.GetBlock(_eatBlock).type != _eatBlockType;
|
||||
if (moved || gone)
|
||||
{
|
||||
@@ -822,17 +848,14 @@ namespace NecromancerTome
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Есть можно сидя или просто остановившись рядом (зависнув над падалью): без ввода и почти без скорости.
|
||||
bool still = _flySitting || _flyMove.sqrMagnitude < 0.01f && !_flyUp &&
|
||||
new Vector2(_v.motion.x, _v.motion.z).sqrMagnitude < FlySitSpeed * FlySitSpeed;
|
||||
if (!still || !FindCarrion(_world, _v))
|
||||
if (!_still || !FindCarrion(_world, _v))
|
||||
{
|
||||
GameManager.ShowTooltip(_player, Localization.Get("necroPossessNoCarrion"));
|
||||
Debug.Log("[NecromancerTome] Possession: падали нет" + (still ? "" : " - стервятник не остановился"));
|
||||
GameManager.ShowTooltip(_player, Localization.Get(Flying ? "necroPossessNoCarrion" : "necroPossessNoCarrionGround"));
|
||||
Debug.Log("[NecromancerTome] Possession: падали нет" + (_still ? "" : " - " + _v.EntityName + " не остановился"));
|
||||
return;
|
||||
}
|
||||
_eating = true;
|
||||
if (!_flySitting)
|
||||
if (Flying && !_flySitting)
|
||||
{
|
||||
// Сел за еду сразу, не дожидаясь тика полёта: иначе проверка «не сидит» тут же прервала бы еду.
|
||||
_flySitting = true;
|
||||
@@ -845,7 +868,7 @@ namespace NecromancerTome
|
||||
_eatCount = 0;
|
||||
_v.SetLookPosition(_eatCorpse != null ? _eatCorpse.getChestPosition() : _eatBlock.ToVector3() + new Vector3(0.5f, 0.3f, 0.5f));
|
||||
_v.IsEating = true; // как у зомби: анимация поедания и синхронизация по сети
|
||||
Debug.Log("[NecromancerTome] Possession: стервятник ест " + EatName() + ", здоровье " + _v.Health + "/" + _v.GetMaxHealth() +
|
||||
Debug.Log("[NecromancerTome] Possession: " + _v.EntityName + " ест " + EatName() + ", здоровье " + _v.Health + "/" + _v.GetMaxHealth() +
|
||||
", мяса у падали " + Mathf.CeilToInt(EatMeat(0f)));
|
||||
}
|
||||
|
||||
@@ -1011,7 +1034,6 @@ namespace NecromancerTome
|
||||
colors[_tintIndex] = _tintSaved;
|
||||
_tintIndex = -1;
|
||||
}
|
||||
Debug.Log("[NecromancerTome] Possession: прицел " + (_on ? "на цели - красный" : "без цели"));
|
||||
}
|
||||
|
||||
static readonly Color AimOnTarget = new Color(1f, 0.15f, 0.1f, 1f);
|
||||
@@ -1219,6 +1241,47 @@ namespace NecromancerTome
|
||||
static EntityAlive _aimMissNear;
|
||||
static float _aimMissDist, _aimMissOff;
|
||||
|
||||
/// <summary>Враг под прицелом наземного одержимого (указание 09.10: прицел краснеет на противнике у
|
||||
/// всех одержимых): тело врага - отрезок от ног до головы - не дальше GroundAimRadius от луча
|
||||
/// камеры и не дальше GroundAimRange от одержимого, и не за стеной - не дальше первого попадания
|
||||
/// луча (AimPoint).</summary>
|
||||
static EntityAlive GroundAimTarget(World _world, EntityAlive _z)
|
||||
{
|
||||
Transform cam = _player?.cameraTransform;
|
||||
if (cam == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Vector3 from = cam.position + Origin.position;
|
||||
Vector3 dir = cam.forward;
|
||||
float wall = Vector3.Dot(AimPoint() - from, dir) + 0.5f;
|
||||
EntityAlive best = null;
|
||||
float bestAlong = float.MaxValue;
|
||||
for (int i = 0; i < _world.EntityAlives.Count; i++)
|
||||
{
|
||||
EntityAlive e = _world.EntityAlives[i];
|
||||
if (!SnakeControl.IsFoe(e, _z) || (e.position - _z.position).sqrMagnitude > GroundAimRange * GroundAimRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Vector3 feet = e.position, head = e.getHeadPosition();
|
||||
for (int k = 0; k <= 4; k++)
|
||||
{
|
||||
Vector3 p = Vector3.Lerp(feet, head, k / 4f);
|
||||
float a = Vector3.Dot(p - from, dir);
|
||||
if (a > 0f && a < wall && a < bestAlong && Vector3.Cross(p - from, dir).magnitude < GroundAimRadius)
|
||||
{
|
||||
best = e;
|
||||
bestAlong = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
public const float GroundAimRange = 30f; // зрение зомби, как TauntRange
|
||||
public const float GroundAimRadius = 0.6f;
|
||||
|
||||
/// <summary>Раз в полсекунды: есть ли живой враг ближе BodyDangerRange к телу (как у змеи).</summary>
|
||||
static void CheckBody(World _world, EntityAlive _z, float _dt)
|
||||
{
|
||||
@@ -1288,11 +1351,11 @@ namespace NecromancerTome
|
||||
}
|
||||
if (Flying)
|
||||
{
|
||||
text += ", " + Localization.Get("necroPossessHintZoom") + ", " + Localization.Get("necroPossessHintScreamF");
|
||||
text += ", " + Localization.Get("necroPossessHintZoom") + ", " + Localization.Get("necroPossessHintScreamE");
|
||||
}
|
||||
if (Flying)
|
||||
{
|
||||
text += ", " + Localization.Get("necroPossessHintEat");
|
||||
text += ", " + Localization.Get("necroPossessHintEatF");
|
||||
}
|
||||
if (IsPet && RatModel.Is(_z))
|
||||
{
|
||||
@@ -1309,9 +1372,14 @@ namespace NecromancerTome
|
||||
}
|
||||
if (IsPet && BearControl.Is(_z))
|
||||
{
|
||||
text += ", " + Localization.Get("necroPossessHintRoar") + ", " + Localization.Get("necroPossessHintSmash") + ", " +
|
||||
text += ", " + Localization.Get("necroPossessHintRoar") + ", " + Localization.Get("necroPossessHintRear") + ", " +
|
||||
Localization.Get("necroPossessHintSmash") + ", " +
|
||||
Localization.Get("necroPossessHintSkin");
|
||||
}
|
||||
if (EatsCarrion(_z))
|
||||
{
|
||||
text += ", " + Localization.Get("necroPossessHintEatGroundF");
|
||||
}
|
||||
if (_z.jumpMaxDistance >= 5f)
|
||||
{
|
||||
text += ", " + Localization.Get("necroPossessHintLeap");
|
||||
@@ -1765,6 +1833,43 @@ namespace NecromancerTome
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Прицел одержимого - всегда перекрестие (указание 09.10: «то перекрестие, то точка»). Вид прицела
|
||||
/// игра берёт у предмета в руке тела, а у книги призыва и пустой руки он зависит от проверки камеры от
|
||||
/// третьего лица (ItemAction.GetCrosshairType - EntityPlayerLocal.CharacterCameraAngleValid): камера дальше
|
||||
/// 20° от того, куда повёрнуто тело, - прицела нет, и игра рисует точку. В одержимости камера - глаза
|
||||
/// одержимого, тело стоит, так что вид менялся от поворота мыши. Рисуем сами то же, что ветка Plus
|
||||
/// guiDrawCrosshair; цвет - из таблицы игры, его красит Possession.AimTint.</summary>
|
||||
[HarmonyPatch(typeof(EntityPlayerLocal), nameof(EntityPlayerLocal.guiDrawCrosshair))]
|
||||
public static class Patch_EntityPlayerLocal_guiDrawCrosshair_Possession
|
||||
{
|
||||
public static bool Prefix(EntityPlayerLocal __instance, NGuiWdwInGameHUD _guiInGame, bool bModalWindowOpen)
|
||||
{
|
||||
if (!Possession.Active || __instance != Possession.Player)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!_guiInGame.showCrosshair || Event.current.type != EventType.Repaint || __instance.IsDead() ||
|
||||
!GamePrefs.GetBool(EnumGamePrefs.OptionsCrosshairEnabled) || bModalWindowOpen || _guiInGame.CrosshairTexture == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int i = GamePrefs.GetInt(EnumGamePrefs.OptionsCrosshairColor);
|
||||
if (i < 0 || i >= EntityPlayerLocal.crosshairColors.Count)
|
||||
{
|
||||
i = 0;
|
||||
}
|
||||
Color c = EntityPlayerLocal.crosshairColors[i];
|
||||
Vector2 center = __instance.GetCrosshairPosition2D();
|
||||
center.y = Screen.height - center.y;
|
||||
float size = Screen.height * 0.059f * GamePrefs.GetFloat(EnumGamePrefs.OptionsCrosshairScale);
|
||||
Color was = GUI.color;
|
||||
GUI.color = new Color(c.r, c.g, c.b, GamePrefs.GetFloat(EnumGamePrefs.OptionsCrosshairOpacity));
|
||||
GUI.DrawTexture(new Rect(center.x - size / 2f, center.y - size / 2f, size, size), _guiInGame.CrosshairTexture, ScaleMode.StretchToFill);
|
||||
GUI.color = was;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Тело оцепенело: ввод игрока не идёт (как у тела в рэгдолле - stopMoving и выход).</summary>
|
||||
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
|
||||
public static class Patch_PlayerMoveController_Update_Possession
|
||||
|
||||
Reference in New Issue
Block a user