Build 0.20.7.0
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
+2
-20
@@ -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()
|
||||
|
||||
+19
-17
@@ -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))
|
||||
{
|
||||
|
||||
+10
-2
@@ -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))
|
||||
|
||||
+43
-35
@@ -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);
|
||||
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+8
-8
@@ -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
|
||||
|
||||
+2
-4
@@ -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
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveFlag : CharacterAbility
|
||||
{
|
||||
|
||||
+6
-3
@@ -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)
|
||||
|
||||
+5
-3
@@ -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);
|
||||
|
||||
+8
@@ -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()
|
||||
|
||||
+9
-5
@@ -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
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveStat : CharacterAbility
|
||||
{
|
||||
|
||||
+6
-5
@@ -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)
|
||||
|
||||
+4
@@ -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)
|
||||
|
||||
-2
@@ -1,6 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+4
@@ -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
-4
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyFlag : CharacterAbility
|
||||
{
|
||||
|
||||
+18
-8
@@ -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
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyStat : CharacterAbility
|
||||
{
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+5
-3
@@ -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
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPutItem : CharacterAbility
|
||||
{
|
||||
|
||||
+5
-1
@@ -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
-4
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityRevive : CharacterAbility
|
||||
{
|
||||
|
||||
+4
@@ -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
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
-2
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApprenticeship : CharacterAbility
|
||||
{
|
||||
|
||||
-3
@@ -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);
|
||||
}
|
||||
|
||||
+2
-5
@@ -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
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityMultitasker : CharacterAbility
|
||||
{
|
||||
|
||||
+4
-6
@@ -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;
|
||||
|
||||
|
||||
-3
@@ -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
-1
@@ -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
|
||||
|
||||
+3
-8
@@ -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)
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,47 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.20.7.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Changes:
|
||||
- Added a slider to the fabricator that can be used to select how many items to fabricate.
|
||||
- Chance of finding good/excellent/masterwork quality items in higher-difficulty levels.
|
||||
- Changed engines' default repair threshold to 50 to 80.
|
||||
- Quality of the radio voice chat diminishes with distance (gradually fading into radio static), similar to the way as the quality of the text chat.
|
||||
- Voice chat range also affects spectators (= spectators can't hear players talking at the other side of the level).
|
||||
|
||||
Unstable only:
|
||||
- Changed how the restrictions on specialization talent trees work: instead of being only allowed to choose one, you can unlock all of them (but need to complete the entire specialization tree before you can choose another one).
|
||||
- Any number of generic talents can be chosen (but you can still only pick talents from the next tier when you've unlocked 2 generic talents).
|
||||
- Anyone can use the recipes bots have unlocked via talents.
|
||||
- Various fixes, balance changes and improvements to talents.
|
||||
- Fixed Arc Emitter damaging nearby characters that aren't near the arc (e.g. characters behind the shooter's back).
|
||||
- Fixed null reference exception in EnemyAIController.UpdateLimbAttack (caused frequent crashes when getting attacked by swarms).
|
||||
- Improvements to the exosuit sprite.
|
||||
- Fixed bots not being able to apply medical treatment anymore.
|
||||
- Fixed switching from a directional ping to passive immediately making the item's AITarget sector a full 360 degrees, and AITarget's sector not being taken into account when determining what it should reveal on passive sonar.
|
||||
- Fixed submarine availability being displayed incorrectly on some locations (e.g. abandoned outposts) on the campaign map.
|
||||
- Fixed feign death not working when you have a provocative items (e.g. items that make sounds or otherwise attrack monsters) in the inventory.
|
||||
- Restored some of the wrecked sprites that were replaced with tinted version of the normal sprite in the previous builds (WIP).
|
||||
- Fixed fiber plant sprite.
|
||||
- Fixed bigbrother event getting stuck when choosing "not interested".
|
||||
- Fixed autoinjectors (autoinjector headset, PUCS) no longer working.
|
||||
|
||||
Bugfixes:
|
||||
- Fixed main menu sometimes appearing half obstructed when starting the game on Mac.
|
||||
- Fixed speech impediments only affecting the text chat, not the voice chat.
|
||||
- Fixed radio voice chat not working properly if the range of the radio is larger than 250 meters. Happened because characters' positions aren't synced if they're further away than 250 meters from the client. In practice, the quality/volume of the chat would stop diminishing after 250 meters, and then immediately cut off when outside the radio range.
|
||||
- Fixed inability to connect to IPv4 servers when IPv6 is disabled.
|
||||
- Fixed husk appendage breaking if the character already has extra limbs from e.g. genes.
|
||||
- Fixed blocked doorways in Alien_Entrance3.
|
||||
- Fixed Cyrillic symbols not being visible in the server list's server info panel when playing in a language other than Russian.
|
||||
- Fixed certain genetic effects (such as regeneration from Hammerhead Matriarch genes) not working properly when multiple characters have the same effect.
|
||||
- Adjusted railgun, coilgun and double coilgun firing offsets to make the projectile spawn closer to the end of the barrel.
|
||||
- Fixed incorrect tags in the vending machine in EngineeringModule_01_Colony, causing engineering gear to spawn in the output slot.
|
||||
|
||||
Modding:
|
||||
- Made it possible to check if some value is null or not with PropertyConditionals (e.g. CurrentHull="eq null").
|
||||
- Added UseEnvironment.None to Propulsion component.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.20.6.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user