using System.Collections.Generic; using System.Globalization; using UnityEngine; using UnityEngine.Scripting; namespace NecromancerTome { /// /// Подбор посадки модели предмета в руке прямо в игре (10.10.2026, череп): как necropose для жестов. Сдвиг, поворот /// и масштаб применяются к дочерним узлам модели в руке (корень модели держит игра) каждый кадр - переживают смену /// слота. Итог - строкой в лог, затем переносится в настройку модели (NecroSkullSetup и т.п.) и бандл пересобирается /// при закрытой игре. /// public static class HeldTweak { public static bool Enabled; public static Vector3 Pos; // метры, в осях модели public static Vector3 Rot; // градусы, добавка к исходному повороту public static float Scale = 1f; /// Предмет, для которого подбирается посадка (тот, что был в руке при первой команде). Другие предметы /// не трогаются: 10.10 без этого поворот черепа применился и к книге, взятой следом. public static string Item; /// Исходные локальные значения детей модели (по экземпляру узла). static readonly Dictionary _base = new Dictionary(); public static Transform Model() { EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer(); return pl?.inventory?.GetHoldingItemTransform(); } public static string HeldName() { EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer(); return pl?.inventory?.holdingItem?.Name; } public static void Apply() { Transform m = Model(); if (m == null || (Enabled && HeldName() != Item)) { return; } for (int i = 0; i < m.childCount; i++) { Transform c = m.GetChild(i); int id = c.GetInstanceID(); if (!_base.TryGetValue(id, out var b)) { if (!Enabled) { continue; } b = (c.localPosition, c.localRotation, c.localScale); _base[id] = b; } if (Enabled) { c.localPosition = Scale * b.p + Pos; c.localRotation = Quaternion.Euler(Rot) * b.r; c.localScale = b.s * Scale; } else { c.localPosition = b.p; c.localRotation = b.r; c.localScale = b.s; _base.Remove(id); } } } public static string F(float _v) => _v.ToString("0.###", CultureInfo.InvariantCulture); public static string F(Vector3 _v) => F(_v.x) + " " + F(_v.y) + " " + F(_v.z); } /// /// necroheld - посадка модели в руке: /// pos x y z (см) | add x y z (см) | rot x y z (градусы) | scale s | off | show /// [Preserve] public class ConsoleCmdNecroHeld : ConsoleCmdAbstract { public override string[] getCommands() => new[] { "necroheld" }; public override string getDescription() => "NecromancerTome: посадка модели в руке - pos x y z (см)|add x y z (см)|rot x y z|scale s|off|show"; static bool Num(List _p, int _i, out float _v) { _v = 0f; return _p.Count > _i && float.TryParse(_p[_i].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out _v); } static bool Vec(List _p, out Vector3 _v) { bool ok = Num(_p, 1, out float x) & Num(_p, 2, out float y) & Num(_p, 3, out float z); _v = new Vector3(x, y, z); return ok; } public override void Execute(List _params, CommandSenderInfo _senderInfo) { string cmd = _params.Count > 0 ? _params[0] : "show"; Vector3 v; switch (cmd) { case "pos" when Vec(_params, out v): HeldTweak.Pos = v / 100f; break; case "add" when Vec(_params, out v): HeldTweak.Pos += v / 100f; break; case "rot" when Vec(_params, out v): HeldTweak.Rot = v; break; case "scale" when Num(_params, 1, out float s) && s > 0f: HeldTweak.Scale = s; break; case "off": HeldTweak.Enabled = false; HeldTweak.Pos = Vector3.zero; HeldTweak.Rot = Vector3.zero; HeldTweak.Scale = 1f; Out("посадка сброшена"); return; case "show": break; default: Out(getDescription()); return; } if (cmd != "show") { if (!HeldTweak.Enabled) { HeldTweak.Item = HeldTweak.HeldName(); } HeldTweak.Enabled = true; } Transform m = HeldTweak.Model(); Out("предмет " + HeldTweak.Item + ", модель " + (m != null ? m.name : "нет") + ": сдвиг (см) " + HeldTweak.F(HeldTweak.Pos * 100f) + ", поворот " + HeldTweak.F(HeldTweak.Rot) + ", масштаб " + HeldTweak.F(HeldTweak.Scale) + (HeldTweak.Enabled ? "" : " (выключено)")); } static void Out(string _s) { SdtdConsole.Instance.Output(_s); Debug.Log("[NecromancerTome] necroheld: " + _s); } } }