using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
///
/// Руки одержимого в виде от первого лица (указание 13.10: «делаем так, как у игрока»). У персонажа игры две
/// модели: полное тело (его видят другие и он сам со стороны) и отдельные руки для первого лица (baseRigFP,
/// SDCSUtils.CreateVizFP) - рисуются только у себя. Здесь собираем такие же руки по тем же шагам игры:
/// скелет baseRigFPPrefab с аниматором FPPlayerController, голая кожа (basePartsFP), без одежды и волос
/// (указание: «одежду пока не показываем»). Пол - по зомби (IsMale): BaseMale или BaseFemale из
/// archetypes.xml. Кожа серая (указание: «руки вообще серые - типа зомби их так видит») - цвет _Tint у
/// материала кожи, как игра красит тон кожи.
///
/// Руки - под UFPSRoot камеры игрока, как у персонажа; камеру одержимости мы ставим в голову зомби, руки
/// едут вместе с ней. Слой HoldingItem - его камера игрока рисует только при ShowHoldingItemLayer(true).
///
/// Только гуманоиды обычной комплекции (Allows), указание 13.10.
///
/// ПАМЯТЬ. Одна модель рук на время вида от первого лица; Hide выгружает её (SDCSUtils.UnloadViz - материалы
/// и ссылки на ассеты освобождаются) - на переключении вида и на выходе из одержимости.
///
public static class FpArms
{
/// Начала имён классов, у кого вид от первого лица (варианты Feral/Radiated/Charged/Infernal - тоже).
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 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;
static bool _layerWasShown;
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)
{
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);
// Префаб рук выключен: у персонажа его включает BodyAnimator (State = Visible) - тест 13.10: «активны False».
_rig.SetActive(true);
_rig.name = "necroZombieArmsFP";
int layer = LayerMask.NameToLayer("HoldingItem");
int tinted = 0;
foreach (SkinnedMeshRenderer smr in _rig.GetComponentsInChildren(true))
{
smr.gameObject.layer = layer;
// Материалы с _Tint игра уже размножила для этой модели (SDCSUtils.Stitch) - красим свои копии.
foreach (Material m in smr.sharedMaterials)
{
if (m != null && m.HasColor("_Tint"))
{
m.SetColor("_Tint", Tint);
tinted++;
}
}
}
// Коллайдеры рук (у женского скелета игра вешает капсулу на таз) - выключены: руки в голове зомби ловили бы
// луч прицела и толкали бы предметы.
foreach (Collider c in _rig.GetComponentsInChildren(true))
{
c.enabled = false;
}
foreach (HingeJoint j in _rig.GetComponentsInChildren(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);
}
/// Состояние рук для 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)
{
return;
}
if (!_layerWasShown)
{
_player?.ShowHoldingItemLayer(false);
}
SDCSUtils.UnloadViz(_rig);
_rig = null;
_bones = null;
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());
}
}
}