Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs
2024-06-18 16:50:02 +03:00

613 lines
24 KiB
C#

using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Abilities;
using System.Collections.Immutable;
namespace Barotrauma
{
public enum WearableType
{
Item,
Hair,
Beard,
Moustache,
FaceAttachment,
Husk,
Herpes
}
class WearableSprite
{
public ContentPath UnassignedSpritePath { get; private set; }
public string SpritePath { get; private set; }
public ContentXElement SourceElement { get; private set; }
public WearableType Type { get; private set; }
private Sprite _sprite;
public Sprite Sprite
{
get { return _sprite; }
private set
{
if (value == _sprite) { return; }
if (_sprite != null)
{
_sprite.Remove();
}
_sprite = value;
}
}
public LimbType Limb { get; private set; }
public bool HideLimb { get; private set; }
public enum ObscuringMode
{
None,
Hide,
AlphaClip
}
public ObscuringMode ObscureOtherWearables { get; private set; }
public bool HideOtherWearables => ObscureOtherWearables == ObscuringMode.Hide;
public bool AlphaClipOtherWearables => ObscureOtherWearables == ObscuringMode.AlphaClip;
public bool CanBeHiddenByOtherWearables { get; private set; }
/// <summary>
/// Tags or identifiers of items that can hide this one.
/// </summary>
public ImmutableHashSet<Identifier> CanBeHiddenByItem { get; private set; }
public List<WearableType> HideWearablesOfType { get; private set; }
public bool InheritLimbDepth { get; private set; }
/// <summary>
/// Does the wearable inherit all the scalings of the wearer? Also the wearable's own scale is used!
/// </summary>
public bool InheritScale { get; private set; }
public bool IgnoreRagdollScale { get; private set; }
public bool IgnoreLimbScale { get; private set; }
public bool IgnoreTextureScale { get; private set; }
public bool InheritOrigin { get; private set; }
public bool InheritSourceRect { get; private set; }
public float Scale { get; private set; }
public float Rotation { get; private set; }
public LimbType DepthLimb { get; private set; }
private Wearable _wearableComponent;
public Wearable WearableComponent
{
get { return _wearableComponent; }
set
{
if (value == _wearableComponent) { return; }
if (_wearableComponent != null)
{
_wearableComponent.Remove();
}
_wearableComponent = value;
}
}
public string Sound { get; private set; }
public Point? SheetIndex { get; private set; }
public LightComponent LightComponent => LightComponents?.FirstOrDefault();
public List<LightComponent> LightComponents
{
get
{
if (_lightComponents == null)
{
_lightComponents = new List<LightComponent>();
}
return _lightComponents;
}
}
private List<LightComponent> _lightComponents;
public int Variant { get; set; }
private Character _picker;
/// <summary>
/// None = Any/Not Defined -> no effect.
/// Changing the gender forces re-initialization, because the textures can be different for male and female characters.
/// </summary>
public Character Picker
{
get { return _picker; }
set
{
if (value == _picker) { return; }
_picker = value;
IsInitialized = false;
UnassignedSpritePath = ParseSpritePath(SourceElement);
Init(_picker);
}
}
public WearableSprite(ContentXElement subElement, WearableType type)
{
Type = type;
SourceElement = subElement;
UnassignedSpritePath = subElement.GetAttributeContentPath("texture") ?? ContentPath.Empty;
Init();
switch (type)
{
case WearableType.Hair:
case WearableType.Beard:
case WearableType.Moustache:
case WearableType.FaceAttachment:
case WearableType.Husk:
case WearableType.Herpes:
Limb = LimbType.Head;
break;
}
}
/// <summary>
/// Note: this constructor cannot initialize automatically, because the gender is unknown at this point. We only know it when the item is equipped.
/// </summary>
public WearableSprite(ContentXElement subElement, Wearable wearable, int variant = 0)
{
Type = WearableType.Item;
WearableComponent = wearable;
Variant = Math.Max(variant, 0);
UnassignedSpritePath = ParseSpritePath(subElement);
SourceElement = subElement;
}
private ContentPath ParseSpritePath(ContentXElement element)
{
if (element.DoesAttributeReferenceFileNameAlone("texture"))
{
string textureName = element.GetAttributeString("texture", "");
return ContentPath.FromRaw(
element.ContentPackage,
$"{Path.GetDirectoryName(WearableComponent.Item.Prefab.FilePath)}/{textureName}");
}
else
{
return element.GetAttributeContentPath("texture") ?? ContentPath.Empty;
}
}
public void ParsePath(bool parseSpritePath)
{
#if SERVER
// Server doesn't care about texture paths at all
return;
#endif
SpritePath = UnassignedSpritePath.Value;
if (_picker?.Info != null)
{
SpritePath = _picker.Info.ReplaceVars(SpritePath);
}
SpritePath = SpritePath.Replace("[VARIANT]", Variant.ToString());
if (!File.Exists(SpritePath))
{
// If the variant does not exist, parse the path so that it uses first variant.
SpritePath = SpritePath.Replace("[VARIANT]", "1");
}
if (!File.Exists(SpritePath) && _picker?.Info == null)
{
// If there's no character info is defined, try to use first tagset from CharacterInfoPrefab
var charInfoPrefab = CharacterPrefab.HumanPrefab.CharacterInfoPrefab;
SpritePath = charInfoPrefab.ReplaceVars(SpritePath, charInfoPrefab.Heads.First());
}
if (parseSpritePath)
{
Sprite?.ParseTexturePath(file: SpritePath);
}
}
public bool IsInitialized { get; private set; }
public void Init(Character picker = null)
{
if (IsInitialized) { return; }
_picker = picker;
ParsePath(false);
Sprite?.Remove();
Sprite = new Sprite(SourceElement, file: SpritePath);
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
foreach (var mode in Enum.GetValues<ObscuringMode>())
{
if (mode == ObscuringMode.None) { continue; }
if (SourceElement.GetAttributeBool($"{mode}OtherWearables", false))
{
ObscureOtherWearables = mode;
}
}
CanBeHiddenByOtherWearables = SourceElement.GetAttributeBool("canbehiddenbyotherwearables", true);
CanBeHiddenByItem = SourceElement.GetAttributeIdentifierArray(nameof(CanBeHiddenByItem), Array.Empty<Identifier>()).ToImmutableHashSet();
InheritLimbDepth = SourceElement.GetAttributeBool("inheritlimbdepth", true);
var scale = SourceElement.GetAttribute("inheritscale");
if (scale != null)
{
InheritScale = scale.GetAttributeBool(Type != WearableType.Item);
}
else
{
InheritScale = SourceElement.GetAttributeBool("inherittexturescale", Type != WearableType.Item);
}
IgnoreLimbScale = SourceElement.GetAttributeBool("ignorelimbscale", false);
IgnoreTextureScale = SourceElement.GetAttributeBool("ignoretexturescale", false);
IgnoreRagdollScale = SourceElement.GetAttributeBool("ignoreragdollscale", false);
SourceElement.GetAttributeBool("inherittexturescale", false);
InheritOrigin = SourceElement.GetAttributeBool("inheritorigin", Type != WearableType.Item);
InheritSourceRect = SourceElement.GetAttributeBool("inheritsourcerect", Type != WearableType.Item);
DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true);
Sound = SourceElement.GetAttributeString("sound", "");
Scale = SourceElement.GetAttributeFloat("scale", 1.0f);
Rotation = MathHelper.ToRadians(SourceElement.GetAttributeFloat("rotation", 0.0f));
var index = SourceElement.GetAttributePoint("sheetindex", new Point(-1, -1));
if (index.X > -1 && index.Y > -1)
{
SheetIndex = index;
}
HideWearablesOfType = new List<WearableType>();
var wearableTypes = SourceElement.GetAttributeStringArray("hidewearablesoftype", null);
if (wearableTypes != null && wearableTypes.Length > 0)
{
foreach (var value in wearableTypes)
{
if (Enum.TryParse(value, ignoreCase: true, out WearableType wearableType))
{
HideWearablesOfType.Add(wearableType);
}
}
}
IsInitialized = true;
}
}
}
namespace Barotrauma.Items.Components
{
partial class Wearable : Pickable, IServerSerializable
{
private readonly ContentXElement[] wearableElements;
private readonly WearableSprite[] wearableSprites;
private readonly LimbType[] limbType;
private readonly Limb[] limb;
private readonly List<DamageModifier> damageModifiers;
public readonly Dictionary<Identifier, float> SkillModifiers;
public readonly Dictionary<StatTypes, float> WearableStatValues = new Dictionary<StatTypes, float>();
public IEnumerable<DamageModifier> DamageModifiers
{
get { return damageModifiers; }
}
public bool AutoEquipWhenFull { get; private set; }
public bool DisplayContainedStatus { get; private set; }
[Serialize(false, IsPropertySaveable.No, description: "Can the item be used (assuming it has components that are usable in some way) when worn.")]
public bool AllowUseWhenWorn { get; set; }
public readonly int Variants;
private int variant;
public int Variant
{
get { return variant; }
set
{
if (variant == value) { return; }
#if SERVER
variant = value;
if (!item.Submarine?.Loading ?? true)
{
item.CreateServerEvent(this);
}
#elif CLIENT
Character character = picker;
if (character != null)
{
Unequip(character);
}
for (int i = 0; i < wearableSprites.Length; i++)
{
var subElement = wearableElements[i];
wearableSprites[i]?.Sprite?.Remove();
wearableSprites[i] = new WearableSprite(subElement, this, value);
}
if (character != null)
{
Equip(character);
}
variant = value;
#endif
}
}
/// <summary>
/// How deep down does the item protect from pressure? Determined by status effects.
/// </summary>
public readonly float PressureProtection;
public Wearable(Item item, ContentXElement element) : base(item, element)
{
this.item = item;
damageModifiers = new List<DamageModifier>();
SkillModifiers = new Dictionary<Identifier, float>();
int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
Variants = element.GetAttributeInt("variants", 0);
variant = Rand.Range(1, Variants + 1, Rand.RandSync.ServerAndClient);
wearableSprites = new WearableSprite[spriteCount];
wearableElements = new ContentXElement[spriteCount];
limbType = new LimbType[spriteCount];
limb = new Limb[spriteCount];
AutoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
DisplayContainedStatus = element.GetAttributeBool("displaycontainedstatus", false);
int i = 0;
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
if (subElement.GetAttribute("texture") == null)
{
DebugConsole.ThrowError("Item \"" + item.Name + "\" doesn't have a texture specified!",
contentPackage: element.ContentPackage);
return;
}
limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
subElement.GetAttributeString("limb", "Head"), true);
wearableSprites[i] = new WearableSprite(subElement, this, variant);
wearableElements[i] = subElement;
foreach (var lightElement in subElement.Elements())
{
if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
wearableSprites[i].LightComponents.Add(new LightComponent(item, lightElement)
{
Parent = this
});
foreach (var light in wearableSprites[i].LightComponents)
{
item.AddComponent(light);
}
}
i++;
break;
case "damagemodifier":
damageModifiers.Add(new DamageModifier(subElement, item.Name + ", Wearable"));
break;
case "skillmodifier":
Identifier skillIdentifier = subElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
float skillValue = subElement.GetAttributeFloat("skillvalue", 0f);
if (SkillModifiers.ContainsKey(skillIdentifier))
{
SkillModifiers[skillIdentifier] += skillValue;
}
else
{
SkillModifiers.TryAdd(skillIdentifier, skillValue);
}
break;
case "statvalue":
StatTypes statType = CharacterAbilityGroup.ParseStatType(subElement.GetAttributeString("stattype", ""), Name);
float statValue = subElement.GetAttributeFloat("value", 0f);
if (WearableStatValues.ContainsKey(statType))
{
WearableStatValues[statType] += statValue;
}
else
{
WearableStatValues.TryAdd(statType, statValue);
}
break;
case "statuseffect":
if (subElement.GetAttributeString("Target", string.Empty).ToLowerInvariant().Contains("character"))
{
PressureProtection = Math.Max(subElement.GetAttributeFloat(nameof(PressureProtection), 0), PressureProtection);
}
break;
}
}
}
public override void Equip(Character character)
{
foreach (var allowedSlot in allowedSlots)
{
if (allowedSlot == InvSlotType.Any) { continue; }
foreach (Enum value in Enum.GetValues(typeof(InvSlotType)))
{
var slotType = (InvSlotType)value;
if (slotType == InvSlotType.Any || slotType == InvSlotType.None) { continue; }
if (allowedSlot.HasFlag(slotType) && !character.Inventory.IsInLimbSlot(item, slotType))
{
return;
}
}
}
picker = character;
for (int i = 0; i < wearableSprites.Length; i++ )
{
var wearableSprite = wearableSprites[i];
if (!wearableSprite.IsInitialized) { wearableSprite.Init(picker); }
// If the item is gender specific (it has a different textures for male and female), we have to change the gender here so that the texture is updated.
wearableSprite.Picker = picker;
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
if (equipLimb == null) { continue; }
if (item.body != null)
{
item.body.Enabled = false;
}
IsActive = true;
if (wearableSprite.LightComponent != null)
{
foreach (var light in wearableSprite.LightComponents)
{
light.ParentBody = equipLimb.body;
}
}
limb[i] = equipLimb;
if (!equipLimb.WearingItems.Contains(wearableSprite))
{
equipLimb.WearingItems.Add(wearableSprite);
equipLimb.WearingItems.Sort((wearable, nextWearable) =>
{
float depth = wearable?.Sprite?.Depth ?? 0;
float nextDepth = nextWearable?.Sprite?.Depth ?? 0;
return nextDepth.CompareTo(depth);
});
equipLimb.WearingItems.Sort((wearable, nextWearable) =>
{
var wearableComponent = wearable?.WearableComponent;
var nextWearableComponent = nextWearable?.WearableComponent;
if (wearableComponent == null && nextWearableComponent == null) { return 0; }
if (wearableComponent == null) { return -1; }
if (nextWearableComponent == null) { return 1; }
return wearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes).CompareTo(nextWearableComponent.AllowedSlots.Contains(InvSlotType.OuterClothes));
});
}
#if CLIENT
equipLimb.UpdateWearableTypesToHide();
#endif
}
character.OnWearablesChanged();
}
public override void Drop(Character dropper, bool setTransform = true)
{
Character previousPicker = picker;
Unequip(picker);
base.Drop(dropper, setTransform);
previousPicker?.OnWearablesChanged();
picker = null;
IsActive = false;
}
public override void Unequip(Character character)
{
if (character == null || character.Removed) { return; }
if (picker == null) { return; }
for (int i = 0; i < wearableSprites.Length; i++)
{
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
if (equipLimb == null) { continue; }
if (wearableSprites[i].LightComponent != null)
{
foreach (var light in wearableSprites[i].LightComponents)
{
light.ParentBody = null;
}
}
equipLimb.WearingItems.RemoveAll(w => w != null && w == wearableSprites[i]);
#if CLIENT
equipLimb.UpdateWearableTypesToHide();
#endif
limb[i] = null;
}
IsActive = false;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
if (picker == null || picker.Removed)
{
IsActive = false;
return;
}
//if the item is also being held, let the Holdable component control the position
if (item.GetComponent<Holdable>() is not { IsActive: true })
{
item.SetTransform(picker.SimPosition, 0.0f);
}
item.ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
#if CLIENT
PlaySound(ActionType.OnWearing, picker);
#endif
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
Unequip(picker);
foreach (WearableSprite wearableSprite in wearableSprites)
{
wearableSprite?.Sprite?.Remove();
wearableSprite.Picker = null;
}
}
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
componentElement.Add(new XAttribute("variant", variant));
return componentElement;
}
private int loadedVariant = -1;
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
{
base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
loadedVariant = componentElement.GetAttributeInt("variant", -1);
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
//do this here to prevent creating a network event before the item has been fully initialized
if (loadedVariant > 0 && loadedVariant < Variants + 1)
{
Variant = loadedVariant;
}
}
public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteByte((byte)Variant);
base.ServerEventWrite(msg, c, extraData);
}
public override void ClientEventRead(IReadMessage msg, float sendingTime)
{
Variant = (int)msg.ReadByte();
base.ClientEventRead(msg, sendingTime);
}
}
}