From f372660e396923bc797e663dbf3697e1e58e7efd Mon Sep 17 00:00:00 2001 From: AlexCube Date: Fri, 23 Oct 2026 16:24:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D1=80=D0=B5=D0=BB=D0=B0:=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=89=D0=B8=D1=82=D0=B0=20=D0=BE=D1=82=20NullReference?= =?UTF-8?q?=20=D0=B2=20GetDismemberChance=20(+=20=D0=BA=D0=B0=D0=BA=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B2=D0=B5=D0=BD=D0=BE=20=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D0=B5=20-=20=D0=B2=20=D0=BB=D0=BE=D0=B3);=20necrou?= =?UTF-8?q?rn=20probe=20-=20=D1=87=D1=82=D0=BE=20=D1=83=D1=80=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=B2=D0=B8=D0=B4=D0=B8=D1=82=20=D0=B2=20=D0=B7=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D1=87=D1=82=D0=BE=20=D0=B2=D1=8B=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=D0=B0;=20necrofire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- HarmonySrc/CinerariumWork.cs | 23 +++++++++++++++ HarmonySrc/DismemberGuard.cs | 51 ++++++++++++++++++++++++++++++++++ HarmonySrc/HitDiag.cs | 50 --------------------------------- HarmonySrc/NecroDevCommands.cs | 18 ++++++++++++ 4 files changed, 92 insertions(+), 50 deletions(-) create mode 100644 HarmonySrc/DismemberGuard.cs delete mode 100644 HarmonySrc/HitDiag.cs diff --git a/HarmonySrc/CinerariumWork.cs b/HarmonySrc/CinerariumWork.cs index 52277f7..a7a6191 100644 --- a/HarmonySrc/CinerariumWork.cs +++ b/HarmonySrc/CinerariumWork.cs @@ -61,11 +61,29 @@ namespace NecromancerTome /// У перевозки по нити - ящик «откуда» последнего шага (дух летит сначала к нему), иначе null. public static Vector3i? LastFrom; + /// Что выбрал последний шаг (для necrourn probe, 23.10: «стою в 19 м - не работают»). + public static string LastWhat; + + /// Пробный шаг урны без изменений: что в зоне и что дух выбрал бы сейчас (команда necrourn x y z probe). + public static string Probe(World _world, TEFeatureCinerarium _urn) + { + string zone = ""; + if (!_urn.IsLogist && !_urn.IsWorker) + { + Zone z = Scan(_world, _urn); + zone = "коллекторов " + z.Collectors.Count + ", станций " + z.Stations.Count + ", ящиков " + z.Boxes.Count + + ", сумка " + (z.Bag != null) + ", спелых " + z.Ripe.Count + (z.Ripe.Count > 0 ? " (" + z.Ripe[0] + ")" : "") + + ", пустых клеток " + z.Empty.Count + "; "; + } + Vector3i? at = Step(_world, _urn, out bool stuck, true); + return zone + "шаг: " + (at.HasValue ? LastWhat + " в " + at.Value : "нечего делать") + (stuck ? ", класть некуда" : ""); + } static Vector3i? DoStep(World _world, TEFeatureCinerarium _urn, out bool _stuck) { _stuck = false; LastFrom = null; + LastWhat = "?"; // Урна с нитями - логист (16.10: «занимается только перетаскиванием из ящиков в ящики»). if (_urn.IsLogist) { @@ -88,6 +106,7 @@ namespace NecromancerTome { c.SetModified(); } + LastWhat = "расходники коллектору"; return z.Where[c]; } } @@ -103,6 +122,7 @@ namespace NecromancerTome c.Items = items; c.SetModified(); } + LastWhat = "готовое из коллектора"; return z.Where[c]; } } @@ -115,6 +135,7 @@ namespace NecromancerTome { w.Output = output; } + LastWhat = "готовое со станции"; return z.Where[w]; } } @@ -123,6 +144,7 @@ namespace NecromancerTome { if (Harvest(_world, p, z, ref blocked)) { + LastWhat = "сбор урожая"; return p; } } @@ -131,6 +153,7 @@ namespace NecromancerTome Vector3i? planted = PlantRandom(_world, z); if (planted.HasValue) { + LastWhat = "посадка"; return planted; } } diff --git a/HarmonySrc/DismemberGuard.cs b/HarmonySrc/DismemberGuard.cs new file mode 100644 index 0000000..16db189 --- /dev/null +++ b/HarmonySrc/DismemberGuard.cs @@ -0,0 +1,51 @@ +using System; +using HarmonyLib; +using UnityEngine; + +namespace NecromancerTome +{ + /// + /// Стрела: NullReferenceException в ItemAction.GetDismemberChance (23.10.2026). Вызывается из ProjectileMoveScript.checkCollision + /// аргументом к ItemActionAttack.Hit - поэтому в первых логах исключение выглядело как ошибка «внутри Hit» (метод был встроен). + /// Пустое звено цепочки actionData.invData.holdingEntity.inventory / invData.item у стрелы, запомнившей данные лука при выстреле. + /// Исключение вылетало раньше, чем стрела сменит состояние, и она «попадала» снова каждый шаг физики - пачки по 6-19 ошибок. + /// Здесь: при исключении шанс отстрела конечности 0 (стрела долетает как обычно), в лог - какое звено пустое (не больше + /// MaxLogs раз за запуск). Причина звена - выяснить по этим строкам. + /// + [HarmonyPatch(typeof(ItemAction), nameof(ItemAction.GetDismemberChance))] + public static class Patch_ItemAction_GetDismemberChance_Guard + { + const int MaxLogs = 5; + static int _logged; + + public static Exception Finalizer(Exception __exception, ItemActionData _actionData, WorldRayHitInfo hitInfo, ref float __result) + { + if (__exception == null) + { + return null; + } + __result = 0f; + if (_logged < MaxLogs) + { + _logged++; + try + { + ItemInventoryData inv = _actionData?.invData; + EntityAlive holder = inv?.holdingEntity; + Debug.Log("[NecromancerTome] DismemberGuard: " + __exception.GetType().Name + " в GetDismemberChance - actionData " + + (_actionData == null ? "NULL" : _actionData.GetType().Name) + + ", invData " + (inv == null ? "NULL" : "slot " + inv.slotIdx) + + ", item " + (inv?.item == null ? "NULL" : inv.item.Name) + + ", holdingEntity " + (holder == null ? "NULL" : holder.GetType().Name + " " + holder.entityId + (holder.IsDead() ? " dead" : "")) + + ", inventory " + (holder == null ? "-" : holder.inventory == null ? "NULL" : "в руке " + holder.inventory.holdingItem?.Name) + + ", tag " + (hitInfo?.tag ?? "NULL") + " - шанс отстрела 0"); + } + catch (Exception e2) + { + Debug.Log("[NecromancerTome] DismemberGuard: не удалось описать - " + e2.Message); + } + } + return null; // проглотить: стрела долетает и попадает как обычно + } + } +} diff --git a/HarmonySrc/HitDiag.cs b/HarmonySrc/HitDiag.cs deleted file mode 100644 index 5321563..0000000 --- a/HarmonySrc/HitDiag.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using HarmonyLib; -using UnityEngine; - -namespace NecromancerTome -{ - /// - /// Диагностика (23.10.2026): NullReferenceException внутри ItemActionAttack.Hit при попадании стрелы (ProjectileMoveScript.checkCollision) - /// - пачками по одной стреле (исключение вылетает раньше, чем стрела сменит состояние, и она «попадает» снова каждый шаг физики). - /// Было дважды, рядом с только что догруженными зомби спящих зон. Здесь только запись в лог, во что попала стрела; исключение - /// уходит дальше как было. Не больше MaxLogs записей за запуск. Убрать, когда причина найдена. - /// - [HarmonyPatch(typeof(ItemActionAttack), nameof(ItemActionAttack.Hit))] - public static class Patch_ItemActionAttack_Hit_Diag - { - const int MaxLogs = 5; - static int _logged; - - public static Exception Finalizer(Exception __exception, WorldRayHitInfo hitInfo, int _attackerEntityId, ItemValue damagingItemValue) - { - if (__exception == null || _logged >= MaxLogs) - { - return __exception; - } - _logged++; - try - { - string s = "tag=" + (hitInfo?.tag ?? "null") + ", transform=" + (hitInfo?.transform != null ? hitInfo.transform.name : "NULL") + - ", collider=" + (hitInfo?.hitCollider != null ? hitInfo.hitCollider.name : "NULL"); - if (hitInfo != null && hitInfo.tag != null && hitInfo.tag.StartsWith("E_")) - { - Entity e = ItemActionAttack.FindHitEntityNoTagCheck(hitInfo, out string part); - s += ", entity=" + (e == null ? "NULL" : e.GetType().Name + " " + EntityClass.list[e.entityClass].entityClassName + " " + e.entityId + - (e is EntityAlive ea ? (ea.IsDead() ? " dead" : " alive") + ", emodel " + (ea.emodel != null) : "")) + - ", part=" + (part ?? "null"); - } - Entity atk = GameManager.Instance?.World?.GetEntity(_attackerEntityId); - s += ", attacker=" + (atk == null ? "NULL " + _attackerEntityId : atk.GetType().Name + " " + atk.entityId) + - ", item=" + (damagingItemValue?.ItemClass?.Name ?? "null"); - Debug.Log("[NecromancerTome] HitDiag: исключение в ItemActionAttack.Hit (" + __exception.GetType().Name + "): " + s + - "\n" + __exception.StackTrace); - } - catch (Exception e2) - { - Debug.Log("[NecromancerTome] HitDiag: не удалось описать попадание - " + e2.Message); - } - return __exception; - } - } -} diff --git a/HarmonySrc/NecroDevCommands.cs b/HarmonySrc/NecroDevCommands.cs index 574d6a1..299dedf 100644 --- a/HarmonySrc/NecroDevCommands.cs +++ b/HarmonySrc/NecroDevCommands.cs @@ -15,6 +15,7 @@ namespace NecromancerTome /// necroplace блок x y z [поворот 0-3] - поставить блок (как игрок: OnBlockAdded, TE) /// necrohold предмет [число] - положить в текущую ячейку пояса и взять в руку /// necroslot [N] - какая ячейка пояса в руке и что в ячейках; N - взять в руку ячейку N (23.10: проверка Archer's Eye без мыши) + /// necrofire press|release - ЛКМ предметом в руке: нажать / отпустить (лук: натянуть / выстрелить; 23.10, ошибка в Hit от стрелы) /// necrobox x y z list|clear|add предмет число - содержимое хранилища (ящик, сумка урны) /// necrourn x y z info|skull 0|1|from|to|out bx by bz|clear|sample предмет число|group все|нет - урна /// necroclick 0|1 - отпущенная ЛКМ/ПКМ ножом для нитей по блоку под прицелом @@ -45,6 +46,13 @@ namespace NecromancerTome case "necroaim": Aim(w, pl); break; case "necroplace": PlaceBlock(w, _params); break; case "necrohold": Hold(pl, _params); break; + case "necrofire": + { + bool rel = _params.Count > 0 && _params[0] == "release"; + pl.inventory.Execute(0, rel, pl.playerInput); + Out("necrofire: " + (rel ? "отпущено" : "нажато") + ", в руке " + pl.inventory.holdingItem?.Name + ", заряжено " + pl.inventory.holdingItemItemValue.Meta); + break; + } case "necroslot": { if (_params.Count > 0) @@ -470,6 +478,7 @@ namespace NecromancerTome u.SetSample(_p.Count > 5 ? new ItemStack(ItemClass.GetItem(_p[4], true), int.Parse(_p[5])) : ItemStack.Empty.Clone()); break; case "contents": CinerariumContents.Open(GameManager.Instance.World.GetPrimaryPlayer(), u); break; + case "probe": Out("necrourn: " + pos + " " + CinerariumWork.Probe(GameManager.Instance.World, u)); return; case "run": u.ZoneCommand("zurn_run", GameManager.Instance.World.GetPrimaryPlayer()); break; case "stop": u.ZoneCommand("zurn_stop", null); break; case "size": u.ZoneSide = int.Parse(_p[4]); u.SetModified(); break; @@ -528,6 +537,15 @@ namespace NecromancerTome public override void Execute(List _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrohold", _params); } + [Preserve] + public class ConsoleCmdNecroFire : ConsoleCmdAbstract + { + public override string[] getCommands() => new[] { "necrofire" }; + public override string getDescription() => "NecromancerTome (разработка): ЛКМ предметом в руке: press|release"; + public override bool AllowedInMainMenu => false; + public override void Execute(List _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrofire", _params); + } + [Preserve] public class ConsoleCmdNecroSlot : ConsoleCmdAbstract {