Files
necromants-tome-7d2d-3-2/HarmonySrc/FpArms.cs
T

452 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Руки одержимого в виде от первого лица (указание 13.10: «делаем так, как у игрока»). У персонажа игры две
/// модели: полное тело (его видят другие и он сам со стороны) и отдельные руки для первого лица (baseRigFP,
/// SDCSUtils.CreateVizFP) - рисуются только у себя. Здесь собираем такие же руки по тем же шагам игры:
/// скелет baseRigFPPrefab с аниматором FPPlayerController, голая кожа (basePartsFP), без одежды и волос
/// (указание: «одежду пока не показываем»). Пол - по зомби (IsMale): BaseMale или BaseFemale из
/// archetypes.xml. Кожа серая (указание: «руки вообще серые - типа зомби их так видит») - цвет _Tint у
/// материала кожи, как игра красит тон кожи.
///
/// Руки - под UFPSRoot камеры игрока, как у персонажа; камеру одержимости мы ставим в голову зомби, руки
/// едут вместе с ней. Слой HoldingItem - его камера игрока рисует только при ShowHoldingItemLayer(true).
///
/// Только гуманоиды обычной комплекции (Allows), указание 13.10.
///
/// ПАМЯТЬ. Одна модель рук на время вида от первого лица; Hide выгружает её (SDCSUtils.UnloadViz - материалы
/// и ссылки на ассеты освобождаются) - на переключении вида и на выходе из одержимости.
/// </summary>
public static class FpArms
{
/// <summary>Начала имён классов, у кого вид от первого лица (варианты Feral/Radiated/Charged/Infernal - тоже).</summary>
static readonly string[] Allowed =
{
"zombieArlene", "zombieBiker", "zombieBoe", "zombieBowler", "zombieBurnt", "zombieBusinessMan", "zombieDarlene",
"zombieInmate", "zombieJanitor", "zombieJoe", "zombieLab", "zombieLumberjack", "zombieMaleHazmat", "zombieMarlene",
"zombieMoe", "zombieNurse", "zombiePartyGirl", "zombieSkateboarder", "zombieSoldier", "zombieSteve", "zombieTomClark",
"zombieUtilityWorker", "zombieYo",
};
public static Color Tint = new Color(0.6f, 0.6f, 0.6f, 1f);
/// <summary>Цветная текстура кожи (_Albedo): false - убрана, кожа ровно-серая по _Tint, рельеф - от карты нормалей
/// (указание 13.10: «руки вообще серые»; _Tint только умножает, смуглая кожа от него темнела, а не серела).</summary>
public static bool Albedo;
/// <summary>Сигнал аниматора рук на удар (подбор necroarms punch; у пустых рук игры - триггер атаки).</summary>
public static string PunchTrigger = "WeaponFire";
/// <summary>Параметры аниматора рук, выставленные necroarms set (подбор «пустых рук»), - ставятся и при пересборке.</summary>
static readonly Dictionary<string, float> _animParams = new Dictionary<string, float>
{
// Подобрано в игре 13.10: без них аниматор стоял в позе «держит мешок» (WeaponHoldType 45). 0 - пустые руки
// (HoldType у meleeHandPlayer), клип FP_Unarmed_Hold.
{ "IsAlive", 1f }, { "IsFPV", 1f }, { "WeaponHoldType", 0f },
};
/// <summary>Сдвиг рук от камеры (подбор necroarms pos). У персонажа игра опускает руки на 1.7 м: начало их
/// скелета - у ног (vp_FPWeapon PositionOffset 0,-1.7,0.02 для SDCS).</summary>
public static Vector3 Offset = new Vector3(0f, -1.55f, 0.05f); // подобрано в игре 13.10 (у персонажа -1.7, но руки рисуются с FOV 45)
/// <summary>Родитель: true - сама камера, false - её UFPSRoot (как у персонажа).</summary>
public static bool ParentCamera = true;
static GameObject _rig;
static SDCSUtils.TransformCatalog _bones;
static bool _layerWasShown;
public static bool Active => _rig != null;
/// <summary>Пол зомби. IsMale у зомби в entityclasses.xml не задан (все «мужчины» по умолчанию, тест 13.10:
/// Арлин получила мужские руки) - женщину выдают звуки zombiefemale*.</summary>
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)
{
return false;
}
string name = cls.entityClassName;
if (name.Contains("Crawler"))
{
return false;
}
foreach (string a in Allowed)
{
if (name.StartsWith(a))
{
return true;
}
}
return false;
}
public static void Show(EntityPlayerLocal _player, EntityAlive _z)
{
if (_rig != null || _player == null || _z == null)
{
return;
}
Archetype arch = Archetype.GetArchetype(IsFemale(_z) ? "BaseFemale" : "BaseMale");
Transform cam = _player.cameraTransform;
if (arch == null || cam == null)
{
Debug.LogWarning("[NecromancerTome] FpArms: нет архетипа или камеры - рук не будет");
return;
}
Transform parent = ParentCamera ? cam : cam.FindInChildren("UFPSRoot") ?? cam;
Archetype wasArch = SDCSUtils.tmpArchetype;
SDCSUtils.AssetRefs wasOwner = SDCSUtils.tmpAssetsOwner;
try
{
SDCSUtils.tmpArchetype = arch;
SDCSUtils.setupRig(ref _rig, ref _bones, SDCSUtils.baseRigFPPrefab, parent, SDCSUtils.FPAnimController);
SDCSUtils.tmpAssetsOwner = SDCSUtils.GetAssetOwnerForRig(_rig);
SDCSUtils.setupBase(_rig, _bones, SDCSUtils.basePartsFP, true);
}
finally
{
SDCSUtils.tmpArchetype = wasArch;
SDCSUtils.tmpAssetsOwner = wasOwner;
}
_rig.transform.SetLocalPositionAndRotation(Offset, Quaternion.identity);
// Скрипты префаба (vp_FPWeapon, риг IK и др.) рассчитаны на руки персонажа: включённая копия с ними повесила
// игру (тест 13.10: «прожал ~ и всё зависло», лог - vp_FPWeapon «Could not find CharacterController»).
// Рукам хватает скелета, аниматора и сетки - скрипты снимаем до включения.
// С конца и в два прохода: скрипт, от которого зависит другой ([RequireComponent]), снимется вторым.
int stripped = 0;
for (int pass = 0; pass < 2; pass++)
{
MonoBehaviour[] mbs = _rig.GetComponentsInChildren<MonoBehaviour>(true);
for (int i = mbs.Length - 1; i >= 0; i--)
{
if (mbs[i] != null)
{
Object.DestroyImmediate(mbs[i]);
stripped++;
}
}
}
// Префаб рук выключен: у персонажа его включает BodyAnimator (State = Visible) - тест 13.10: «активны False».
_rig.SetActive(true);
// Аниматор у персонажа включает тот же BodyAnimator (тест 13.10: «рука одна и без анимации»).
Animator anim = _rig.GetComponent<Animator>();
if (anim != null)
{
anim.enabled = true;
anim.cullingMode = AnimatorCullingMode.AlwaysAnimate;
anim.Rebind();
foreach (KeyValuePair<string, float> kv in _animParams)
{
SetParam(anim, kv.Key, kv.Value);
}
}
Debug.Log("[NecromancerTome] FpArms: снято скриптов " + stripped + ", аниматор " + (anim != null ? anim.runtimeAnimatorController?.name : "нет"));
_rig.name = "necroZombieArmsFP";
int layer = LayerMask.NameToLayer("HoldingItem");
int tinted = 0;
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
smr.gameObject.layer = layer;
// Материалы с _Tint игра уже размножила для этой модели (SDCSUtils.Stitch) - красим свои копии.
foreach (Material m in smr.sharedMaterials)
{
if (m != null && m.HasColor("_Tint"))
{
m.SetColor("_Tint", Tint);
if (!Albedo && m.HasProperty("_Albedo"))
{
m.SetTexture("_Albedo", null);
}
tinted++;
}
}
}
// Коллайдеры рук (у женского скелета игра вешает капсулу на таз) - выключены: руки в голове зомби ловили бы
// луч прицела и толкали бы предметы.
foreach (Collider c in _rig.GetComponentsInChildren<Collider>(true))
{
c.enabled = false;
}
foreach (HingeJoint j in _rig.GetComponentsInChildren<HingeJoint>(true))
{
if (j.connectedBody == null)
{
j.gameObject.SetActive(false);
}
}
_layerWasShown = _player.playerCamera != null && (_player.playerCamera.cullingMask & (1 << layer)) != 0;
_player.ShowHoldingItemLayer(true);
Debug.Log("[NecromancerTome] FpArms: руки " + arch.Name + " для " + _z.EntityName + ", серых материалов " + tinted +
", родитель " + parent.name);
}
/// <summary>Состояние рук для necroarms: где они относительно камеры, видны ли, кто рисует их слой.</summary>
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<SkinnedMeshRenderer>(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;
}
static Animator Anim => _rig != null ? _rig.GetComponent<Animator>() : null;
static bool SetParam(Animator _a, string _name, float _v)
{
foreach (AnimatorControllerParameter p in _a.parameters)
{
if (p.name != _name)
{
continue;
}
switch (p.type)
{
case AnimatorControllerParameterType.Float: _a.SetFloat(_name, _v); break;
case AnimatorControllerParameterType.Int: _a.SetInteger(_name, (int)_v); break;
case AnimatorControllerParameterType.Bool: _a.SetBool(_name, _v != 0f); break;
default: _a.SetTrigger(_name); break;
}
return true;
}
return false;
}
/// <summary>necroarms set: параметр аниматора (запоминается для пересборки; у триггера - только срабатывание).</summary>
public static string Set(string _name, float _v)
{
Animator a = Anim;
if (a == null)
{
return "рук нет";
}
if (!SetParam(a, _name, _v))
{
return "нет параметра " + _name;
}
_animParams[_name] = _v;
return _name + " = " + _v.ToString(CultureInfo.InvariantCulture);
}
/// <summary>Удар одержимого - руки бьют кулаком (указание 13.10: хват пустых рук, «там как раз анимация ударов
/// кулаками»).</summary>
public static void Punch()
{
Animator a = Anim;
if (a != null && !string.IsNullOrEmpty(PunchTrigger))
{
a.SetTrigger(PunchTrigger);
}
}
public static string Trigger(string _name)
{
Animator a = Anim;
if (a == null)
{
return "рук нет";
}
a.SetTrigger(_name);
return "триггер " + _name;
}
/// <summary>Аниматор рук: вкл, контроллер, текущие клипы по слоям и все параметры со значениями.</summary>
public static string DumpAnim()
{
Animator a = Anim;
if (a == null)
{
return "рук нет";
}
string s = "аниматор вкл " + a.enabled + ", контроллер " + a.runtimeAnimatorController?.name + ", слоёв " + a.layerCount;
for (int l = 0; l < a.layerCount; l++)
{
string clips = "";
foreach (AnimatorClipInfo ci in a.GetCurrentAnimatorClipInfo(l))
{
clips += " " + ci.clip?.name + "(" + ci.weight.ToString("F2") + ")";
}
s += "\n слой " + l + " " + a.GetLayerName(l) + " вес " + a.GetLayerWeight(l).ToString("F2") + ":" + clips;
}
foreach (AnimatorControllerParameter p in a.parameters)
{
string v = p.type == AnimatorControllerParameterType.Float ? a.GetFloat(p.name).ToString("F2", CultureInfo.InvariantCulture)
: p.type == AnimatorControllerParameterType.Int ? a.GetInteger(p.name).ToString()
: p.type == AnimatorControllerParameterType.Bool ? a.GetBool(p.name).ToString() : "trigger";
s += "\n " + p.name + " = " + v;
}
return s;
}
/// <summary>Материалы рук: шейдер и его цветовые/текстурные свойства (подбор серого).</summary>
public static string DumpMat()
{
if (_rig == null)
{
return "рук нет";
}
string s = "";
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
foreach (Material m in smr.sharedMaterials)
{
if (m == null)
{
continue;
}
s += "\n " + smr.name + " / " + m.name + " / " + m.shader.name + ":";
for (int i = 0; i < m.shader.GetPropertyCount(); i++)
{
UnityEngine.Rendering.ShaderPropertyType t = m.shader.GetPropertyType(i);
string n = m.shader.GetPropertyName(i);
if (t == UnityEngine.Rendering.ShaderPropertyType.Color)
{
s += " " + n + "=" + m.GetColor(n);
}
else if (t == UnityEngine.Rendering.ShaderPropertyType.Texture)
{
s += " " + n + "[" + (m.GetTexture(n) != null ? m.GetTexture(n).name : "-") + "]";
}
else
{
s += " " + n + "=" + m.GetFloat(n).ToString("F2", CultureInfo.InvariantCulture);
}
}
}
}
return s;
}
/// <summary>Пересобрать руки на месте (после necroarms pos/parent).</summary>
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)
{
return;
}
if (!_layerWasShown)
{
_player?.ShowHoldingItemLayer(false);
}
SDCSUtils.UnloadViz(_rig);
_rig = null;
_bones = null;
Debug.Log("[NecromancerTome] FpArms: руки убраны");
}
}
/// <summary>
/// necroarms - руки одержимого от первого лица (13.10): без параметров - состояние; pos x y z - сдвиг от камеры;
/// parent cam|ufps - к чему крепить. Сдвиг и родитель применяются сразу (руки пересобираются).
/// </summary>
[Preserve]
public class ConsoleCmdNecroArms : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necroarms" };
public override string getDescription() => "NecromancerTome: руки одержимого от первого лица - [pos x y z | parent cam|ufps | anim | mat | set <param> <value> | trigger <name> | tint r g b | albedo on|off | punch <trigger>]";
void Out(string _s)
{
SdtdConsole.Instance.Output(_s);
Debug.Log("[NecromancerTome] necroarms: " + _s);
}
public override void Execute(List<string> _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();
}
else if (cmd == "albedo" && _params.Count >= 2)
{
FpArms.Albedo = _params[1] == "on";
FpArms.Rebuild();
}
else if (cmd == "punch" && _params.Count >= 2)
{
FpArms.PunchTrigger = _params[1];
FpArms.Punch();
Out("удар - " + FpArms.PunchTrigger);
return;
}
else if (cmd == "anim")
{
Out(FpArms.DumpAnim());
return;
}
else if (cmd == "mat")
{
Out(FpArms.DumpMat());
return;
}
else if (cmd == "set" && _params.Count >= 3 && float.TryParse(_params[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float v))
{
Out(FpArms.Set(_params[1], v));
return;
}
else if (cmd == "trigger" && _params.Count >= 2)
{
Out(FpArms.Trigger(_params[1]));
return;
}
else if (cmd == "tint" && _params.Count >= 4 &&
float.TryParse(_params[1].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float r) &&
float.TryParse(_params[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float g) &&
float.TryParse(_params[3].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out float b))
{
FpArms.Tint = new Color(r, g, b, 1f);
FpArms.Rebuild();
}
Out(FpArms.Dump());
}
}
}