From 85a7c2721eaed412b9ac5d0441695811bafd16af Mon Sep 17 00:00:00 2001 From: AlexCube Date: Tue, 13 Oct 2026 13:05:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B4=D0=B5=D1=80=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C:=20=D0=BF=D1=80=D0=B8=D1=86=D0=B5?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B8=D0=BC=D0=BE=20=D1=81=D0=B2=D0=BE=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=82=D0=B5=D0=BB=D0=B0,=20=D0=BF=D0=BB=D0=B5?= =?UTF-8?q?=D0=B2=D0=BE=D0=BA=20=D0=BC=D1=83=D1=82=D0=B0=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B2=20=D0=BF=D1=80=D0=B8=D1=86=D0=B5=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AimPoint пропускает тело одержимого: у крупных зомби луч камеры упирался в свою спину - прицел не краснел, удары и выстрелы шли в себя. - Плевок мутанта (E): скорость снарядов пересчитывается навесом в цель под прицелом (до 15 м), вместо ванильного веера под ноги; подсказка на 13 языках. Co-Authored-By: Claude Opus 5.5 --- Config/Localization.csv | 2 +- HarmonySrc/Possession.cs | 40 +++++++++++++++++++++++++++++----- HarmonySrc/PossessionPowers.cs | 24 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/Config/Localization.csv b/Config/Localization.csv index 263b518..e9d0242 100644 --- a/Config/Localization.csv +++ b/Config/Localization.csv @@ -229,7 +229,7 @@ necroPossessHintThrow,items,Item,,,,"E - lift a boulder, RMB - throw it",,"E - F necroPossessHintSwarm,items,Item,,,,RMB - spew Pharaoh's beetles,,RMT - Käfer des Pharao ausspeien,RMB: escupir escarabajos del faraón,Clic droit : cracher les scarabées du pharaon,RMB - sputa gli scarabei del faraone,右クリック: ファラオの甲虫を吐く,우클릭 - 파라오의 딱정벌레 뱉기,PPM - wypluj żuki faraona,RMB - cuspir besouros do faraó,ПКМ - изрыгнуть жуков фараона,Sağ tık - firavun böceklerini kus,右键 - 喷出法老甲虫,右鍵 - 噴出法老甲蟲 necroPossessHintInvis,items,Item,,,,E - invisibility (a strike breaks it),,E - Unsichtbarkeit (ein Schlag bricht sie),E: invisibilidad (un golpe la rompe),E : invisibilité (un coup la rompt),E - invisibilità (un colpo la spezza),E: 透明化(攻撃すると解ける),E - 투명화 (공격하면 풀림),E - niewidzialność (cios ją przerywa),E - invisibilidade (um golpe a quebra),E - невидимость (удар снимает),E - görünmezlik (vuruş bozar),E - 隐身(攻击会解除),E - 隱身(攻擊會解除) necroPossessKeyRmb,items,Item,,,,RMB,,RMT,RMB,Clic droit,RMB,右クリック,우클릭,PPM,RMB,ПКМ,Sağ tık,右键,右鍵 -necroPossessHintSpitE,items,Item,,,,E - spit around you,,E - um sich speien,E: escupir a tu alrededor,E : cracher autour de soi,E - sputa tutto intorno,E: 周囲に吐きかける,E - 주변에 침 뱉기,E - pluj wokół siebie,E - cuspir ao redor,E - плевок вокруг себя,E - etrafına tükür,E - 向四周喷吐,E - 向四周噴吐 +necroPossessHintSpitE,items,Item,,,,E - spit where you aim,,E - dorthin speien wohin du zielst,E: escupir hacia donde apuntas,E : cracher là où tu vises,E - sputa dove miri,E: 狙った場所に吐きかける,E - 조준한 곳에 침 뱉기,E - pluj tam gdzie celujesz,E - cuspir onde você mira,E - плевок в прицел,E - nişan aldığın yere tükür,E - 向准星处喷吐,E - 向準星處噴吐 necroPossessHintZoom,items,Item,,,,RMB (hold) - keen eye: zoom like a scope,,RMT (halten) - scharfes Auge: Zoom wie ein Zielfernrohr,"RMB (mantener): ojo avizor, zoom como una mira","Clic droit (maintenir) : œil perçant, zoom comme une lunette",RMB (tieni premuto) - occhio acuto: zoom come un mirino,右クリック(長押し): 鋭い目・スコープのようにズーム,우클릭(누르기) - 매의 눈: 조준경처럼 확대,PPM (przytrzymaj) - bystre oko: przybliżenie jak luneta,RMB (segurar) - olho aguçado: zoom como uma luneta,"ПКМ (удерживать) - зоркий глаз: приближение, как у прицела",Sağ tık (basılı tut) - keskin göz: dürbün gibi yakınlaştır,右键(按住)- 锐眼:像瞄准镜一样放大,右鍵(按住)- 銳眼:像瞄準鏡一樣放大 necroPossessHintLeap,items,Item,,,,Space or E - long leap,,Leertaste oder E - weiter Sprung,Espacio o E: salto largo,Espace ou E : grand bond,Spazio o E - balzo lungo,スペースまたはE: 大ジャンプ,스페이스 또는 E - 멀리뛰기,Spacja lub E - daleki skok,Espaço ou E - salto longo,Пробел или E - дальний прыжок,Boşluk veya E - uzun sıçrayış,空格或E - 远跳,空白鍵或E - 遠跳 necroPetRecallHint,items,Item,,,,Recall pet - E,,Diener zurückrufen - E,Devolver al sirviente - E,Rappeler le serviteur - E,Richiama il servitore - E,僕を呼び戻す - E,종복 불러들이기 - E,Odwołaj sługę - E,Chamar o servo de volta - E,Отозвать питомца - E,Hizmetkârı geri çağır - E,召回仆从 - E,召回僕從 - E diff --git a/HarmonySrc/Possession.cs b/HarmonySrc/Possession.cs index c8982c0..049e7b3 100644 --- a/HarmonySrc/Possession.cs +++ b/HarmonySrc/Possession.cs @@ -592,7 +592,7 @@ namespace NecromancerTome } break; case PossessionPowers.Kind.Mutant: - Ranged(z, detonate, detonate); // E - плевок веером вокруг себя (указание 13.10) + Ranged(z, detonate, detonate); // E - плевок (указание 13.10), в прицел - Patch_ProjectileMoveScript_Fire_PossessedSpit break; default: Ranged(z, ranged, rangedHeld); @@ -1523,7 +1523,9 @@ namespace NecromancerTome return best != null ? best.getChestPosition() : aim; } - /// Точка под прицелом камеры (в мировых координатах), до 50 м. + /// Точка под прицелом камеры (в мировых координатах), до 50 м. Тело самого одержимого луч + /// пропускает (13.10: «у всех зомби должен краснеть прицел», «у мутанта атака не целится»): камера + /// за головой, и у крупных зомби луч упирался в собственную спину - точка прицела была на себе. static Vector3 AimPoint() { Transform cam = _player?.cameraTransform; @@ -1531,14 +1533,42 @@ namespace NecromancerTome { return Zombie.position + Quaternion.Euler(-_pitch, _yaw, 0f) * Vector3.forward * 30f; } - if (Physics.Raycast(cam.position, cam.forward, out RaycastHit hit, 50f, -5, QueryTriggerInteraction.Ignore) && - hit.distance > CamBack + 0.5f) + int n = Physics.RaycastNonAlloc(cam.position, cam.forward, _aimHits, 50f, -5, QueryTriggerInteraction.Ignore); + Transform self = Zombie != null ? Zombie.transform : null; + float best = float.MaxValue; + Vector3 point = Vector3.zero; + for (int i = 0; i < n; i++) { - return hit.point + Origin.position; + RaycastHit hit = _aimHits[i]; + if (hit.distance <= CamBack + 0.5f || hit.distance >= best || + self != null && (hit.transform == self || hit.transform.IsChildOf(self))) + { + continue; + } + best = hit.distance; + point = hit.point; + } + if (best < float.MaxValue) + { + return point + Origin.position; } return cam.position + Origin.position + cam.forward * 50f; } + static readonly RaycastHit[] _aimHits = new RaycastHit[32]; + + /// Куда летит плевок мутанта (указание 13.10: «у мутанта атака не целится»): грудь врага под + /// прицелом, иначе точка под прицелом; дальше SpitRange - точка на луче к ней. + public static Vector3 SpitTarget(Vector3 _from) + { + EntityAlive foe = Zombie != null && Zombie.world != null ? GroundAimTarget(Zombie.world, Zombie) : null; + Vector3 to = foe != null ? foe.getChestPosition() : AimPoint(); + Vector3 d = to - _from; + return d.magnitude > SpitRange ? _from + d.normalized * SpitRange : to; + } + + public const float SpitRange = 15f; + /// ПКМ - дальняя атака зомби по шагам ванильной EAIRangedAttackTarget. static void Ranged(EntityAlive _z, bool _pressed, bool _held) { diff --git a/HarmonySrc/PossessionPowers.cs b/HarmonySrc/PossessionPowers.cs index 5b36436..37affc8 100644 --- a/HarmonySrc/PossessionPowers.cs +++ b/HarmonySrc/PossessionPowers.cs @@ -352,4 +352,28 @@ namespace NecromancerTome } } } + + /// Плевок одержимого мутанта - в прицел (указание 13.10: «у мутанта атака не целится»). Ванильный + /// ItemActionVomit мутанта бросает снаряды веером под ноги (SpreadVerticalOffset -50, разброс 90° вбок) со + /// скоростью 4.5 м/с - ИИ так поливает того, кто вплотную. Здесь после вылета скорость каждого снаряда + /// пересчитывается навесом в Possession.SpitTarget, как ProjectileMoveScript.Fire делает для _isBallistic, с + /// небольшим разбросом между снарядами очереди. + [HarmonyPatch(typeof(ProjectileMoveScript), nameof(ProjectileMoveScript.Fire))] + public static class Patch_ProjectileMoveScript_Fire_PossessedSpit + { + public static void Postfix(ProjectileMoveScript __instance, Vector3 _idealStartPos, Entity _firingEntity) + { + if (!Possession.IsPossessed(_firingEntity) || PossessionPowers.Current != PossessionPowers.Kind.Mutant || + __instance.itemActionProjectile == null || __instance.itemActionProjectile.FlyTime < 0f) + { + return; + } + Vector3 from = _idealStartPos.y != 0f ? _idealStartPos : __instance.transform.position + Origin.position; + Vector3 d = Possession.SpitTarget(from) + Random.insideUnitSphere * 0.4f - from; + float t = Mathf.Clamp(d.magnitude / 12f, 0.2f, 1.2f); + __instance.velocity = d / t; + __instance.velocity.y -= __instance.gravity * 0.5f * t; + __instance.flyDirection = __instance.velocity.normalized; + } + } }