Build 0.20.7.0

This commit is contained in:
Markus Isberg
2022-11-18 18:13:38 +02:00
parent 8c8fd865c5
commit ecb6d40b4b
111 changed files with 1346 additions and 701 deletions
@@ -102,6 +102,8 @@ namespace Barotrauma
private bool inDetectable;
public double InDetectableSetTime;
/// <summary>
/// Should be reset to false each frame and kept indetectable by e.g. a status effect.
/// </summary>
@@ -115,7 +117,8 @@ namespace Barotrauma
{
inDetectable = value;
if (inDetectable)
{
{
InDetectableSetTime = Timing.TotalTime;
NeedsUpdate = true;
}
}
@@ -257,9 +260,14 @@ namespace Barotrauma
SightRange -= speed * deltaTime * (MaxSightRange / FadeOutTime);
}
public bool HasSector()
{
return sectorRad < MathHelper.TwoPi;
}
public bool IsWithinSector(Vector2 worldPosition)
{
if (sectorRad >= MathHelper.TwoPi) { return true; }
if (!HasSector()) { return true; }
Vector2 diff = worldPosition - WorldPosition;
return Math.Abs(MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir))) <= sectorRad * 0.5f;
}
@@ -1083,7 +1083,7 @@ namespace Barotrauma
State = AIState.Idle;
return;
}
else
else if (!owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
{
SelectedAiTarget = owner.AiTarget;
}
@@ -2147,7 +2147,6 @@ namespace Barotrauma
if (SelectedAiTarget?.Entity == null) { return false; }
if (AttackLimb?.attack == null) { return false; }
if (damageTarget == null) { return false; }
ActiveAttack = AttackLimb.attack;
if (wallTarget != null)
{
// If the selected target is not the wall target, make the wall target the selected target.
@@ -2156,9 +2155,10 @@ namespace Barotrauma
{
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority);
State = AIState.Attack;
return true;
}
}
if (damageTarget == null) { return false; }
ActiveAttack = AttackLimb.attack;
if (ActiveAttack.Ranged && ActiveAttack.RequiredAngleToShoot > 0)
{
Limb referenceLimb = GetLimbToRotate(ActiveAttack);
@@ -3810,6 +3810,7 @@ namespace Barotrauma
{
SelectTarget(door.Item.AiTarget, SelectedTargetMemory.Priority);
State = AIState.Attack;
return false;
}
}
}
@@ -3881,9 +3882,8 @@ namespace Barotrauma
return targetLimb;
}
private Character GetOwner(Item item)
private static Character GetOwner(Item item)
{
// If the item is held by a character, attack the character instead.
var pickable = item.GetComponent<Pickable>();
if (pickable != null)
{
@@ -413,28 +413,10 @@ namespace Barotrauma
}
}
}
private void ApplyTreatment(Affliction affliction, Item item)
{
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
bool remove = false;
foreach (ItemComponent ic in item.Components)
{
if (!ic.HasRequiredContainedItems(user: character, addMessage: false)) { continue; }
#if CLIENT
ic.PlaySound(ActionType.OnUse, character);
#endif
ic.WasUsed = true;
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: character);
if (ic.DeleteOnUse)
{
remove = true;
}
}
if (remove)
{
Entity.Spawner?.AddItemToRemoveQueue(item);
}
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
}
protected override bool CheckObjectiveSpecific()
@@ -1371,7 +1371,7 @@ namespace Barotrauma
Limb head = GetLimb(LimbType.Head);
Limb torso = GetLimb(LimbType.Torso);
Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition;
targetMovement = new Vector2(diff.X, 0.0f);
TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left;
@@ -1386,15 +1386,25 @@ namespace Barotrauma
float prevVitality = target.Vitality;
bool wasCritical = prevVitality < 0.0f;
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
{
target.Oxygen += deltaTime * 0.5f; //Stabilize them
}
float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
int skill = (int)character.GetSkillLevel("medical");
if (GameMain.NetworkMember is not { IsClient: true })
{
if (cprBoost >= 1f)
{
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
}
}
//pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17)
if (cprAnimTimer > 15.0f && targetHead != null && head != null)
{
@@ -1405,23 +1415,15 @@ namespace Barotrauma
torso.PullJointEnabled = true;
//Serverside code
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
if (GameMain.NetworkMember is not { IsClient: true })
{
if (target.Oxygen < -10.0f)
{
if (cprBoost >= 1f)
{
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
}
else
{
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
}
}
}
@@ -25,6 +25,8 @@ namespace Barotrauma
FriendlyNPC = 3
}
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
{
public readonly static List<Character> CharacterList = new List<Character>();
@@ -347,7 +349,7 @@ namespace Barotrauma
private readonly Dictionary<ItemPrefab, double> itemSelectedDurations = new Dictionary<ItemPrefab, double>();
private double itemSelectedTime;
public float InvisibleTimer;
public float InvisibleTimer { get; set; }
public readonly CharacterPrefab Prefab;
@@ -1372,7 +1374,28 @@ namespace Barotrauma
tags.RemoveWhere(t => t.StartsWith("variant"));
tags.Add($"variant{headId.Value}".ToIdentifier());
}
var oldHeadInfo = Info.Head;
Info.RecreateHead(tags.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
if (hairIndex == -1)
{
Info.Head.HairIndex = oldHeadInfo.HairIndex;
}
if (beardIndex == -1)
{
Info.Head.BeardIndex = oldHeadInfo.BeardIndex;
}
if (moustacheIndex == -1)
{
Info.Head.MoustacheIndex = oldHeadInfo.MoustacheIndex;
}
if (faceAttachmentIndex == -1)
{
Info.Head.FaceAttachmentIndex = oldHeadInfo.FaceAttachmentIndex;
}
Info.Head.SkinColor = oldHeadInfo.SkinColor;
Info.Head.HairColor = oldHeadInfo.HairColor;
Info.Head.FacialHairColor = oldHeadInfo.FacialHairColor;
Info.CheckColors();
#if CLIENT
head.RecreateSprites();
#endif
@@ -3050,7 +3073,8 @@ namespace Barotrauma
ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
ApplyStatusEffects(ActionType.OnActive, deltaTime);
if (aiTarget != null)
//wait 0.1 seconds so status effects that continuously set InDetectable to true can keep the character InDetectable
if (aiTarget != null && Timing.TotalTime > aiTarget.InDetectableSetTime + 0.1f)
{
aiTarget.InDetectable = false;
}
@@ -5087,25 +5111,48 @@ namespace Barotrauma
return abilityFlags.HasFlag(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
}
private readonly Dictionary<Identifier, float> abilityResistances = new Dictionary<Identifier, float>();
private readonly Dictionary<TalentResistanceIdentifier, float> abilityResistances = new();
public float GetAbilityResistance(AfflictionPrefab affliction)
{
return abilityResistances.TryGetValue(affliction.Identifier, out float value) ? value : abilityResistances.TryGetValue(affliction.AfflictionType, out float typeValue) ? typeValue : 1f;
float resistance = 0f;
bool hadResistance = false;
foreach (var (key, value) in abilityResistances)
{
if (key.ResistanceIdentifier == affliction.AfflictionType ||
key.ResistanceIdentifier == affliction.Identifier)
{
resistance += value;
hadResistance = true;
}
}
return hadResistance ? resistance : 1f;
}
public void ChangeAbilityResistance(Identifier resistanceId, float value)
public void ChangeAbilityResistance(TalentResistanceIdentifier identifier, float value)
{
if (abilityResistances.ContainsKey(resistanceId))
if (!MathUtils.IsValid(value))
{
abilityResistances[resistanceId] *= value;
#if DEBUG
DebugConsole.ThrowError($"Attempted to set ability resistance to an invalid value ({value})\n" + Environment.StackTrace.CleanupStackTrace());
#endif
return;
}
if (abilityResistances.ContainsKey(identifier))
{
abilityResistances[identifier] *= value;
}
else
{
abilityResistances.Add(resistanceId, value);
abilityResistances.Add(identifier, value);
}
}
public void RemoveAbilityResistance(TalentResistanceIdentifier identifier) => abilityResistances.Remove(identifier);
/// <summary>
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
/// </summary>
@@ -63,27 +63,34 @@ namespace Barotrauma
public readonly CharacterInfo CharacterInfo;
public readonly HeadPreset Preset;
private int hairIndex;
public int HairIndex { get; set; }
public int HairIndex
private int? hairWithHatIndex;
public void SetHairWithHatIndex()
{
get => hairIndex;
set
if (CharacterInfo.Hairs is null)
{
hairIndex = value;
if (CharacterInfo.Hairs is null)
if (HairIndex == -1)
{
HairWithHatIndex = value;
return;
#if DEBUG
DebugConsole.ThrowError("Setting \"hairWithHatIndex\" before \"Hairs\" are defined!");
#else
DebugConsole.AddWarning("Setting \"hairWithHatIndex\" before \"Hairs\" are defined!");
#endif
}
HairWithHatIndex = HairElement?.GetAttributeInt("replacewhenwearinghat", hairIndex) ?? -1;
if (HairWithHatIndex < 0 || HairWithHatIndex >= CharacterInfo.Hairs.Count)
hairWithHatIndex = HairIndex;
}
else
{
hairWithHatIndex = HairElement?.GetAttributeInt("replacewhenwearinghat", HairIndex) ?? -1;
if (hairWithHatIndex < 0 || hairWithHatIndex >= CharacterInfo.Hairs.Count)
{
HairWithHatIndex = hairIndex;
hairWithHatIndex = HairIndex;
}
}
}
public int HairWithHatIndex { get; private set; }
public int BeardIndex;
public int MoustacheIndex;
public int FaceAttachmentIndex;
@@ -99,26 +106,29 @@ namespace Barotrauma
get
{
if (CharacterInfo.Hairs == null) { return null; }
if (hairIndex >= CharacterInfo.Hairs.Count)
if (HairIndex >= CharacterInfo.Hairs.Count)
{
DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairIndex})");
DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairIndex})");
}
return CharacterInfo.Hairs.ElementAtOrDefault(hairIndex);
return CharacterInfo.Hairs.ElementAtOrDefault(HairIndex);
}
}
public ContentXElement HairWithHatElement
{
get
{
if (CharacterInfo.Hairs == null) { return null; }
if (HairWithHatIndex >= CharacterInfo.Hairs.Count)
if (hairWithHatIndex == null)
{
DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairWithHatIndex})");
SetHairWithHatIndex();
}
return CharacterInfo.Hairs.ElementAtOrDefault(HairWithHatIndex);
if (CharacterInfo.Hairs == null) { return null; }
if (hairWithHatIndex >= CharacterInfo.Hairs.Count)
{
DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairWithHatIndex})");
}
return CharacterInfo.Hairs.ElementAtOrDefault(hairWithHatIndex.Value);
}
}
public ContentXElement BeardElement
{
get
@@ -711,7 +721,7 @@ namespace Barotrauma
private bool IsColorValid(in Color clr)
=> clr.R != 0 || clr.G != 0 || clr.B != 0;
private void CheckColors()
public void CheckColors()
{
if (!IsColorValid(Head.HairColor))
{
@@ -27,6 +27,14 @@ namespace Barotrauma
get { return _strength; }
set
{
if (!MathUtils.IsValid(value))
{
#if DEBUG
DebugConsole.ThrowError($"Attempted to set an affliction to an invalid strength ({value})\n" + Environment.StackTrace.CleanupStackTrace());
#endif
return;
}
if (_nonClampedStrength < 0 && value > 0)
{
_nonClampedStrength = value;
@@ -440,9 +448,9 @@ namespace Barotrauma
{
statusEffect.Apply(type, deltaTime, characterHealth.Character, targetLimb);
}
if (targetLimb != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
if (characterHealth?.Character?.AnimController?.Limbs != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
statusEffect.Apply(type, deltaTime, targetLimb.character, targets: targetLimb.character.AnimController.Limbs);
statusEffect.Apply(type, deltaTime, characterHealth.Character, targets: characterHealth.Character.AnimController.Limbs);
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
@@ -405,45 +405,53 @@ namespace Barotrauma
var root = doc.Root.FromPackage(pathToAppendage.ContentPackage);
var limbElements = root.GetChildElements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);
//the IDs may need to be offset if the character has other extra appendages (e.g. from gene splicing)
//that take up the IDs of this appendage
int idOffset = 0;
foreach (var jointElement in root.GetChildElements("joint"))
{
if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement))
if (!limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement)) { continue; }
var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
Limb attachLimb = null;
if (matchingAffliction.AttachLimbId > -1)
{
var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
Limb attachLimb = null;
if (matchingAffliction.AttachLimbId > -1)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
}
else if (matchingAffliction.AttachLimbName != null)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
}
else if (matchingAffliction.AttachLimbType != LimbType.None)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
}
if (attachLimb == null)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
}
if (attachLimb != null)
{
jointParams.Limb1 = attachLimb.Params.ID;
var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams)
{
// Ensure that we have a valid id for the new limb
ID = ragdoll.Limbs.Length
};
jointParams.Limb2 = appendageLimbParams.ID;
Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
huskAppendage.body.Submarine = character.Submarine;
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
ragdoll.AddLimb(huskAppendage);
ragdoll.AddJoint(jointParams);
appendage.Add(huskAppendage);
}
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
}
else if (matchingAffliction.AttachLimbName != null)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
}
else if (matchingAffliction.AttachLimbType != LimbType.None)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
}
if (attachLimb == null)
{
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
}
if (attachLimb != null)
{
jointParams.Limb1 = attachLimb.Params.ID;
//the joint attaches to a limb outside the character's normal limb count = to another part of the appendage
// -> if the appendage's IDs have been offset, we need to take that into account to attach to the correct limb
if (jointParams.Limb1 >= ragdoll.RagdollParams.Limbs.Count)
{
jointParams.Limb1 += idOffset;
}
var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams);
if (idOffset == 0)
{
idOffset = ragdoll.Limbs.Length - appendageLimbParams.ID;
}
jointParams.Limb2 = appendageLimbParams.ID = ragdoll.Limbs.Length;
Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
huskAppendage.body.Submarine = character.Submarine;
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
ragdoll.AddLimb(huskAppendage);
ragdoll.AddJoint(jointParams);
appendage.Add(huskAppendage);
}
}
return appendage;
}
@@ -887,7 +887,7 @@ namespace Barotrauma
//clamp above 0.1 (no amount of oxygen low resistance should keep the character alive indefinitely)
float decreaseSpeed = Math.Max(0.1f, 1f - oxygenlowResistance);
//the character dies of oxygen deprivation in 100 seconds after losing consciousness
OxygenAmount = MathHelper.Clamp(OxygenAmount - decreaseSpeed * deltaTime, -100.0f, 100.0f);
OxygenAmount = MathHelper.Clamp(OxygenAmount - decreaseSpeed * deltaTime, -100.0f, 100.0f);
}
else
{
@@ -895,15 +895,21 @@ namespace Barotrauma
float increaseSpeed = 10.0f;
decreaseSpeed *= (1f - oxygenlowResistance);
increaseSpeed *= (1f + oxygenlowResistance);
float holdBreathMultiplier = 1f + GetStatValue(StatTypes.HoldBreathMultiplier);
decreaseSpeed *= holdBreathMultiplier;
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f);
float holdBreathMultiplier = Character.GetStatValue(StatTypes.HoldBreathMultiplier);
if (holdBreathMultiplier <= -1.0f)
{
OxygenAmount = -100.0f;
}
else
{
decreaseSpeed /= 1.0f + Character.GetStatValue(StatTypes.HoldBreathMultiplier);
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f);
}
}
UpdateOxygenProjSpecific(prevOxygen, deltaTime);
}
partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime);
partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime);
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,5 +1,5 @@
using System.Collections.Immutable;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System.Collections.Immutable;
namespace Barotrauma.Abilities
{
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGainSimultaneousSkill : CharacterAbility
{
@@ -15,6 +12,10 @@ namespace Barotrauma.Abilities
skillIdentifier = abilityElement.GetAttributeIdentifier("skillidentifier", "");
ignoreAbilitySkillGain = abilityElement.GetAttributeBool("ignoreabilityskillgain", true);
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
if (skillIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined.");
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
@@ -23,13 +24,12 @@ namespace Barotrauma.Abilities
{
if (ignoreAbilitySkillGain && abilitySkillGain.GainedFromAbility) { return; }
Identifier identifier = skillIdentifier == "inherit" ? abilitySkillGain.SkillIdentifier : skillIdentifier;
if (targetAllies)
{
foreach (Character character in Character.GetFriendlyCrew(Character))
foreach (Character otherCharacter in Character.GetFriendlyCrew(Character))
{
if (character == Character) { continue; }
Character.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true);
if (otherCharacter == Character) { continue; }
otherCharacter.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true);
}
}
else
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveAffliction : CharacterAbility
{
@@ -18,7 +16,7 @@ namespace Barotrauma.Abilities
if (afflictionId.IsEmpty)
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveAffliction - affliction identifier not set.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set.");
}
}
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveFlag : CharacterAbility
{
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveMoney : CharacterAbility
{
@@ -13,6 +11,11 @@ namespace Barotrauma.Abilities
{
amount = abilityElement.GetAttributeInt("amount", 0);
scalingStatIdentifier = abilityElement.GetAttributeIdentifier("scalingstatidentifier", Identifier.Empty);
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0.");
}
}
private void ApplyEffectSpecific(Character targetCharacter)
@@ -1,6 +1,4 @@
using System;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
public enum PermanentStatPlaceholder
{
@@ -28,6 +26,10 @@ namespace Barotrauma.Abilities
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
if (statIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined.");
}
string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty);
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
@@ -11,6 +11,14 @@ namespace Barotrauma.Abilities
{
factionIdentifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
amount = abilityElement.GetAttributeFloat("amount", 0f);
if (factionIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined.");
}
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0.");
}
}
protected override void ApplyEffect()
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveResistance : CharacterAbility
{
@@ -10,17 +8,23 @@ namespace Barotrauma.Abilities
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", abilityElement.GetAttributeIdentifier("resistance", Identifier.Empty));
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); // rename this to resistance for consistency
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (resistanceId.IsEmpty)
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
}
if (MathUtils.NearlyEqual(multiplier, 1))
{
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing.");
}
}
public override void InitializeAbility(bool addingFirstTime)
{
Character.ChangeAbilityResistance(resistanceId, multiplier);
TalentResistanceIdentifier identifier = new(resistanceId, CharacterTalent.Prefab.Identifier);
Character.ChangeAbilityResistance(identifier, multiplier);
}
}
}
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveStat : CharacterAbility
{
@@ -1,7 +1,4 @@
using Barotrauma.Extensions;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityGiveTalentPoints : CharacterAbility
{
@@ -9,7 +6,11 @@ namespace Barotrauma.Abilities
public CharacterAbilityGiveTalentPoints(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
amount = abilityElement.GetAttributeInt("amount", 0);
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.");
}
}
public override void InitializeAbility(bool addingFirstTime)
@@ -9,6 +9,10 @@ namespace Barotrauma.Abilities
public CharacterAbilityGiveTalentPointsToAllies(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
if (amount == 0)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.");
}
}
public override void InitializeAbility(bool addingFirstTime)
@@ -1,6 +1,4 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -6,6 +6,10 @@ namespace Barotrauma.Abilities
public CharacterAbilityMarkAsLooted(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
if (identifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}.");
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
@@ -1,7 +1,4 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyFlag : CharacterAbility
{
@@ -1,23 +1,25 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyResistance : CharacterAbility
{
private readonly Identifier resistanceId;
private readonly float resistance;
private readonly float multiplier;
bool lastState;
public override bool AllowClientSimulation => true;
// should probably be split to different classes
public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", abilityElement.GetAttributeIdentifier("resistance", Identifier.Empty));
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (resistanceId.IsEmpty)
{
DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set.");
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}.");
}
if (MathUtils.NearlyEqual(multiplier, 1.0f))
{
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing.");
}
}
@@ -25,7 +27,15 @@ namespace Barotrauma.Abilities
{
if (conditionsMatched != lastState)
{
Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance);
TalentResistanceIdentifier identifier = new(resistanceId, CharacterTalent.Prefab.Identifier);
if (conditionsMatched)
{
Character.ChangeAbilityResistance(identifier, multiplier);
}
else
{
Character.RemoveAbilityResistance(identifier);
}
lastState = conditionsMatched;
}
}
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStat : CharacterAbility
{
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,5 +1,4 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyValue : CharacterAbility
{
@@ -11,6 +9,10 @@ namespace Barotrauma.Abilities
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
if (MathUtils.NearlyEqual(addedValue, 0.0f) && MathUtils.NearlyEqual(multiplyValue, 1.0f))
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing.");
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityPutItem : CharacterAbility
{
@@ -9,7 +9,11 @@ namespace Barotrauma.Abilities
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
if (statIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty.");
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityRevive : CharacterAbility
{
@@ -11,6 +11,10 @@ namespace Barotrauma.Abilities
{
identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
value = abilityElement.GetAttributeInt("value", 0);
if (identifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty.");
}
}
public override void InitializeAbility(bool addingFirstTime)
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityApprenticeship : CharacterAbility
{
@@ -1,20 +1,17 @@
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityAtmosMachine : CharacterAbility
{
private readonly float addedValue;
private readonly float multiplyValue;
private readonly Identifier[] tags;
private readonly int maxMultiplyCount;
public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
tags = abilityElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue);
}
@@ -1,11 +1,8 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityBountyHunter : CharacterAbility
{
private float vitalityPercentage;
private readonly float vitalityPercentage;
public CharacterAbilityBountyHunter(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityMultitasker : CharacterAbility
{
@@ -1,12 +1,10 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityPsychoClown : CharacterAbility
{
private StatTypes statType;
private float maxValue;
private string afflictionIdentifier;
private readonly StatTypes statType;
private readonly float maxValue;
private readonly string afflictionIdentifier;
private float lastValue = 0f;
public override bool AllowClientSimulation => true;
@@ -1,8 +1,5 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -1,8 +1,8 @@
#nullable enable
using Barotrauma.Extensions;
using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.Extensions;
namespace Barotrauma.Abilities
{
@@ -66,8 +66,8 @@ namespace Barotrauma
IEnumerable<TalentSubTree> blockingSubTrees = tree.TalentSubTrees.Where(tst => tst.BlockedTrees.Contains(targetTree.Identifier)),
requiredSubTrees = tree.TalentSubTrees.Where(tst => targetTree.RequiredTrees.Contains(tst.Identifier));
return requiredSubTrees.All(tst => tst.IsCompleted(selectedTalents)) && // check if we meet requirements
!blockingSubTrees.Any(tst => tst.HasAnyTalent(selectedTalents)); // check if any other talent trees are blocking this one
return requiredSubTrees.All(tst => tst.HasEnoughTalents(selectedTalents)) && // check if we meet requirements
!blockingSubTrees.Any(tst => tst.HasAnyTalent(selectedTalents) && !tst.HasMaxTalents(selectedTalents)); // check if any other talent trees are blocking this one
}
// i hate this function - markus
@@ -128,16 +128,26 @@ namespace Barotrauma
public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier, IReadOnlyCollection<Identifier> selectedTalents)
{
if (character?.Info?.Job.Prefab == null) { return false; }
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; }
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
foreach (var subTree in talentTree!.TalentSubTrees)
{
if (subTree.AllTalentIdentifiers.Contains(talentIdentifier) && subTree.HasMaxTalents(selectedTalents)) { return false; }
foreach (var talentOptionStage in subTree.TalentOptionStages)
{
bool hasTalentInThisTier = talentOptionStage.HasEnoughTalents(selectedTalents);
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
{
return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
}
bool optionStageCompleted = talentOptionStage.HasEnoughTalents(selectedTalents);
if (!optionStageCompleted)
{
break;
}
/*bool hasTalentInThisTier = talentOptionStage.HasMaxTalents(selectedTalents);
if (!hasTalentInThisTier)
{
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
@@ -145,7 +155,7 @@ namespace Barotrauma
return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
}
break;
}
}*/
}
}
@@ -196,7 +206,8 @@ namespace Barotrauma
public readonly ImmutableHashSet<Identifier> RequiredTrees;
public readonly ImmutableHashSet<Identifier> BlockedTrees;
public bool IsCompleted(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.All(option => option.HasEnoughTalents(talents));
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.All(option => option.HasEnoughTalents(talents));
public bool HasMaxTalents(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.All(option => option.HasMaxTalents(talents));
public bool HasAnyTalent(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.Any(option => option.HasSelectedTalent(talents));
public TalentSubTree(ContentXElement subTreeElement)
@@ -228,6 +239,13 @@ namespace Barotrauma
public IEnumerable<Identifier> TalentIdentifiers => talentIdentifiers;
/// <summary>
/// How many talents need to be unlocked to consider this tree completed
/// </summary>
public readonly int RequiredTalents;
/// <summary>
/// How many talents can be unlocked in total
/// </summary>
public readonly int MaxChosenTalents;
/// <summary>
@@ -236,8 +254,9 @@ namespace Barotrauma
/// </summary>
public readonly Dictionary<Identifier, ImmutableHashSet<Identifier>> ShowCaseTalents = new Dictionary<Identifier, ImmutableHashSet<Identifier>>();
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents;
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= RequiredTalents;
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= RequiredTalents;
public bool HasMaxTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
// No LINQ
public bool HasSelectedTalent(IReadOnlyCollection<Identifier> selectedTalents)
@@ -267,10 +286,15 @@ namespace Barotrauma
public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier)
{
MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1);
MaxChosenTalents = talentOptionsElement.GetAttributeInt(nameof(MaxChosenTalents), 1);
RequiredTalents = talentOptionsElement.GetAttributeInt(nameof(RequiredTalents), MaxChosenTalents);
if (RequiredTalents > MaxChosenTalents)
{
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents.");
}
HashSet<Identifier> identifiers = new HashSet<Identifier>();
foreach (ContentXElement talentOptionElement in talentOptionsElement.Elements())
{
Identifier elementName = talentOptionElement.Name.ToIdentifier();
@@ -293,6 +317,15 @@ namespace Barotrauma
}
talentIdentifiers = identifiers.ToImmutableHashSet();
if (RequiredTalents > talentIdentifiers.Count)
{
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage.");
}
if (MaxChosenTalents > talentIdentifiers.Count)
{
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents.");
}
}
}
}
@@ -22,7 +22,7 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes)]
public Identifier OrderTargetTag { get; set; }
[Serialize(OrderPriority.Top, IsPropertySaveable.Yes)]
[Serialize(OrderPriority.Any, IsPropertySaveable.Yes)]
public OrderPriority Priority { get; set; }
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -43,6 +43,9 @@ partial class UIHighlightAction : EventAction
[Serialize(true, IsPropertySaveable.Yes)]
public bool Bounce { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool HighlightMultiple { get; set; }
private bool isFinished;
public UIHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -207,25 +207,23 @@ namespace Barotrauma
level.StartLocation.Connections.ForEach(c => c.Locked = false);
}
}
AddChildEvents(initialEventSet);
void AddChildEvents(EventSet eventSet)
}
RegisterNonRepeatableChildEvents(initialEventSet);
void RegisterNonRepeatableChildEvents(EventSet eventSet)
{
if (eventSet == null) { return; }
if (eventSet.OncePerLevel)
{
if (eventSet == null) { return; }
if (eventSet.OncePerOutpost)
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
{
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
{
nonRepeatableEvents.Add(ep);
}
}
foreach (EventSet childSet in eventSet.ChildSets)
{
AddChildEvents(childSet);
nonRepeatableEvents.Add(ep);
}
}
}
foreach (EventSet childSet in eventSet.ChildSets)
{
RegisterNonRepeatableChildEvents(childSet);
}
}
}
PreloadContent(GetFilesToPreload());
@@ -374,26 +372,18 @@ namespace Barotrauma
/// </summary>
public void RegisterEventHistory()
{
if (level?.LevelData != null)
if (level?.LevelData == null) { return; }
level.LevelData.EventsExhausted = true;
if (level.LevelData.Type == LevelData.LevelType.Outpost)
{
level.LevelData.EventsExhausted = true;
if (level.LevelData.Type == LevelData.LevelType.Outpost)
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
if (level.LevelData.EventHistory.Count > MaxEventHistory)
{
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
if (level.LevelData.EventHistory.Count > MaxEventHistory)
{
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
}
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
}
foreach (var usedUniqueSet in usedUniqueSets)
{
if (!level.LevelData.UsedUniqueSets.Contains(usedUniqueSet.Identifier))
{
level.LevelData.UsedUniqueSets.Add(usedUniqueSet.Identifier);
}
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
}
}
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
}
public void SkipEventCooldown()
@@ -418,16 +408,11 @@ namespace Barotrauma
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
if (eventSet.Unique && !usedUniqueSets.Contains(eventSet))
{
usedUniqueSets.Add(eventSet);
}
int applyCount = 1;
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
if (eventSet.PerRuin)
{
applyCount = level.Ruins.Count();
applyCount = level.Ruins.Count;
foreach (var ruin in level.Ruins)
{
spawnPosFilter.Add(pos => pos.Ruin == ruin);
@@ -435,7 +420,7 @@ namespace Barotrauma
}
else if (eventSet.PerCave)
{
applyCount = level.Caves.Count();
applyCount = level.Caves.Count;
foreach (var cave in level.Caves)
{
spawnPosFilter.Add(pos => pos.Cave == cave);
@@ -452,8 +437,8 @@ namespace Barotrauma
}
bool isPrefabSuitable(EventPrefab e)
=> e.BiomeIdentifier.IsEmpty ||
e.BiomeIdentifier == level.LevelData?.Biome?.Identifier;
=> (e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
!level.LevelData.NonRepeatableEvents.Contains(e);
foreach (var subEventPrefab in eventSet.EventPrefabs)
{
@@ -610,8 +595,7 @@ namespace Barotrauma
return
level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty &&
level.LevelData.Type == eventSet.LevelType &&
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier) &&
(!eventSet.Unique || !level.LevelData.UsedUniqueSets.Contains(eventSet.Identifier));
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
}
private bool IsValidForLocation(EventSet eventSet, Location location)
@@ -114,15 +114,9 @@ namespace Barotrauma
public readonly bool DisableInHuntingGrounds;
/// <summary>
/// If true, events from this set shouldn't be selected again as long as they remain in <see cref="LevelData.NonRepeatableEvents"/> which has a limited size.
/// Use <see cref="Unique"/> to prevent selecting the whole set again altogether.
/// If true, events from this set can only occur once in the level.
/// </summary>
public readonly bool OncePerOutpost;
/// <summary>
/// If true, the whole set can only be selected once for a level.
/// </summary>
public readonly bool Unique;
public readonly bool OncePerLevel;
public readonly bool DelayWhenCrewAway;
@@ -289,8 +283,7 @@ namespace Barotrauma
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
Unique = element.GetAttributeBool("unique", false);
OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", false));
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
ResetTime = element.GetAttributeFloat("resettime", 0);
@@ -386,23 +386,27 @@ namespace Barotrauma
#if CLIENT
foreach (Character character in crewCharacters)
{
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
GiveMissionExperience(character.Info);
}
#else
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
{
//give the experience to the stored characterinfo if the client isn't currently controlling a character
CharacterInfo info = c.Character?.Info ?? c.CharacterInfo;
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
GiveMissionExperience(c.Character?.Info ?? c.CharacterInfo);
}
foreach (Character bot in GameSession.GetSessionCrewCharacters(CharacterType.Bot))
{
GiveMissionExperience(bot.Info);
}
#endif
void GiveMissionExperience(CharacterInfo info)
{
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
}
// apply money gains afterwards to prevent them from affecting XP gains
var missionMoneyGainMultiplier = new AbilityMissionMoneyGainMultiplier(this, 1f);
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
@@ -306,14 +306,6 @@ namespace Barotrauma
return validContainers;
}
private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
{
(0, 1.0f),
(1, 0.0f),
(2, 0.0f),
(3, 0.0f),
};
private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
{
List<Item> newItems = new List<Item>();
@@ -336,11 +328,7 @@ namespace Barotrauma
break;
}
var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab);
int quality =
existingItem?.Quality ??
ToolBox.SelectWeightedRandom(
qualityCommonnesses.Select(q => q.quality).ToList(),
qualityCommonnesses.Select(q => q.commonness).ToList(), Rand.RandSync.ServerAndClient);
int quality = existingItem?.Quality ?? Quality.GetSpawnedItemQuality(validContainer.Key.Item.Submarine, Level.Loaded, Rand.RandSync.ServerAndClient);
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false)
{
@@ -276,7 +276,7 @@ namespace Barotrauma.Items.Components
}
}
//get all walls within range
//get all walls within range the arc could potentially hit
List<Entity> entitiesInRange = new List<Entity>(100);
foreach (Structure structure in Structure.WallList)
{
@@ -284,10 +284,10 @@ namespace Barotrauma.Items.Components
if (structure.Submarine != null&& !submarinesInRange.Contains(structure.Submarine)) { continue; }
var structureWorldRect = structure.WorldRect;
if (worldPosition.X < structureWorldRect.X - range) continue;
if (worldPosition.X > structureWorldRect.Right + range) continue;
if (worldPosition.Y > structureWorldRect.Y + range) continue;
if (worldPosition.Y < structureWorldRect.Y -structureWorldRect.Height - range) continue;
if (worldPosition.X < structureWorldRect.X - range) { continue; }
if (worldPosition.X > structureWorldRect.Right + range) { continue; }
if (worldPosition.Y > structureWorldRect.Y + range) { continue; }
if (worldPosition.Y < structureWorldRect.Y - structureWorldRect.Height - range) { continue; }
if (structure.Submarine != null)
{
@@ -317,6 +317,7 @@ namespace Barotrauma.Items.Components
nodes.Add(new Node(worldPosition, -1));
}
//get all characters within range the arc could potentially hit
float totalRange = RaycastRange + range;
foreach (Character character in Character.CharacterList)
{
@@ -325,11 +326,20 @@ namespace Barotrauma.Items.Components
if (OutdoorsOnly && character.Submarine != null) { continue; }
if (character.Submarine != null && !submarinesInRange.Contains(character.Submarine)) { continue; }
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls ||
(RaycastRange > 0.0f && MathUtils.LineToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition) < range * range * RangeMultiplierInWalls))
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls)
{
entitiesInRange.Add(character);
charactersInRange.Add((character, nodes[0]));
}
//if the weapon does a raycast, check distance to the ray too (not just the end of the ray)
if (RaycastRange > 0)
{
float distSqr = MathUtils.LineSegmentToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition);
//if the distance from the initial raycast to the character is small (e.g. goes through the character), we know it must hit
if (distSqr < range * range * RangeMultiplierInWalls)
{
if (!entitiesInRange.Contains(character)) { entitiesInRange.Add(character); }
charactersInRange.Add((character, nodes.First()));
}
}
}
@@ -378,7 +388,7 @@ namespace Barotrauma.Items.Components
}
else if (entitiesInRange[i] is Character character)
{
dist = Vector2.Distance(character.WorldPosition, currPos);
dist = MathUtils.LineSegmentToPointDistanceSquared(currPos, nodes[parentNodeIndex].WorldPosition, character.WorldPosition);
}
if (dist < closestDist)
@@ -494,17 +504,31 @@ namespace Barotrauma.Items.Components
if (IgnoreUser && character == user) { continue; }
if (OutdoorsOnly && character.Submarine != null) { continue; }
Vector2 characterMin = new Vector2(character.AnimController.Limbs.Min(l => l.WorldPosition.X), character.AnimController.Limbs.Min(l => l.WorldPosition.Y));
Vector2 characterMax = new Vector2(character.AnimController.Limbs.Max(l => l.WorldPosition.X), character.AnimController.Limbs.Max(l => l.WorldPosition.Y));
if (targetStructure.IsHorizontal)
{
if (otherEntity.WorldPosition.X < targetStructure.WorldRect.X) { continue; }
if (otherEntity.WorldPosition.X > targetStructure.WorldRect.Right) { continue; }
if (Math.Abs(otherEntity.WorldPosition.Y - targetStructure.WorldPosition.Y) > currentRange) { continue; }
if (characterMax.X < targetStructure.WorldRect.X) { continue; }
if (characterMin.X > targetStructure.WorldRect.Right) { continue; }
if (Math.Abs(characterMin.Y - targetStructure.WorldPosition.Y) > currentRange &&
Math.Abs(characterMax.Y - targetStructure.WorldPosition.Y) > currentRange)
{
continue;
}
}
else
{
if (otherEntity.WorldPosition.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; }
if (otherEntity.WorldPosition.Y > targetStructure.WorldRect.Y) { continue; }
if (Math.Abs(otherEntity.WorldPosition.X - targetStructure.WorldPosition.X) > currentRange) { continue; }
if (characterMax.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; }
if (characterMin.Y > targetStructure.WorldRect.Y) { continue; }
if (Math.Abs(characterMin.X - targetStructure.WorldPosition.X) > currentRange &&
Math.Abs(characterMax.X - targetStructure.WorldPosition.X) > currentRange)
{
continue;
}
}
if (!charactersInRange.Any(c => c.character == character))
{
charactersInRange.Add((character, nodes[parentNodeIndex]));
}
float closestNodeDistSqr = float.MaxValue;
int closestNodeIndex = -1;
@@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components
{
public enum UseEnvironment
{
Air, Water, Both
Air, Water, Both, None
};
private float useState;
@@ -44,6 +44,7 @@ namespace Barotrauma.Items.Components
{
if (character == null || character.Removed) { return false; }
if (!character.IsKeyDown(InputType.Aim) || character.Stun > 0.0f) { return false; }
if (UsableIn == UseEnvironment.None) { return false; }
IsActive = true;
useState = 0.1f;
@@ -476,6 +476,7 @@ namespace Barotrauma.Items.Components
{
foreach (Item item in Inventory.AllItemsMod)
{
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter);
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter);
item.GetComponent<GeneticMaterial>()?.Equip(ownerCharacter);
autoInjectCooldown = AutoInjectInterval;
@@ -450,6 +450,7 @@ namespace Barotrauma.Items.Components
public override bool Select(Character activator)
{
if (activator == null || activator.Removed) { return false; }
if (Item.Condition <= 0.0f && !UpdateWhenInactive) { return false; }
if (UsableIn == UseEnvironment.Water && !activator.AnimController.InWater ||
UsableIn == UseEnvironment.Air && activator.AnimController.InWater)
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
{
private ImmutableDictionary<uint, FabricationRecipe> fabricationRecipes; //this is not readonly because tutorials fuck this up!!!!
private const int MaxAmountToFabricate = 99;
private FabricationRecipe fabricatedItem;
private float timeUntilReady;
private float requiredTime;
@@ -39,6 +41,16 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, IsPropertySaveable.Yes)]
public float SkillRequirementMultiplier { get; set; }
private int amountToFabricate;
[Serialize(1, IsPropertySaveable.Yes)]
public int AmountToFabricate
{
get { return amountToFabricate; }
set { amountToFabricate = MathHelper.Clamp(value, 1, MaxAmountToFabricate); }
}
private int amountRemaining;
private const float TinkeringSpeedIncrease = 2.5f;
private enum FabricatorState
@@ -183,16 +195,20 @@ namespace Barotrauma.Items.Components
if (selectedItem == null) { return; }
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
#if CLIENT
itemList.Enabled = false;
activateButton.Text = TextManager.Get("FabricatorCancel");
#endif
IsActive = true;
this.user = user;
fabricatedItem = selectedItem;
RefreshAvailableIngredients();
#if CLIENT
itemList.Enabled = false;
if (amountInput != null)
{
amountInput.Enabled = false;
}
RefreshActivateButtonText();
#endif
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
if (!isClient)
{
@@ -249,10 +265,11 @@ namespace Barotrauma.Items.Components
}
#elif CLIENT
itemList.Enabled = true;
if (activateButton != null)
if (amountInput != null)
{
activateButton.Text = TextManager.Get(CreateButtonText);
amountInput.Enabled = true;
}
RefreshActivateButtonText();
#endif
fabricatedItem = null;
}
@@ -518,20 +535,16 @@ namespace Barotrauma.Items.Components
}
}
//disabled "continuous fabrication" for now
//before we enable it, there should be some UI controls for fabricating a specific number of items
/*var prevFabricatedItem = fabricatedItem;
var prevFabricatedItem = fabricatedItem;
var prevUser = user;
CancelFabricating();
if (CanBeFabricated(prevFabricatedItem))
amountRemaining--;
if (amountRemaining > 0 && CanBeFabricated(prevFabricatedItem, availableIngredients, prevUser))
{
//keep fabricating if we can fabricate more
StartFabricating(prevFabricatedItem, prevUser, addToServerLog: false);
}*/
CancelFabricating();
}
}
}
@@ -594,7 +607,15 @@ namespace Barotrauma.Items.Components
private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary<Identifier, List<Item>> availableIngredients, Character character)
{
if (fabricableItem == null) { return false; }
if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; }
if (fabricableItem.RequiresRecipe)
{
if (character == null) { return false; }
if (!character.HasRecipeForItem(fabricableItem.TargetItem.Identifier) &&
GameSession.GetSessionCrewCharacters(CharacterType.Bot).None(c => c.HasRecipeForItem(fabricableItem.TargetItem.Identifier)))
{
return false;
}
}
if (fabricableItem.RequiredMoney > 0)
{
@@ -151,7 +151,6 @@ namespace Barotrauma.Items.Components
set
{
bool changed = currentMode != value;
currentMode = value;
#if CLIENT
if (changed) { prevPassivePingRadius = float.MaxValue; }
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -9,14 +10,6 @@ namespace Barotrauma.Items.Components
{
public const int MaxQuality = 3;
public static readonly float[] QualityCommonnesses = new float[]
{
0.8f,
0.15f,
0.045f,
0.005f,
};
public enum StatType
{
Condition,
@@ -81,5 +74,29 @@ namespace Barotrauma.Items.Components
if (!statValues.ContainsKey(statType)) { return 0.0f; }
return statValues[statType] * qualityLevel;
}
/// <summary>
/// Get a random quality for an item spawning in some sub, taking into account the type of the submarine and the difficulty of the current level
/// (high-quality items become more common as difficulty increases)
/// </summary>
public static int GetSpawnedItemQuality(Submarine submarine, Level level, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
{
if (submarine?.Info == null || level == null || submarine.Info.Type == SubmarineType.Player) { return 0; }
float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, 1.0f);
return ToolBox.SelectWeightedRandom(Enumerable.Range(0, MaxQuality + 1), q => GetCommonness(q, difficultyFactor), randSync);
static float GetCommonness(int quality, float difficultyFactor)
{
return quality switch
{
0 => 1,
1 => MathHelper.Lerp(0.0f, 1f, difficultyFactor),
2 => MathHelper.Lerp(0.0f, 1f, Math.Max(difficultyFactor-0.15f, 0f)), //15 difficulty transition to next biome - unlock Excellent loot
3 => MathHelper.Lerp(0.0f, 1f, Math.Max(difficultyFactor-0.35f, 0f)), //35 difficulty transition to next biome - unlock Masterwork loot
_ => 0.0f,
};
}
}
}
}
@@ -1617,11 +1617,7 @@ namespace Barotrauma
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null)
{
if (effect.intervalTimer > 0.0f)
{
effect.intervalTimer -= deltaTime;
return;
}
if (effect.ShouldWaitForInterval(this, deltaTime)) { return; }
if (!isNetworkEvent && checkCondition)
{
if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; }
@@ -2758,7 +2754,6 @@ namespace Barotrauma
return;
}
#endif
bool remove = false;
foreach (ItemComponent ic in components)
{
@@ -2788,7 +2783,6 @@ namespace Barotrauma
{
var abilityItem = new AbilityApplyTreatment(user, character, this);
user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem);
}
if (remove) { Spawner?.AddItemToRemoveQueue(this); }
@@ -15,10 +15,7 @@ namespace Barotrauma
}
[NetworkSerialize]
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct
{
public override int GetHashCode() => HashCode.Combine(TalentIdentifier, CharacterID, Stat);
}
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct;
private readonly Dictionary<TalentStatIdentifier, float> talentStats = new();
@@ -3943,31 +3943,11 @@ namespace Barotrauma
Submarine outpost = null;
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
{
if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null)
if (LevelData.OutpostGenerationParamsExist)
{
Location location = i == 0 ? StartLocation : EndLocation;
OutpostGenerationParams outpostGenerationParams = null;
if (LevelData.ForceOutpostGenerationParams != null)
{
outpostGenerationParams = LevelData.ForceOutpostGenerationParams;
}
else
{
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
if (!suitableParams.Any())
{
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
if (!suitableParams.Any())
{
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
suitableParams = OutpostGenerationParams.OutpostParams;
}
}
outpostGenerationParams = suitableParams.GetRandom(Rand.RandSync.ServerAndClient);
}
OutpostGenerationParams outpostGenerationParams = LevelData.ForceOutpostGenerationParams ??
LevelData.GetSuitableOutpostGenerationParams(location).GetRandom(Rand.RandSync.ServerAndClient);
LocationType locationType = location?.Type;
if (locationType == null)
{
@@ -57,10 +57,19 @@ namespace Barotrauma
/// </summary>
public int? MinMainPathWidth;
/// <summary>
/// Events that have previously triggered in this level. Used for making events the player hasn't seen yet more likely to trigger when re-entering the level. Has a maximum size of <see cref="EventManager.MaxEventHistory"/>.
/// </summary>
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
public readonly HashSet<Identifier> UsedUniqueSets = new HashSet<Identifier>();
/// <summary>
/// Events that have already triggered in this level and can never trigger again. <see cref="EventSet.OncePerLevel"/>.
/// </summary>
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
/// <summary>
/// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed. <see cref="EventSet.Exhaustible"/>.
/// </summary>
public bool EventsExhausted { get; set; }
/// <summary>
@@ -144,8 +153,6 @@ namespace Barotrauma
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
UsedUniqueSets = element.GetAttributeIdentifierArray(nameof(UsedUniqueSets), Array.Empty<Identifier>()).ToHashSet();
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
}
@@ -245,6 +252,23 @@ namespace Barotrauma
return levelData;
}
public bool OutpostGenerationParamsExist => ForceOutpostGenerationParams != null || OutpostGenerationParams.OutpostParams.Any();
public static IEnumerable<OutpostGenerationParams> GetSuitableOutpostGenerationParams(Location location)
{
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
if (!suitableParams.Any())
{
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
if (!suitableParams.Any())
{
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
suitableParams = OutpostGenerationParams.OutpostParams;
}
}
return suitableParams;
}
public void Save(XElement parentElement)
{
var newElement = new XElement("Level",
@@ -287,11 +311,6 @@ namespace Barotrauma
}
}
if (UsedUniqueSets.Any())
{
newElement.Add(new XAttribute(nameof(UsedUniqueSets), string.Join(',', UsedUniqueSets)));
}
parentElement.Add(newElement);
}
}
@@ -1291,19 +1291,32 @@ namespace Barotrauma
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
}
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass)
public bool CanHaveSubsForSale()
{
if (!HasOutpost()) { return 0; }
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
return HasOutpost() && CanHaveCampaignInteraction(CampaignMode.InteractionType.PurchaseSub);
}
public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined);
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass = SubmarineClass.Undefined)
{
if (CanHaveSubsForSale())
{
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
}
return 0;
}
public bool IsSubmarineAvailable(SubmarineInfo info)
{
return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
}
private bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
{
return LevelData != null &&
LevelData.OutpostGenerationParamsExist &&
LevelData.GetSuitableOutpostGenerationParams(this).Any(p => p.CanHaveCampaignInteraction(interactionType));
}
public void Reset()
{
if (Type != OriginalType)
@@ -85,9 +85,9 @@ namespace Barotrauma
}
public float StoreMaxReputationModifier { get; } = 0.1f;
public float StoreSellPriceModifier { get; } = 0.8f;
public float StoreSellPriceModifier { get; } = 0.3f;
public float DailySpecialPriceModifier { get; } = 0.5f;
public float RequestGoodPriceModifier { get; } = 1.5f;
public float RequestGoodPriceModifier { get; } = 2f;
public int StoreInitialBalance { get; } = 5000;
/// <summary>
/// In percentages
@@ -260,6 +260,21 @@ namespace Barotrauma
return humanPrefabCollections.GetRandom(randSync);
}
public bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
{
foreach (var collection in humanPrefabCollections)
{
foreach (var prefab in collection)
{
if (prefab.CampaignInteractionType == interactionType)
{
return true;
}
}
}
return false;
}
public ImmutableHashSet<Identifier> GetStoreIdentifiers()
{
if (StoreIdentifiers == null)
@@ -164,7 +164,11 @@ namespace Barotrauma.Networking
return command;
}
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionmult = 2.0f)
/// <summary>
/// How much messages sent by <paramref name="sender"/> should get garbled. Takes the distance between the entities and optionally the obstructions between them into account (see <paramref name="obstructionMultiplier"/>).
/// </summary>
/// <param name="obstructionMultiplier">Values greater than or equal to 1 cause the message to get garbled more heavily when there's some obstruction between the characters. Values smaller than 1 mean the garbling only depends on distance.</param>
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionMultiplier = 2.0f)
{
if (listener == null || sender == null)
{
@@ -177,12 +181,12 @@ namespace Barotrauma.Networking
Hull listenerHull = listener == null ? null : Hull.FindHull(listener.WorldPosition);
Hull sourceHull = sender == null ? null : Hull.FindHull(sender.WorldPosition);
if (sourceHull != listenerHull)
if (sourceHull != listenerHull && obstructionMultiplier >= 1.0f)
{
if ((sourceHull == null || !sourceHull.GetConnectedHulls(includingThis: false, searchDepth: 2, ignoreClosedGaps: true).Contains(listenerHull)) &&
Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null)
{
dist = (dist + 100f) * obstructionmult;
dist = (dist + 100f) * obstructionMultiplier;
}
}
if (dist > range) { return 1.0f; }
@@ -197,9 +201,9 @@ namespace Barotrauma.Networking
return ApplyDistanceEffect(listener, Sender, Text, SpeakRange);
}
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionmult = 2.0f)
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionMultiplier = 2.0f)
{
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionmult));
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionMultiplier));
}
public static string ApplyDistanceEffect(string text, float garbleAmount)
@@ -252,7 +256,7 @@ namespace Barotrauma.Networking
var senderRadio = senderItem.GetComponent<WifiComponent>();
if (!receiverRadio.CanReceive(senderRadio)) { continue; }
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range);
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range, obstructionMultiplier: 0);
if (sender.SpeechImpediment > 0.0f)
{
//speech impediment doesn't reduce the range when using a radio, but adds extra garbling
@@ -17,14 +17,9 @@ namespace Barotrauma.Networking
public LidgrenAddress(IPAddress netAddress)
{
if (IPAddress.IsLoopback(netAddress))
{
NetAddress = IPAddress.Loopback;
}
else
{
NetAddress = netAddress;
}
if (IPAddress.IsLoopback(netAddress)) { netAddress = IPAddress.Loopback; }
if (netAddress.IsIPv4MappedToIPv6) { netAddress = netAddress.MapToIPv4(); }
NetAddress = netAddress;
}
public new static Option<LidgrenAddress> Parse(string endpointStr)
@@ -19,7 +19,7 @@ namespace Barotrauma.Networking
public LidgrenEndpoint(IPEndPoint netEndpoint) : base(new LidgrenAddress(netEndpoint.Address))
{
NetEndpoint = netEndpoint;
NetEndpoint = new IPEndPoint((Address as LidgrenAddress)!.NetAddress, netEndpoint.Port);
}
public new static Option<LidgrenEndpoint> Parse(string endpointStr)
@@ -95,11 +95,7 @@ namespace Barotrauma
public override void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition = null)
{
if (this.type != type) { return; }
if (intervalTimer > 0.0f)
{
intervalTimer -= deltaTime;
return;
}
if (ShouldWaitForInterval(entity, deltaTime)) { return; }
if (!HasRequiredItems(entity)) { return; }
if (delayType == DelayTypes.ReachCursor && Character.Controlled == null) { return; }
if (!Stackable)
@@ -416,22 +416,26 @@ namespace Barotrauma
return false;
}
switch (Operator)
{
case OperatorType.Equals:
if (type == typeof(bool))
{
return property.GetBoolValue(target) == (AttributeValue == "true" || AttributeValue == "True");
if (type == typeof(bool))
{
return property.GetBoolValue(target) == (AttributeValue == "true" || AttributeValue == "True");
}
var value = property.GetValue(target);
return Equals(value, AttributeValue);
}
return property.GetValue(target).ToString().Equals(AttributeValue);
case OperatorType.NotEquals:
if (type == typeof(bool))
{
return property.GetBoolValue(target) != (AttributeValue == "true" || AttributeValue == "True");
if (type == typeof(bool))
{
return property.GetBoolValue(target) != (AttributeValue == "true" || AttributeValue == "True");
}
var value = property.GetValue(target);
return !Equals(value, AttributeValue);
}
return !property.GetValue(target).ToString().Equals(AttributeValue);
case OperatorType.GreaterThan:
case OperatorType.LessThanEquals:
case OperatorType.LessThan:
@@ -441,6 +445,18 @@ namespace Barotrauma
break;
}
return false;
static bool Equals(object value, string desiredValue)
{
if (value == null)
{
return desiredValue.Equals("null", StringComparison.OrdinalIgnoreCase);
}
else
{
return value.ToString().Equals(desiredValue);
}
}
}
}
@@ -343,7 +343,7 @@ namespace Barotrauma
private readonly float lifeTime;
private float lifeTimer;
public float intervalTimer;
public Dictionary<Entity, float> intervalTimers = new Dictionary<Entity, float>();
public static readonly List<DurationListElement> DurationList = new List<DurationListElement>();
@@ -1120,6 +1120,26 @@ namespace Barotrauma
}
}
private static readonly List<Entity> intervalsToRemove = new List<Entity>();
public bool ShouldWaitForInterval(Entity entity, float deltaTime)
{
if (Interval > 0.0f && entity != null)
{
if (intervalTimers.ContainsKey(entity))
{
intervalTimers[entity] -= deltaTime;
if (intervalTimers[entity] > 0.0f) { return true; }
}
intervalsToRemove.Clear();
intervalsToRemove.AddRange(intervalTimers.Keys.Where(e => e.Removed));
foreach (var toRemove in intervalsToRemove)
{
intervalTimers.Remove(toRemove);
}
}
return false;
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
{
if (this.type != type || !HasRequiredItems(entity)) { return; }
@@ -1147,12 +1167,7 @@ namespace Barotrauma
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition = null)
{
if (this.type != type) { return; }
if (intervalTimer > 0.0f)
{
intervalTimer -= deltaTime;
return;
}
if (ShouldWaitForInterval(entity, deltaTime)) { return; }
currentTargets.Clear();
foreach (ISerializableEntity target in targets)
@@ -1255,11 +1270,8 @@ namespace Barotrauma
lifeTimer -= deltaTime;
if (lifeTimer <= 0) { return; }
}
if (intervalTimer > 0.0f)
{
intervalTimer -= deltaTime;
return;
}
if (ShouldWaitForInterval(entity, deltaTime)) { return; }
Hull hull = GetHull(entity);
Vector2 position = GetPosition(entity, targets, worldPosition);
if (useItemCount > 0)
@@ -1942,7 +1954,10 @@ namespace Barotrauma
ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true);
intervalTimer = Interval;
if (Interval > 0.0f && entity != null)
{
intervalTimers[entity] = Interval;
}
static Character CharacterFromTarget(ISerializableEntity target)
{
@@ -1,12 +1,12 @@
#nullable enable
using Barotrauma.IO;
using Barotrauma.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Globalization;
using System.Text.Unicode;
@@ -33,54 +33,104 @@ namespace Barotrauma
public static int LanguageVersion { get; private set; } = 0;
private static readonly ImmutableArray<Range<int>> CjkRanges = new[]
{
UnicodeRanges.HangulJamo,
UnicodeRanges.Hiragana,
UnicodeRanges.Katakana,
UnicodeRanges.CjkRadicalsSupplement,
UnicodeRanges.CjkSymbolsandPunctuation,
UnicodeRanges.EnclosedCjkLettersandMonths,
UnicodeRanges.CjkCompatibility,
UnicodeRanges.CjkUnifiedIdeographsExtensionA,
UnicodeRanges.CjkUnifiedIdeographs,
UnicodeRanges.HangulSyllables,
UnicodeRanges.CjkCompatibilityForms
}.Select(r => new Range<int>(r.FirstCodePoint, r.FirstCodePoint+r.Length-1))
.OrderBy(r => r.Start)
.ToImmutableArray();
private static ImmutableArray<Range<int>> UnicodeToIntRanges(params UnicodeRange[] ranges)
=> ranges
.Select(r => new Range<int>(r.FirstCodePoint, r.FirstCodePoint + r.Length - 1))
.OrderBy(r => r.Start)
.ToImmutableArray();
/// <summary>
/// Does the string contain symbols from Chinese, Japanese or Korean languages
/// </summary>
public static bool IsCJK(LocalizedString text)
[Flags]
public enum SpeciallyHandledCharCategory
{
return IsCJK(text.Value);
None = 0x0,
CJK = 0x1,
Cyrillic = 0x2,
All = 0x3
}
public static bool IsCJK(string text)
{
if (string.IsNullOrEmpty(text)) { return false; }
public static readonly ImmutableArray<SpeciallyHandledCharCategory> SpeciallyHandledCharCategories
= Enum.GetValues<SpeciallyHandledCharCategory>()
.Where(c => c is not (SpeciallyHandledCharCategory.None or SpeciallyHandledCharCategory.All))
.ToImmutableArray();
private static readonly ImmutableDictionary<SpeciallyHandledCharCategory, ImmutableArray<Range<int>>> SpeciallyHandledCharacterRanges
= new[]
{
(SpeciallyHandledCharCategory.CJK, UnicodeToIntRanges(
UnicodeRanges.HangulJamo,
UnicodeRanges.Hiragana,
UnicodeRanges.Katakana,
UnicodeRanges.CjkRadicalsSupplement,
UnicodeRanges.CjkSymbolsandPunctuation,
UnicodeRanges.EnclosedCjkLettersandMonths,
UnicodeRanges.CjkCompatibility,
UnicodeRanges.CjkUnifiedIdeographsExtensionA,
UnicodeRanges.CjkUnifiedIdeographs,
UnicodeRanges.HangulSyllables,
UnicodeRanges.CjkCompatibilityForms
)),
(SpeciallyHandledCharCategory.Cyrillic, UnicodeToIntRanges(
UnicodeRanges.Cyrillic,
UnicodeRanges.CyrillicSupplement,
UnicodeRanges.CyrillicExtendedA,
UnicodeRanges.CyrillicExtendedB,
UnicodeRanges.CyrillicExtendedC
))
}.ToImmutableDictionary();
public static SpeciallyHandledCharCategory GetSpeciallyHandledCategories(LocalizedString text)
=> GetSpeciallyHandledCategories(text.Value);
public static SpeciallyHandledCharCategory GetSpeciallyHandledCategories(string text)
{
if (string.IsNullOrEmpty(text)) { return SpeciallyHandledCharCategory.None; }
var retVal = SpeciallyHandledCharCategory.None;
for (int i = 0; i < text.Length; i++)
{
char chr = text[i];
for (int j = 0; j < CjkRanges.Length; j++)
foreach (var category in SpeciallyHandledCharCategories)
{
var range = CjkRanges[j];
if (retVal.HasFlag(category)) { continue; }
for (int j = 0; j < SpeciallyHandledCharacterRanges[category].Length; j++)
{
var range = SpeciallyHandledCharacterRanges[category][j];
// If chr < range.Start, we know that it can't
// be in any of the following ranges, so let's
// not even bother checking them
if (chr < range.Start) { break; }
// If chr < range.Start, we know that it can't
// be in any of the following ranges, so let's
// not even bother checking them
if (chr < range.Start) { break; }
// This character is in a range, return true
if (range.Contains(chr)) { return true; }
// This character is in a range, set the flag
if (range.Contains(chr))
{
retVal |= category;
break;
}
}
}
if (retVal == SpeciallyHandledCharCategory.All)
{
// Input contains characters from all
// specially handled categories, there's
// no need to inspect the string further
return SpeciallyHandledCharCategory.All;
}
}
return false;
return retVal;
}
public static bool IsCJK(LocalizedString text)
=> IsCJK(text.Value);
public static bool IsCJK(string text)
=> GetSpeciallyHandledCategories(text).HasFlag(SpeciallyHandledCharCategory.CJK);
/// <summary>
/// Check if the currently selected language is available, and switch to English if not
/// </summary>
@@ -562,35 +562,45 @@ namespace Barotrauma
public static double LineSegmentToPointDistanceSquared(Point lineA, Point lineB, Point point)
{
double xDiff = lineB.X - lineA.X;
double yDiff = lineB.Y - lineA.Y;
return LineSegmentToPointDistanceSquared(lineA.X, lineA.Y, lineB.X, lineB.Y, point.X, point.Y);
}
public static float LineSegmentToPointDistanceSquared(Vector2 lineA, Vector2 lineB, Vector2 point)
{
return (float)LineSegmentToPointDistanceSquared(lineA.X, lineA.Y, lineB.X, lineB.Y, point.X, point.Y);
}
private static double LineSegmentToPointDistanceSquared(double line1X, double line1Y, double line2X, double line2Y, double pointX, double pointY)
{
double xDiff = line2X - line1X;
double yDiff = line2Y - line1Y;
if (xDiff == 0 && yDiff == 0)
{
double v1 = lineA.X - point.X;
double v2 = lineA.Y - point.Y;
double v1 = line1X - pointX;
double v2 = line1Y - pointY;
return (v1 * v1) + (v2 * v2);
}
// Calculate the t that minimizes the distance.
double t = ((point.X - lineA.X) * xDiff + (point.Y - lineA.Y) * yDiff) / (xDiff * xDiff + yDiff * yDiff);
double t = ((pointX - line1X) * xDiff + (pointY - line1Y) * yDiff) / (xDiff * xDiff + yDiff * yDiff);
// See if this represents one of the segment's
// end points or a point in the middle.
if (t < 0)
{
xDiff = point.X - lineA.X;
yDiff = point.Y - lineA.Y;
xDiff = pointX - line1X;
yDiff = pointY - line1Y;
}
else if (t > 1)
{
xDiff = point.X - lineB.X;
yDiff = point.Y - lineB.Y;
xDiff = pointX - line2X;
yDiff = pointY - line2Y;
}
else
{
xDiff = point.X - (lineA.X + t * xDiff);
yDiff = point.Y - (lineA.Y + t * yDiff);
xDiff = pointX - (line1X + t * xDiff);
yDiff = pointY - (line1Y + t * yDiff);
}
return xDiff * xDiff + yDiff * yDiff;
@@ -7,6 +7,7 @@ using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Barotrauma.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
@@ -825,16 +826,29 @@ namespace Barotrauma
public static bool StatIdentifierMatches(Identifier original, Identifier match)
{
if (original == match) { return true; }
return Matches(original, match) || Matches(match, original);
for (int i = 0; i < match.Value.Length; i++)
static bool Matches(Identifier a, Identifier b)
{
if (i >= original.Value.Length) { return match[i] is '~'; }
if (!CharEquals(original[i], match[i])) { return false; }
for (int i = 0; i < b.Value.Length; i++)
{
if (i >= a.Value.Length) { return b[i] is '~'; }
if (!CharEquals(a[i], b[i])) { return false; }
}
return false;
}
return false;
static bool CharEquals(char a, char b) => char.ToLowerInvariant(a) == char.ToLowerInvariant(b);
}
public static bool EquivalentTo(this IPEndPoint self, IPEndPoint other)
=> self.Address.EquivalentTo(other.Address) && self.Port == other.Port;
public static bool EquivalentTo(this IPAddress self, IPAddress other)
{
if (self.IsIPv4MappedToIPv6) { self = self.MapToIPv4(); }
if (other.IsIPv4MappedToIPv6) { other = other.MapToIPv4(); }
return self.Equals(other);
}
}
}