using Barotrauma.Extensions; using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class GeneticMaterial : ItemComponent, IServerSerializable { private readonly LocalizedString materialName; private Character targetCharacter; private AfflictionPrefab selectedEffect, selectedTaintedEffect; [Serialize("", IsPropertySaveable.Yes)] public string Effect { get; set; } [Serialize("geneticmaterialdebuff", IsPropertySaveable.Yes)] public Identifier TaintedEffect { get; set; } private bool tainted; [Serialize(false, IsPropertySaveable.Yes)] public bool Tainted { get { return tainted; } private set { if (!value) { return; } tainted = true; item.AllowDeconstruct = false; if (!TaintedEffect.IsEmpty) { selectedTaintedEffect = AfflictionPrefab.Prefabs.Where(a => a.Identifier == TaintedEffect || a.AfflictionType == TaintedEffect).GetRandomUnsynced(); } } } //only for saving the selected tainted effect [Serialize("", IsPropertySaveable.Yes)] public Identifier SelectedTaintedEffect { get { return selectedTaintedEffect?.Identifier ?? Identifier.Empty; } private set { selectedTaintedEffect = !value.IsEmpty ? AfflictionPrefab.Prefabs.Find(a => a.Identifier == value) : null; } } public GeneticMaterial(Item item, ContentXElement element) : base(item, element) { string nameId = element.GetAttributeString("nameidentifier", ""); if (!string.IsNullOrEmpty(nameId)) { materialName = TextManager.Get(nameId); } if (!string.IsNullOrEmpty(Effect)) { selectedEffect = AfflictionPrefab.Prefabs.Where(a => a.Identifier == Effect || a.AfflictionType == Effect).GetRandomUnsynced(); } } [Serialize(3.0f, IsPropertySaveable.No)] public float ConditionIncreaseOnCombineMin { get; set; } [Serialize(8.0f, IsPropertySaveable.No)] public float ConditionIncreaseOnCombineMax { get; set; } public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial) { return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted && item.AllowDeconstruct && otherGeneticMaterial.item.AllowDeconstruct; } public override void Equip(Character character) { if (character == null) { return; } IsActive = true; if (targetCharacter != null) { return; } if (selectedEffect != null) { targetCharacter = character; ApplyStatusEffects(ActionType.OnWearing, 1.0f, targetCharacter); float selectedEffectStrength = GetCombinedEffectStrength(); character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength)); var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect); if (affliction != null) { affliction.Strength = selectedEffectStrength; //force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing affliction.SetStrength(selectedEffectStrength); } #if SERVER item.CreateServerEvent(this); #endif } if (tainted && selectedTaintedEffect != null) { float selectedTaintedEffectStrength = GetCombinedTaintedEffectStrength(); character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength)); var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect); if (affliction != null) { affliction.Strength = selectedTaintedEffectStrength; //force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing affliction.SetStrength(selectedTaintedEffectStrength); } targetCharacter = character; #if SERVER item.CreateServerEvent(this); #endif } foreach (Item containedItem in item.ContainedItems) { containedItem.GetComponent()?.Equip(character); } } public override void Update(float deltaTime, Camera cam) { base.Update(deltaTime, cam); if (targetCharacter != null) { var rootContainer = item.RootContainer; if (!targetCharacter.HasEquippedItem(item) && (rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface))) { item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter); IsActive = false; var affliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect); if (affliction != null) { affliction.Strength = GetCombinedEffectStrength(); } var taintedAffliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect); if (taintedAffliction != null) { taintedAffliction.Strength = GetCombinedTaintedEffectStrength(); } targetCharacter = null; } } } public enum CombineResult { None, Refined, Combined } public CombineResult Combine(GeneticMaterial otherGeneticMaterial, Character user) { if (!CanBeCombinedWith(otherGeneticMaterial)) { return CombineResult.None; } float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax); conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f; if (item.Prefab == otherGeneticMaterial.item.Prefab) { float taintedProbability = GetTaintedProbabilityOnRefine(otherGeneticMaterial, user); item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease; if (taintedProbability >= Rand.Range(0.0f, 1.0f)) { MakeTainted(); } return CombineResult.Refined; } else { item.Condition = otherGeneticMaterial.Item.Condition = (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + conditionIncrease; item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null); item.AllowDeconstruct = false; otherGeneticMaterial.Item.AllowDeconstruct = false; if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f)) { MakeTainted(); } return CombineResult.Combined; } } private float GetCombinedEffectStrength() { float effectStrength = 0.0f; foreach (Item otherItem in targetCharacter.Inventory.FindAllItems(recursive: true)) { var geneticMaterial = otherItem.GetComponent(); if (geneticMaterial == null || !geneticMaterial.IsActive) { continue; } if (geneticMaterial.selectedEffect == selectedEffect) { effectStrength += otherItem.ConditionPercentage / 100.0f * selectedEffect.MaxStrength; } } return effectStrength; } private float GetCombinedTaintedEffectStrength() { float taintedEffectStrength = 0.0f; foreach (Item otherItem in targetCharacter.Inventory.FindAllItems(recursive: true)) { var geneticMaterial = otherItem.GetComponent(); if (geneticMaterial == null || !geneticMaterial.IsActive) { continue; } if (selectedTaintedEffect != null && geneticMaterial.selectedTaintedEffect == selectedTaintedEffect) { taintedEffectStrength += otherItem.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength; } } return taintedEffectStrength; } private float GetTaintedProbabilityOnRefine(GeneticMaterial otherGeneticMaterial, Character user) { if (user == null) { return 1.0f; } float probability = MathHelper.Lerp(0.0f, 0.99f, Math.Max(item.Condition, otherGeneticMaterial.Item.Condition) / 100.0f); probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user)); return MathHelper.Clamp(probability, 0.0f, 1.0f); } private static float GetTaintedProbabilityOnCombine(Character user) { if (user == null) { return 1.0f; } float probability = 1.0f - user.GetStatValue(StatTypes.GeneticMaterialTaintedProbabilityReductionOnCombine); return MathHelper.Clamp(probability, 0.0f, 1.0f); } private void MakeTainted() { if (GameMain.NetworkMember?.IsClient ?? false) { return; } Tainted = true; #if SERVER item.CreateServerEvent(this); #endif } public static LocalizedString TryCreateName(ItemPrefab prefab, XElement element) { foreach (XElement subElement in element.Elements()) { if (subElement.NameAsIdentifier() == nameof(GeneticMaterial)) { Identifier nameId = subElement.GetAttributeIdentifier("nameidentifier", ""); if (!nameId.IsEmpty) { return prefab.Name.Replace("[type]", TextManager.Get(nameId).Fallback(nameId.Value)); } } } return prefab.Name; } } }