From a27caefc343e0cc70dc655f306e4f7e5f00f141c Mon Sep 17 00:00:00 2001 From: AlexCube Date: Tue, 13 Oct 2026 15:41:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D1=83=D0=BA=D0=B8=20=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=B8=D0=BC=D0=BE=D0=B3=D0=BE:=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=BE=20=D0=B7=D0=B2=D1=83=D0=BA=D0=B0=D0=BC=20?= =?UTF-8?q?zombiefemale,=20=D1=81=D0=B4=D0=B2=D0=B8=D0=B3=20-1.7=20=D0=BC?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=20=D0=BA=D0=B0=D0=BC=D0=B5=D1=80=D1=83,?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=B0=20necroarms=20(?= =?UTF-8?q?=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5,=20pos,?= =?UTF-8?q?=20parent)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- HarmonySrc/FpArms.cs | 100 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/HarmonySrc/FpArms.cs b/HarmonySrc/FpArms.cs index dd99db7..a846500 100644 --- a/HarmonySrc/FpArms.cs +++ b/HarmonySrc/FpArms.cs @@ -1,4 +1,7 @@ +using System.Collections.Generic; +using System.Globalization; using UnityEngine; +using UnityEngine.Scripting; namespace NecromancerTome { @@ -31,6 +34,11 @@ namespace NecromancerTome }; public static readonly Color Tint = new Color(0.55f, 0.56f, 0.55f, 1f); + /// Сдвиг рук от камеры (подбор necroarms pos). У персонажа игра опускает руки на 1.7 м: начало их + /// скелета - у ног (vp_FPWeapon PositionOffset 0,-1.7,0.02 для SDCS). + public static Vector3 Offset = new Vector3(0f, -1.7f, 0.02f); + /// Родитель: true - сама камера, false - её UFPSRoot (как у персонажа). + public static bool ParentCamera = true; static GameObject _rig; static SDCSUtils.TransformCatalog _bones; @@ -38,6 +46,18 @@ namespace NecromancerTome public static bool Active => _rig != null; + /// Пол зомби. IsMale у зомби в entityclasses.xml не задан (все «мужчины» по умолчанию, тест 13.10: + /// Арлин получила мужские руки) - женщину выдают звуки zombiefemale*. + public static bool IsFemale(EntityAlive _z) + { + if (_z == null || !EntityClass.list.TryGetValue(_z.entityClass, out EntityClass cls) || cls?.Properties == null) + { + return false; + } + string s = cls.Properties.GetString("SoundAlert"); + return s != null && s.ToLowerInvariant().Contains("female"); + } + public static bool Allows(EntityAlive _z) { if (_z == null || !EntityClass.list.TryGetValue(_z.entityClass, out EntityClass cls) || cls?.entityClassName == null) @@ -65,14 +85,14 @@ namespace NecromancerTome { return; } - Archetype arch = Archetype.GetArchetype(_z.IsMale ? "BaseMale" : "BaseFemale"); + Archetype arch = Archetype.GetArchetype(IsFemale(_z) ? "BaseFemale" : "BaseMale"); Transform cam = _player.cameraTransform; if (arch == null || cam == null) { Debug.LogWarning("[NecromancerTome] FpArms: нет архетипа или камеры - рук не будет"); return; } - Transform parent = cam.FindInChildren("UFPSRoot") ?? cam; + Transform parent = ParentCamera ? cam : cam.FindInChildren("UFPSRoot") ?? cam; Archetype wasArch = SDCSUtils.tmpArchetype; SDCSUtils.AssetRefs wasOwner = SDCSUtils.tmpAssetsOwner; try @@ -87,7 +107,7 @@ namespace NecromancerTome SDCSUtils.tmpArchetype = wasArch; SDCSUtils.tmpAssetsOwner = wasOwner; } - _rig.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); + _rig.transform.SetLocalPositionAndRotation(Offset, Quaternion.identity); _rig.name = "necroZombieArmsFP"; int layer = LayerMask.NameToLayer("HoldingItem"); int tinted = 0; @@ -123,6 +143,43 @@ namespace NecromancerTome ", родитель " + parent.name); } + /// Состояние рук для necroarms: где они относительно камеры, видны ли, кто рисует их слой. + public static string Dump() + { + if (_rig == null) + { + return "рук нет (вид от третьего лица или зомби не из списка)"; + } + Camera main = Camera.main; + Transform par = _rig.transform.parent; + string s = "активны " + _rig.activeInHierarchy + ", родитель " + (par != null ? par.name + (par.gameObject.activeInHierarchy ? "" : " (ВЫКЛЮЧЕН)") : "нет") + + ", сдвиг " + _rig.transform.localPosition.ToString("F2"); + foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren(true)) + { + Vector3 c = main != null ? main.transform.InverseTransformPoint(smr.bounds.center) : smr.bounds.center; + s += "\n " + smr.name + ": вкл " + smr.enabled + "/" + smr.gameObject.activeInHierarchy + ", виден " + smr.isVisible + + ", слой " + LayerMask.LayerToName(smr.gameObject.layer) + ", центр от камеры " + c.ToString("F2") + ", размер " + smr.bounds.size.ToString("F2"); + } + foreach (Camera c in Camera.allCameras) + { + s += "\n камера " + c.name + (c == main ? " (main)" : "") + ": слой HoldingItem " + + ((c.cullingMask & (1 << LayerMask.NameToLayer("HoldingItem"))) != 0 ? "рисует" : "нет") + ", near " + c.nearClipPlane.ToString("F3"); + } + return s; + } + + /// Пересобрать руки на месте (после necroarms pos/parent). + public static void Rebuild() + { + if (_rig == null || !Possession.Active || Possession.Zombie == null) + { + return; + } + EntityPlayerLocal p = Possession.Player; + Hide(p); + Show(p, Possession.Zombie); + } + public static void Hide(EntityPlayerLocal _player) { if (_rig == null) @@ -139,4 +196,41 @@ namespace NecromancerTome Debug.Log("[NecromancerTome] FpArms: руки убраны"); } } + + + /// + /// necroarms - руки одержимого от первого лица (13.10): без параметров - состояние; pos x y z - сдвиг от камеры; + /// parent cam|ufps - к чему крепить. Сдвиг и родитель применяются сразу (руки пересобираются). + /// + [Preserve] + public class ConsoleCmdNecroArms : ConsoleCmdAbstract + { + public override string[] getCommands() => new[] { "necroarms" }; + public override string getDescription() => "NecromancerTome: руки одержимого от первого лица - [pos x y z | parent cam|ufps]"; + + void Out(string _s) + { + SdtdConsole.Instance.Output(_s); + Debug.Log("[NecromancerTome] necroarms: " + _s); + } + + public override void Execute(List _params, CommandSenderInfo _senderInfo) + { + string cmd = _params.Count > 0 ? _params[0] : ""; + if (cmd == "pos" && _params.Count >= 4 && + float.TryParse(_params[1].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float x) && + float.TryParse(_params[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float y) && + float.TryParse(_params[3].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float z)) + { + FpArms.Offset = new Vector3(x, y, z); + FpArms.Rebuild(); + } + else if (cmd == "parent" && _params.Count >= 2) + { + FpArms.ParentCamera = _params[1] != "ufps"; + FpArms.Rebuild(); + } + Out(FpArms.Dump()); + } + } }