0.1500.3.0 (🗿 edition)

This commit is contained in:
Markus Isberg
2021-09-17 22:47:21 +09:00
parent 1231170fce
commit 5a6bbcc79e
75 changed files with 1145 additions and 441 deletions
@@ -161,8 +161,8 @@ namespace Barotrauma
for (int i = 0; i < container.Inventory.Capacity; i++)
{
if (container.Inventory.GetItemAt(i) != null) { continue; }
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab i && container.CanBeContained(i) &&
Config.ForbiddenAmmunition.None(id => id.Equals(i.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab)
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) &&
Config.ForbiddenAmmunition.None(id => id.Equals(ip.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab)
{
Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Wreck);
if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
@@ -1430,6 +1430,7 @@ namespace Barotrauma
//throwing conscious/moving characters around takes more force -> double the flow force
if (character.CanMove) { flowForce *= 2.0f; }
flowForce *= 1 - Math.Clamp(character.GetStatValue(StatTypes.FlowResistance), 0f, 1f);
float flowForceMagnitude = flowForce.Length();
float limbMultipier = limbs.Count(l => l.inWater) / (float)limbs.Length;
@@ -251,6 +251,8 @@ namespace Barotrauma
private readonly List<Attacker> lastAttackers = new List<Attacker>();
public IEnumerable<Attacker> LastAttackers => lastAttackers;
public Character LastAttacker => lastAttackers.LastOrDefault()?.Character;
public Character LastOrderedCharacter { get; private set; }
public Character SecondLastOrderedCharacter { get; private set; }
public Entity LastDamageSource;
@@ -2720,7 +2722,7 @@ namespace Barotrauma
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true;
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 5.0f * 5.0f;
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 2.5f * 2.5f;
bool wasRagdolled = false;
bool selfRagdolled = false;
@@ -3131,6 +3133,12 @@ namespace Barotrauma
{
var abilityOrderedCharacter = new AbilityCharacter(this);
orderGiver.CheckTalents(AbilityEffectType.OnGiveOrder, abilityOrderedCharacter);
if (orderGiver.LastOrderedCharacter != this)
{
orderGiver.SecondLastOrderedCharacter = orderGiver.LastOrderedCharacter;
orderGiver.LastOrderedCharacter = this;
}
}
if (AIController is HumanAIController humanAI)
@@ -3406,6 +3414,13 @@ namespace Barotrauma
AddDamage(worldPosition, attackAfflictions, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier * attackData.DamageMultiplier) :
DamageLimb(worldPosition, targetLimb, attackAfflictions, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier * attackData.DamageMultiplier, penetration: penetration + attackData.AddedPenetration);
if (attacker != null)
{
var abilityAttackResult = new AbilityAttackResult(attackResult);
attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult);
CheckTalents(AbilityEffectType.OnAttackedResult, abilityAttackResult);
}
if (limbHit == null) { return new AttackResult(); }
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
if (attacker != null)
@@ -3623,11 +3638,6 @@ namespace Barotrauma
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
}
if (attacker != null)
{
var abilityAttackResult = new AbilityAttackResult(attackResult);
attacker.CheckTalents(AbilityEffectType.OnAttackResult, abilityAttackResult);
}
return attackResult;
}
@@ -979,6 +979,8 @@ namespace Barotrauma
increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier;
}
increase *= 1f + Character.GetStatValue(StatTypes.SkillGainSpeed);
float prevLevel = Job.GetSkillLevel(skillIdentifier);
Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum));
@@ -1527,6 +1529,17 @@ namespace Barotrauma
return 0f;
}
}
public float GetSavedStatValue(StatTypes statType, string statIdentifier)
{
if (savedStatValues.TryGetValue(statType, out var statValues))
{
return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue);
}
else
{
return 0f;
}
}
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue)
{
@@ -971,7 +971,7 @@ namespace Barotrauma
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, float randomization = 0.0f)
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, bool ignoreHiddenAfflictions = false, float randomization = 0.0f)
{
//key = item identifier
//float = suitability
@@ -980,6 +980,7 @@ namespace Barotrauma
foreach (Affliction affliction in getAfflictions(limb))
{
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
if (ignoreHiddenAfflictions && affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
{
if (!treatmentSuitability.ContainsKey(treatment.Key))
@@ -7,12 +7,12 @@ namespace Barotrauma.Abilities
{
private enum WeaponType
{
Any = 0,
Melee = 1,
Any = 0,
Melee = 1,
Ranged = 2
};
private WeaponType weapontype;
private readonly WeaponType weapontype;
public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
switch (conditionElement.GetAttributeString("weapontype", ""))
@@ -43,7 +43,7 @@ namespace Barotrauma.Abilities
break;
default:
aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee;
break;
break;
}
}
}
@@ -6,12 +6,12 @@ namespace Barotrauma.Abilities
{
class AbilityConditionItem : AbilityConditionData
{
private readonly string identifier;
private readonly string[] identifiers;
private readonly string[] tags;
public AbilityConditionItem(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
identifier = conditionElement.GetAttributeString("identifier", string.Empty).ToLowerInvariant();
identifiers = conditionElement.GetAttributeStringArray("identifiers", Array.Empty<string>(), convertToLowerInvariant: true);
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
}
@@ -29,15 +29,15 @@ namespace Barotrauma.Abilities
if (itemPrefab != null)
{
if (!string.IsNullOrEmpty(identifier))
if (identifiers.Any())
{
if (itemPrefab.Identifier != identifier)
if (!identifiers.Any(t => itemPrefab.Identifier == t))
{
return false;
}
}
return tags.Any(t => itemPrefab.Tags.Any(p => t == p));
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
}
else
{
@@ -0,0 +1,20 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasVelocity : AbilityConditionDataless
{
private readonly float velocity;
public AbilityConditionHasVelocity(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
velocity = conditionElement.GetAttributeFloat("velocity", 0f);
}
protected override bool MatchesConditionSpecific()
{
return character.AnimController.Collider.LinearVelocity.LengthSquared() > velocity * velocity;
}
}
}
@@ -13,7 +13,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
if (character.Submarine == null || character.Submarine.TeamID != character.TeamID) { return false; }
if (!character.IsInFriendlySub) { return false; }
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
return currentFloodPercentage / 100 > floodPercentage;
}
@@ -10,6 +10,7 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects;
private readonly bool nearbyCharactersAppliesToSelf;
private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -18,6 +19,7 @@ namespace Barotrauma.Abilities
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
}
protected void ApplyEffectSpecific(Character targetCharacter)
@@ -26,7 +28,7 @@ namespace Barotrauma.Abilities
{
if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
// currently used this to spawn items on the targeted character
// currently used to spawn items on the targeted character
statusEffect.SetUser(targetCharacter);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter);
}
@@ -34,6 +36,10 @@ namespace Barotrauma.Abilities
{
targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
if (!nearbyCharactersAppliesToSelf)
{
targets.RemoveAll(c => c == Character);
}
statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToLastOrderedCharacter : CharacterAbilityApplyStatusEffects
{
public CharacterAbilityApplyStatusEffectsToLastOrderedCharacter(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect()
{
if (IsViableTarget(Character.LastOrderedCharacter))
{
ApplyEffectSpecific(Character.LastOrderedCharacter);
}
if (Character.HasAbilityFlag(AbilityFlags.AllowSecondOrderedTarget) && IsViableTarget(Character.SecondLastOrderedCharacter))
{
ApplyEffectSpecific(Character.SecondLastOrderedCharacter);
}
}
private bool IsViableTarget(Character targetCharacter)
{
if (targetCharacter == null || targetCharacter.Removed) { return false; }
if (targetCharacter == Character) { return false; }
return true;
}
}
}
@@ -27,8 +27,7 @@ namespace Barotrauma.Abilities
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", false);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
giveOnAddingFirstTime = abilityElement.GetAttributeBool("giveonaddingfirsttime", characterAbilityGroup.AbilityEffectType == AbilityEffectType.None);
}
public override void InitializeAbility(bool addingFirstTime)
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -7,13 +8,22 @@ namespace Barotrauma.Abilities
{
public override bool AppliesEffectOnIntervalUpdate => true;
private string skillIdentifier;
private float skillIncrease;
private readonly string skillIdentifier;
private readonly float skillIncrease;
public CharacterAbilityIncreaseSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
skillIncrease = abilityElement.GetAttributeFloat("skillincrease", 0f);
if (string.IsNullOrEmpty(skillIdentifier))
{
DebugConsole.ThrowError($"Error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill identifier not defined in CharacterAbilityIncreaseSkill.");
}
if (MathUtils.NearlyEqual(skillIncrease, 0))
{
DebugConsole.AddWarning($"Possible error in talent \"{characterAbilityGroup.CharacterTalent.DebugIdentifier}\" - skill increase set to 0.");
}
}
protected override void ApplyEffect()
@@ -35,7 +45,17 @@ namespace Barotrauma.Abilities
private void ApplyEffectSpecific(Character character)
{
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f);
if (skillIdentifier.Equals("random"))
{
var skill = character.Info?.Job?.Skills?.GetRandom();
if (skill == null) { return; }
character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f);
}
else
{
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, character.Position + Vector2.UnitY * 175.0f);
}
}
}
}
@@ -0,0 +1,34 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToFlooding : CharacterAbility
{
private readonly StatTypes statType;
private readonly float maxValue;
private float lastValue = 0f;
public CharacterAbilityModifyStatToFlooding(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched && Character.IsInFriendlySub)
{
float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
lastValue = currentFloodPercentage / 100f * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -4,8 +4,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityModifyValue : CharacterAbility
{
private float addedValue;
private float multiplyValue;
private readonly float addedValue;
private readonly float multiplyValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -6,12 +6,14 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityByTheBook : CharacterAbility
{
private int moneyAmount;
private int max;
private readonly int moneyAmount;
private readonly int experienceAmount;
private readonly int max;
public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0);
experienceAmount = abilityElement.GetAttributeInt("experienceamount", 0);
max = abilityElement.GetAttributeInt("max", 0);
}
@@ -28,6 +30,10 @@ namespace Barotrauma.Abilities
if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount);
foreach (Character character in Character.GetFriendlyCrew(Character))
{
character.Info?.GiveExperience(experienceAmount);
}
timesGiven++;
}
@@ -0,0 +1,43 @@
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityEnigmaMachine : CharacterAbility
{
private readonly float addedValue;
private readonly float multiplyValue;
private readonly string[] tags;
private readonly int maxMultiplyCount;
public CharacterAbilityEnigmaMachine(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is IAbilityValue abilityValue)
{
int multiplyCount = 0;
foreach (Item item in Item.ItemList)
{
if (item.Prefab.Tags.Any(t => tags.Contains(t)))
{
multiplyCount++;
if (multiplyCount == maxMultiplyCount)
{
break;
}
}
}
abilityValue.Value += addedValue * multiplyCount;
}
}
}
}
@@ -1,30 +0,0 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityIndustrialRevolution : CharacterAbility
{
float addedFabricationSpeed;
public CharacterAbilityIndustrialRevolution(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedFabricationSpeed = abilityElement.GetAttributeFloat("addedfabricationspeed", 0f);
}
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
{
if (conditionsMatched)
{
// not necessarily the cleanest or performant way, but at least this shouldn't break anything.
// must be done every frame in order to work.
if (Character.SelectedConstruction?.GetComponent<Fabricator>() is Fabricator fabricator && fabricator.IsActive)
{
fabricator.FabricationSpeedMultiplier += addedFabricationSpeed;
}
}
}
}
}
@@ -1,39 +0,0 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityTaskmaster : CharacterAbility
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsRemove;
private Character lastCharacter;
public CharacterAbilityTaskmaster(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
statusEffectsRemove = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsremove"));
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
{
if (targetCharacter == Character) { return; }
foreach (var statusEffect in statusEffectsRemove)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, lastCharacter);
}
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
lastCharacter = targetCharacter;
}
}
}
}
@@ -14,6 +14,8 @@ namespace Barotrauma.Abilities
// currently only used to turn off simulation if random conditions are in use
public bool IsActive { get; private set; } = true;
public readonly AbilityEffectType AbilityEffectType;
protected int maxTriggerCount { get; }
protected int timesTriggered = 0;
@@ -24,8 +26,9 @@ namespace Barotrauma.Abilities
// separate dictionaries for each type of characterability?
protected readonly List<CharacterAbility> characterAbilities = new List<CharacterAbility>();
public CharacterAbilityGroup(CharacterTalent characterTalent, XElement abilityElementGroup)
public CharacterAbilityGroup(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup)
{
AbilityEffectType = abilityEffectType;
CharacterTalent = characterTalent;
Character = CharacterTalent.Character;
maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
@@ -168,8 +171,7 @@ namespace Barotrauma.Abilities
public static StatTypes ParseStatType(string statTypeString, string debugIdentifier)
{
StatTypes statType;
if (!Enum.TryParse(statTypeString, true, out statType))
if (!Enum.TryParse(statTypeString, true, out StatTypes statType))
{
DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
}
@@ -8,7 +8,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGroupEffect : CharacterAbilityGroup
{
public CharacterAbilityGroupEffect(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup) { }
public CharacterAbilityGroupEffect(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) :
base(abilityEffectType, characterTalent, abilityElementGroup) { }
public void CheckAbilityGroup(AbilityObject abilityObject)
{
@@ -15,7 +15,8 @@ namespace Barotrauma.Abilities
private float effectDelayTimer;
public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup)
public CharacterAbilityGroupInterval(AbilityEffectType abilityEffectType, CharacterTalent characterTalent, XElement abilityElementGroup) :
base(abilityEffectType, characterTalent, abilityElementGroup)
{
// too many overlapping intervals could cause hitching? maybe randomize a little
interval = abilityElementGroup.GetAttributeFloat("interval", 0f);
@@ -85,14 +85,13 @@ namespace Barotrauma
// XML logic
private void LoadAbilityGroupInterval(XElement abilityGroup)
{
string name = abilityGroup.Name.ToString().ToLowerInvariant();
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(this, abilityGroup));
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(AbilityEffectType.Undefined, this, abilityGroup));
}
private void LoadAbilityGroupEffect(XElement abilityGroup)
{
AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none"));
AddAbilityGroupEffect(new CharacterAbilityGroupEffect(this, abilityGroup), abilityEffectType);
AddAbilityGroupEffect(new CharacterAbilityGroupEffect(abilityEffectType, this, abilityGroup), abilityEffectType);
}
public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None)
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -8,13 +7,19 @@ namespace Barotrauma
{
class TalentTree
{
public enum TalentTreeStageState
{
Invalid,
Locked,
Unlocked,
Available,
Highlighted
}
public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>();
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
private static HashSet<string> subtreeTalents = new HashSet<string>();
private const string PlaceholderTalent = "placeholder";
public XElement ConfigElement
{
get;
@@ -35,14 +40,13 @@ namespace Barotrauma
foreach (XElement subTreeElement in element.GetChildElements("subtree"))
{
TalentSubTrees.Add(new TalentSubTree(subTreeElement));
TalentSubTrees.Add(new TalentSubTree(subTreeElement));
}
// talents found and unlocked using the identifier wihin the talent tree, so no duplicates may occur
HashSet<string> duplicateSet = new HashSet<string>();
foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))))
{
if (talent == PlaceholderTalent) { continue; }
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null)
{
@@ -97,7 +101,7 @@ namespace Barotrauma
}
break;
default:
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}");
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}");
break;
}
}
@@ -117,10 +121,63 @@ namespace Barotrauma
return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? Enumerable.Empty<string>());
}
// i hate this function - markus
public static TalentTreeStageState GetTalentOptionStageState(Character character, string subTreeIdentifier, int index, List<string> selectedTalents)
{
if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; }
if (!JobTalentTrees.TryGetValue(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; }
TalentSubTree subTree = talentTree.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier);
if (subTree == null) { return TalentTreeStageState.Invalid; }
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier)))
{
return TalentTreeStageState.Unlocked;
}
if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier)))
{
return TalentTreeStageState.Highlighted;
}
bool hasTalentInLastTier = true;
bool isLastTalentPurchased = true;
int lastindex = index - 1;
if (lastindex >= 0)
{
TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex];
hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent);
isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier));
}
if (!hasTalentInLastTier)
{
return TalentTreeStageState.Locked;
}
bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0;
if (hasPointsForNewTalent)
{
return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available;
}
return TalentTreeStageState.Locked;
bool HasTalent(TalentPrefab t)
{
return selectedTalents.Contains(t.Identifier);
}
}
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable<string> selectedTalents)
{
if (talentIdentifier == PlaceholderTalent) { return false; }
if (character?.Info?.Job.Prefab == null) { return false; }
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; }
@@ -173,12 +230,16 @@ namespace Barotrauma
{
public string Identifier { get; }
public string DisplayName { get; }
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
public TalentSubTree(XElement subTreeElement)
{
Identifier = subTreeElement.GetAttributeString("identifier", "");
DisplayName = TextManager.Get("talenttree." + Identifier, returnNull: true) ?? Identifier;
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
{
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
@@ -196,6 +257,7 @@ namespace Barotrauma
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
{
string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty);
if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier))
{
DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\".");
@@ -12,16 +12,16 @@
public enum ActionType
{
Always, OnPicked, OnUse, OnSecondaryUse,
OnWearing, OnContaining, OnContained, OnNotContained,
OnActive, OnFailure, OnBroken,
OnFire, InWater, NotInWater,
OnImpact,
OnEating,
OnDamaged,
OnSevered,
OnProduceSpawned,
OnOpen, OnClose,
Always = 0, OnPicked = 1, OnUse = 2, OnSecondaryUse = 3,
OnWearing = 4, OnContaining = 5, OnContained = 6, OnNotContained = 7,
OnActive = 8, OnFailure = 9, OnBroken = 10,
OnFire = 11, InWater = 12, NotInWater = 13,
OnImpact = 14,
OnEating = 15,
OnDamaged = 16,
OnSevered = 17,
OnProduceSpawned = 18,
OnOpen = 19, OnClose = 20,
OnDeath = OnBroken,
OnSuccess,
OnAbility,
@@ -34,6 +34,7 @@
OnAttack,
OnAttackResult,
OnAttacked,
OnAttackedResult,
OnGainSkillPoint,
OnAllyGainSkillPoint,
OnRepairComplete,
@@ -57,7 +58,9 @@
OnGainMissionMoney,
OnItemDeconstructed,
OnItemDeconstructedMaterial,
OnItemDeconstructedRetainProbability,
OnStopTinkering,
OnItemPicked,
AfterSubmarineAttacked,
}
@@ -78,26 +81,37 @@
BuffDurationMultiplier,
DebuffDurationMultiplier,
MedicalItemEffectivenessMultiplier,
FlowResistance,
// Combat
AttackMultiplier,
TeamAttackMultiplier,
RangedAttackSpeed,
TurretAttackSpeed,
TurretPowerCostReduction,
TurretChargeSpeed,
MeleeAttackSpeed,
MeleeAttackMultiplier,
RangedAttackMultiplier,
RangedSpreadReduction,
// Utility
RepairSpeed,
DeconstructorSpeedMultiplier,
TinkeringDuration,
RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier,
MaxRepairConditionMultiplier,
IncreaseFabricationQuality,
GeneticMaterialRefineBonus,
GeneticMaterialTaintedProbabilityReductionOnCombine,
SkillGainSpeed,
// Misc
ReputationGainMultiplier,
MissionMoneyGainMultiplier,
ExperienceGainMultiplier,
MissionExperienceGainMultiplier,
// these should be deprecated and moved to their own implementation, no sense making them share space with stat values
Coathor,
Coauthor,
WarriorPoetMissionRuns,
WarriorPoetEnemiesKilled,
}
@@ -113,7 +127,8 @@
CanTinkerFabricatorsAndDeconstructors,
TinkeringPowersDevices,
GainSkillPastMaximum,
RetainExperienceForNewCharacter
RetainExperienceForNewCharacter,
AllowSecondOrderedTarget,
}
}
@@ -306,7 +306,7 @@ namespace Barotrauma
case 0:
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
requireKill.All(c => c.Removed || c.IsDead) &&
requireKill.All(c => c.Removed || c.IsDead || (c.LockHands && c.Submarine == Submarine.MainSub)) &&
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
{
State = 1;
@@ -382,11 +382,11 @@ namespace Barotrauma
State = newState;
}
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)));
private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m));
private bool Survived(Character character)
private bool DeadOrCaptured(Character character)
{
return character != null && !character.Removed && !character.IsDead;
return character != null && !character.Removed && (character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub));
}
public override void End()
@@ -247,8 +247,8 @@ namespace Barotrauma
public override void End()
{
var root = item.GetRootContainer() ?? item;
if (root.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed)
var root = item?.GetRootContainer() ?? item;
if (root?.CurrentHull?.Submarine == null || (!root.CurrentHull.Submarine.AtEndExit && !root.CurrentHull.Submarine.AtStartExit) || item.Removed)
{
return;
}
@@ -92,5 +92,12 @@ namespace Barotrauma.Extensions
{
return MathUtils.NearlyEqual(v.X, other.X) && MathUtils.NearlyEqual(v.Y, other.Y);
}
public static Vector2 Pad(this Vector2 v, Vector4 padding)
{
v.X += padding.X + padding.Z;
v.Y += padding.Y + padding.W;
return v;
}
}
}
@@ -60,7 +60,6 @@ namespace Barotrauma.Items.Components
}
}
public GeneticMaterial(Item item, XElement element)
: base(item, element)
{
@@ -85,7 +84,7 @@ namespace Barotrauma.Items.Components
public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial)
{
return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted;
return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted && item.AllowDeconstruct && otherGeneticMaterial.item.AllowDeconstruct;
}
public override void Equip(Character character)
@@ -147,9 +146,12 @@ namespace Barotrauma.Items.Components
public bool Combine(GeneticMaterial otherGeneticMaterial, Character user)
{
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
conditionIncrease *= 1.0f + user.GetStatValue(StatTypes.GeneticMaterialRefineBonus);
if (item.Prefab == otherGeneticMaterial.item.Prefab)
{
item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease;
float taintedProbability = GetTaintedProbabilityOnRefine(user);
if (taintedProbability >= Rand.Range(0.0f, 1.0f))
{
@@ -160,9 +162,14 @@ namespace Barotrauma.Items.Components
else
{
item.Condition = otherGeneticMaterial.Item.Condition =
(item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
(item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + conditionIncrease;
item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null);
MakeTainted();
item.AllowDeconstruct = false;
otherGeneticMaterial.Item.AllowDeconstruct = false;
if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f))
{
MakeTainted();
}
return false;
}
}
@@ -172,7 +179,14 @@ namespace Barotrauma.Items.Components
if (user == null) { return 1.0f; }
float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f);
probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user));
return probability;
return MathHelper.Clamp(probability, 0.0f, 1.0f);
}
private 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()
@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using Barotrauma.Abilities;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -73,12 +74,15 @@ namespace Barotrauma.Items.Components
if (PickingTime > 0.0f)
{
var abilityPickingTime = new AbilityValueItem(PickingTime, item.Prefab);
picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime);
if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue)
{
#if SERVER
item.CreateServerEvent(this);
#endif
pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, abilityPickingTime.Value));
}
return false;
}
@@ -523,7 +523,20 @@ namespace Barotrauma.Items.Components
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new ISerializableEntity[] { targetStructure });
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user);
float structureFixAmount = StructureFixAmount;
if (structureFixAmount >= 0f)
{
structureFixAmount *= 1 + user.GetStatValue(StatTypes.RepairToolStructureRepairMultiplier);
structureFixAmount *= 1 + item.GetQualityModifier(Quality.StatType.RepairToolStructureRepairMultiplier);
}
else
{
structureFixAmount *= 1 + user.GetStatValue(StatTypes.RepairToolStructureDamageMultiplier);
structureFixAmount *= 1 + item.GetQualityModifier(Quality.StatType.RepairToolStructureDamageMultiplier);
}
targetStructure.AddDamage(sectionIndex, -structureFixAmount * degreeOfSuccess, user);
//if the next section is small enough, apply the effect to it as well
//(to make it easier to fix a small "left-over" section)
@@ -535,7 +548,7 @@ namespace Barotrauma.Items.Components
(nextSectionLength > 0 && nextSectionLength < Structure.WallSectionSize * 0.3f))
{
//targetStructure.HighLightSection(sectionIndex + i);
targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess);
targetStructure.AddDamage(sectionIndex + i, -structureFixAmount * degreeOfSuccess);
}
}
return true;
@@ -606,7 +619,8 @@ namespace Barotrauma.Items.Components
levelResource.requiredItems.Any() &&
levelResource.HasRequiredItems(user, addMessage: false))
{
levelResource.DeattachTimer += deltaTime;
float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier);
levelResource.DeattachTimer += addedDetachTime;
#if CLIENT
Character.Controlled?.UpdateHUDProgressBar(
this,
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
public int Capacity
{
get { return capacity; }
set { capacity = Math.Max(value, 1); }
set { capacity = Math.Max(value, 0); }
}
//how many items can be contained
@@ -86,15 +86,9 @@ namespace Barotrauma.Items.Components
}
}
#if DEBUG
[Editable]
#endif
[Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")]
public Vector2 ItemPos { get; set; }
#if DEBUG
[Editable]
#endif
[Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")]
public Vector2 ItemInterval { get; set; }
@@ -329,11 +323,24 @@ namespace Barotrauma.Items.Components
{
return slotRestrictions.Any(s => s.MatchesItem(item));
}
public bool CanBeContained(Item item, int index)
{
if (index < 0 || index >= capacity) { return false; }
return slotRestrictions[index].MatchesItem(item);
}
public bool CanBeContained(ItemPrefab itemPrefab)
{
return slotRestrictions.Any(s => s.MatchesItem(itemPrefab));
}
public bool CanBeContained(ItemPrefab itemPrefab, int index)
{
if (index < 0 || index >= capacity) { return false; }
return slotRestrictions[index].MatchesItem(itemPrefab);
}
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public override void Update(float deltaTime, Camera cam)
@@ -256,6 +256,17 @@ namespace Barotrauma.Items.Components
}
}
if (user != null && !user.Removed)
{
var deconstructItemRetainProbability = new AbilityValueItem(0f, targetItem.Prefab);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedRetainProbability, deconstructItemRetainProbability);
if (deconstructItemRetainProbability.Value > Rand.Range(0f, 1f, Rand.RandSync.Unsynced))
{
allowRemove = false;
}
}
if (targetItem.AllowDeconstruct && allowRemove)
{
//drop all items that are inside the deconstructed item
@@ -24,12 +24,6 @@ namespace Barotrauma.Items.Components
private Character user;
public float FabricationSpeedMultiplier
{
get;
set;
}
private ItemContainer inputContainer, outputContainer;
[Serialize(1.0f, true)]
@@ -249,7 +243,6 @@ namespace Barotrauma.Items.Components
var availableIngredients = GetAvailableIngredients();
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
{
FabricationSpeedMultiplier = 1f;
CancelFabricating();
return;
}
@@ -286,8 +279,7 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f) * FabricationSpeedMultiplier;
FabricationSpeedMultiplier = 1f;
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f);
UpdateRequiredTimeProjSpecific();
@@ -328,13 +320,21 @@ namespace Barotrauma.Items.Components
var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem);
if (user != null)
int quality = 0;
if (user?.Info != null)
{
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem);
}
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
float floatQuality = 0.0f;
foreach (string tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
}
quality = (int)floatQuality;
}
var tempUser = user;
@@ -343,12 +343,20 @@ namespace Barotrauma.Items.Components
if (i < amountFittingContainer)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
});
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition,
onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); });
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
});
}
}
@@ -116,6 +116,8 @@ namespace Barotrauma.Items.Components
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
Voltage -= deltaTime;
}
public void InfectBallast(string identifier, bool allowMultiplePerShip = false)
@@ -0,0 +1,72 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Quality : ItemComponent
{
public const int MaxQuality = 3;
public enum StatType
{
Condition,
ExplosionRadius,
ExplosionDamage,
RepairSpeed,
RepairToolStructureRepairMultiplier,
RepairToolStructureDamageMultiplier,
RepairToolDeattachTimeMultiplier,
// unused as of now
AttackMultiplier,
AttackSpeedMultiplier,
ForceDoorsOpenSpeedMultiplier,
RangedSpreadReduction,
ChargeSpeedMultiplier,
MovementSpeedMultiplier,
// generic stats to be used for various needs, declared just in case (localization)
EffectivenessMultiplier,
PowerOutputMultiplier,
ConsumptionReductionMultiplier,
}
private readonly Dictionary<StatType, float> statValues = new Dictionary<StatType, float>();
private int qualityLevel;
[Serialize(0, false)]
public int QualityLevel
{
get { return qualityLevel; }
set { qualityLevel = MathHelper.Clamp(value, 0, MaxQuality); }
}
public Quality(Item item, XElement element) : base(item, element)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLower())
{
case "stattype":
case "statvalue":
case "qualitystat":
string statTypeString = subElement.GetAttributeString("stattype", "");
if (!Enum.TryParse(statTypeString, true, out StatType statType))
{
DebugConsole.ThrowError("Invalid stat type type \"" + statTypeString + "\" in item (" + item.prefab.Identifier + ")");
}
float statValue = subElement.GetAttributeFloat("value", 0f);
statValues.TryAdd(statType, statValue);
break;
}
}
}
public float GetValue(StatType statType)
{
if (!statValues.ContainsKey(statType)) { return 0.0f; }
return statValues[statType] * qualityLevel;
}
}
}
@@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components
public RemoteController(Item item, XElement element)
: base(item, element)
{
DrawHudWhenEquipped = false;
}
public override bool Select(Character character)
@@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components
{
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
{
private string header;
private readonly string header;
private float deteriorationTimer;
private float deteriorateAlwaysResetTimer;
@@ -182,6 +182,10 @@ namespace Barotrauma.Items.Components
if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; }
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
{
h.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
}
return false;
}
@@ -217,6 +221,11 @@ namespace Barotrauma.Items.Components
{
GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
{
GameMain.Server?.CreateEntityEvent(bestRepairItem, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, h, character.ID });
}
return false;
}
@@ -243,7 +252,7 @@ namespace Barotrauma.Items.Components
}
return true;
Item GetBestRepairItem(Character character)
static Item GetBestRepairItem(Character character)
{
return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault();
}
@@ -386,6 +395,9 @@ namespace Barotrauma.Items.Components
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed);
item.MaxRepairConditionMultiplier = 1 + CurrentFixer.GetStatValue(StatTypes.MaxRepairConditionMultiplier);
if (currentFixerAction == FixActions.Repair)
{
@@ -383,6 +383,10 @@ namespace Barotrauma.Items.Components
else
{
float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime;
if (chargeDeltaTime > 0f && user != null)
{
chargeDeltaTime *= 1f + user.GetStatValue(StatTypes.TurretChargeSpeed);
}
currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime);
}
tryingToCharge = false;
@@ -285,7 +285,7 @@ namespace Barotrauma.Items.Components
int i = 0;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLower())
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
if (subElement.Attribute("texture") == null)
@@ -97,13 +97,15 @@ namespace Barotrauma
private Dictionary<string, Connection> connections;
private List<Repairable> repairables;
private readonly List<Repairable> repairables;
private Queue<float> impactQueue = new Queue<float>();
private Quality qualityComponent;
private readonly Queue<float> impactQueue = new Queue<float>();
//a dictionary containing lists of the status effects in all the components of the item
private bool[] hasStatusEffectsOfType;
private Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
private readonly bool[] hasStatusEffectsOfType;
private readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
@@ -447,7 +449,7 @@ namespace Barotrauma
}
public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition);
public float MaxCondition => Prefab.Health * healthMultiplier;
public float MaxCondition => Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition));
public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition);
private float offsetOnSelectedMultiplier = 1.0f;
@@ -465,12 +467,18 @@ namespace Barotrauma
public float HealthMultiplier
{
get => healthMultiplier;
set
{
healthMultiplier = value;
}
set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
}
private float maxRepairConditionMultiplier = 1.0f;
[Serialize(1.0f, true)]
public float MaxRepairConditionMultiplier
{
get => maxRepairConditionMultiplier;
set { maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
}
//the default value should be Prefab.Health, but because we can't use it in the attribute,
//we'll just use NaN (which does nothing) and set the default value in the constructor/load
[Serialize(float.NaN, false), Editable]
@@ -618,6 +626,21 @@ namespace Barotrauma
get { return Prefab.UseInHealthInterface; }
}
public int Quality
{
get
{
return qualityComponent?.QualityLevel ?? 0;
}
set
{
if (qualityComponent != null)
{
qualityComponent.QualityLevel = value;
}
}
}
public bool InWater
{
get
@@ -933,6 +956,8 @@ namespace Barotrauma
ownInventory = itemContainer.Inventory;
}
qualityComponent = GetComponent<Quality>();
InitProjSpecific();
if (callOnItemLoaded)
@@ -1122,6 +1147,11 @@ namespace Barotrauma
if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty<T>(); }
return components.Where(c => c is T).Cast<T>();
}
public float GetQualityModifier(Quality.StatType statType)
{
return GetComponent<Quality>()?.GetValue(statType) ?? 0.0f;
}
public void RemoveContained(Item contained)
{
@@ -47,14 +47,14 @@ namespace Barotrauma
{
if (ItemOwnsSelf(item)) { return false; }
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(item)) { return false; }
if (!container.CanBeContained(item, i)) { return false; }
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i);
}
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
{
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(itemPrefab)) { return false; }
if (!container.CanBeContained(itemPrefab, i)) { return false; }
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i);
}
@@ -62,7 +62,7 @@ namespace Barotrauma
{
if (itemPrefab == null) { return 0; }
if (i < 0 || i >= slots.Length) { return 0; }
if (!container.CanBeContained(itemPrefab)) { return 0; }
if (!container.CanBeContained(itemPrefab, i)) { return 0; }
return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.GetMaxStackSize(i)), condition);
}
@@ -370,6 +370,9 @@ namespace Barotrauma
[Serialize(false, false, description: "Hides the condition bar displayed at the bottom of the inventory slot the item is in.")]
public bool HideConditionBar { get; set; }
[Serialize(false, false, description: "Hides the condition displayed in the item's tooltip.")]
public bool HideConditionInTooltip { get; set; }
//if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item
//if false, trigger areas define areas that can be used to highlight the item
[Serialize(true, false)]
@@ -1164,6 +1167,8 @@ namespace Barotrauma
DefaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false);
}
HideConditionInTooltip = element.GetAttributeBool("hideconditionintooltip", HideConditionBar);
//backwards compatibility
if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
{
@@ -128,6 +128,11 @@ namespace Barotrauma
}
float displayRange = Attack.Range;
if (damageSource is Item sourceItem)
{
displayRange *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius);
Attack.DamageMultiplier *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage);
}
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
@@ -142,7 +147,7 @@ namespace Barotrauma
if (displayRange < 0.1f) { return; }
if (Attack.GetStructureDamage(1.0f) > 0.0f || Attack.GetLevelWallDamage(1.0f) > 0.0f)
if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f))
{
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker);
}
@@ -211,9 +216,9 @@ namespace Barotrauma
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
if (dist > Attack.Range) { continue; }
if (dist > displayRange) { continue; }
if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
if (dist < displayRange * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
{
//don't apply OnFire effects if the item is inside a fireproof container
//(or if it's inside a container that's inside a fireproof container, etc)
@@ -240,7 +245,7 @@ namespace Barotrauma
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
{
float distFactor = 1.0f - dist / Attack.Range;
float distFactor = 1.0f - dist / displayRange;
float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
Vector2 explosionPos = worldPosition;
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
@@ -8,6 +9,7 @@ using System.Xml.Linq;
using Microsoft.Xna.Framework;
using File = Barotrauma.IO.File;
using FileStream = Barotrauma.IO.FileStream;
using Path = Barotrauma.IO.Path;
namespace Barotrauma
{
@@ -18,11 +20,12 @@ namespace Barotrauma
public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
XmlResolver = null,
IgnoreWhitespace = true,
};
public static XmlReader CreateReader(System.IO.Stream stream)
=> XmlReader.Create(stream, ReaderSettings);
public static XmlReader CreateReader(System.IO.Stream stream, string baseUri = "")
=> XmlReader.Create(stream, ReaderSettings, baseUri);
public static XDocument TryLoadXml(System.IO.Stream stream)
{
@@ -52,8 +55,8 @@ namespace Barotrauma
{
ToolBox.IsProperFilenameCase(filePath);
using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
using XmlReader reader = CreateReader(stream);
doc = XDocument.Load(reader);
using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath));
doc = XDocument.Load(reader, LoadOptions.SetBaseUri);
}
catch (Exception e)
{
@@ -79,7 +82,7 @@ namespace Barotrauma
try
{
using FileStream stream = File.Open(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
using XmlReader reader = CreateReader(stream);
using XmlReader reader = CreateReader(stream, Path.GetFullPath(filePath));
doc = XDocument.Load(reader);
}
catch
@@ -60,6 +60,8 @@ namespace Barotrauma
// Only used by conditionals targeting an item. By default, containers check the parent item. This allows you to check the grandparent instead.
public readonly bool TargetGrandParent;
public readonly bool TargetContainedItem;
// Remove this after refactoring
public static bool IsValid(XAttribute attribute)
{
@@ -112,6 +114,7 @@ namespace Barotrauma
TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false);
TargetSelf = attribute.Parent.GetAttributeBool("targetself", false);
TargetGrandParent = attribute.Parent.GetAttributeBool("targetgrandparent", false);
TargetContainedItem = attribute.Parent.GetAttributeBool("targetcontaineditem", false);
if (!Enum.TryParse(AttributeName, true, out Type))
{
@@ -171,6 +174,22 @@ namespace Barotrauma
public bool Matches(ISerializableEntity target)
{
if (TargetContainedItem)
{
if (target is Item item)
{
return item.ContainedItems.Any(it => Matches(it));
}
else if (target is Items.Components.ItemComponent ic)
{
return ic.Item.ContainedItems.Any(it => Matches(it));
}
else if (target is Character character)
{
return character.Inventory != null && character.Inventory.AllItems.Any(it => Matches(it));
}
}
switch (Type)
{
case ConditionType.PropertyValue:
@@ -315,7 +315,11 @@ namespace Barotrauma.IO
return System.IO.File.GetLastWriteTime(path);
}
public static FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access = System.IO.FileAccess.ReadWrite)
public static FileStream Open(
string path,
System.IO.FileMode mode,
System.IO.FileAccess access = System.IO.FileAccess.ReadWrite,
System.IO.FileShare? share = null)
{
switch (mode)
{
@@ -331,10 +335,12 @@ namespace Barotrauma.IO
}
break;
}
return new FileStream(path, System.IO.File.Open(path, mode,
access =
!Validation.CanWrite(path, false) ?
System.IO.FileAccess.Read :
access));
access;
var shareVal = share ?? (access == System.IO.FileAccess.Read ? System.IO.FileShare.Read : System.IO.FileShare.None);
return new FileStream(path, System.IO.File.Open(path, mode, access, shareVal));
}
public static FileStream OpenRead(string path)
+29
View File
@@ -1,3 +1,32 @@
---------------------------------------------------------------------------------------------------------
v0.1500.3.0
---------------------------------------------------------------------------------------------------------
Additions and changes:
- More talents and talent-related items (all talent trees now functional and most of the talents implemented).
Changes:
- Ignore hidden afflictions when determining treatment suggestions to show in the health interface.
- Visualize leaks on the status monitor's hull condition tab (unstable only).
- Added "condition_out" output to outpost O2 generator (unstable only).
Fixes:
- Fixed crashing when reloading sprites or resetting to prefab in the sub editor.
- Fixed ability to combine unidentified genetic materials with other genetic materials (unstable only).
- Organ damage doesn't cause concussions (unstable only).
- Fixed talent menu being accessible if you leave it open and switch to a game mode where it shouldn't be accessible (unstable only).
- Fixed ability to contain items other than batteries in cargo scooter's battery slot (unstable only).
- Damaging the mudraptor beak given by mudraptor genes damages the head instead of torso, added damage protection to the beak (unstable only).
- Items that are set to be hidden in menus aren't shown in the status monitor's item finder (unstable only).
- Fixed status monitor's item finder not showing wearable items (unstable only).
- Fixed "in use by xxxx" warning being always visible when using a Reactor PDA (unstable only).
- Fixed Reactor PDA rendering over the command interface (unstable only).
- Fixed assault rifle crosshair being drawn when it's in the bag slot (unstable only).
- Fixed equip buttons not being drawn on equipped items that can only be put to other equip slots, but not on the non-limb slots (e.g. assault rifle).
Modding:
- Option to make property conditionals target contained items using the attribute targetcontaineditem="true".
---------------------------------------------------------------------------------------------------------
v0.1500.2.0
---------------------------------------------------------------------------------------------------------
@@ -11,7 +11,7 @@
autorestart="false"
LevelDifficulty="20"
AllowedRandomMissionTypes="Random,Salvage,Monster,Cargo,Combat"
AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-40959,13312-19903,131072-15043983,15043985-173791,173824-178207,178208-183983,63744-64255,194560-195103"
AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-21327,21329-40959,13312-19903,131072-173791,173824-178207,178208-183983,63744-64255,194560-195103"
ServerMessage=""
tickrate="20"
randomizeseed="True"