Unstable 0.1500.8.0

This commit is contained in:
Markus Isberg
2021-10-16 10:32:38 +09:00
parent de917c5d74
commit fc2f7b76da
57 changed files with 608 additions and 302 deletions
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
@@ -346,7 +347,7 @@ namespace Barotrauma
}
#region Escape
public abstract void Escape(float deltaTime);
public abstract bool Escape(float deltaTime);
public Gap EscapeTarget { get; private set; }
@@ -425,17 +426,38 @@ namespace Barotrauma
}
if (EscapeTarget != null)
{
SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10);
float sqrDist = Vector2.DistanceSquared(Character.SimPosition, EscapeTarget.SimPosition);
if (sqrDist < 0.5f || Character.CurrentHull == null || HasValidPath(requireNonDirty: true, requireUnfinished: false) && pathSteering.CurrentPath.Finished)
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
float sqrDist = diff.LengthSquared();
if (Character.CurrentHull == null || sqrDist < MathUtils.Pow2(50) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
{
// Very close to the target, outside, or at the end of the path -> just steer towards it manually without using the path
// Very close to the target, outside, or at the end of the path -> try to steer through the gap
SteeringManager.Reset();
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(EscapeTarget.WorldPosition - Character.WorldPosition));
if (sqrDist < 4)
pathSteering?.ResetPath();
if (sqrDist < MathUtils.Pow2(50))
{
return true;
// Very close -> just keep steering forward
var forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2);
SteeringManager.SteeringManual(deltaTime, forward);
}
else if (Character.CurrentHull == null)
{
// Outside -> steer away from the target
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(-diff));
}
else
{
// Still inside -> steer towards the target
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(diff));
}
return sqrDist < MathUtils.Pow2(200);
}
else if (pathSteering != null)
{
pathSteering.SteeringSeek(EscapeTarget.SimPosition, weight: 1, minGapSize);
}
else
{
SteeringManager.SteeringSeek(EscapeTarget.SimPosition, 10);
}
}
else
@@ -1903,7 +1903,7 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
if (Character.Params.CanInteract)
if (Character.Params.CanInteract && attackResult.Damage > 10)
{
ReleaseDragTargets();
}
@@ -3607,12 +3607,12 @@ namespace Barotrauma
public bool CanPassThroughHole(Structure wall, int sectionIndex) => CanPassThroughHole(wall, sectionIndex, requiredHoleCount);
public override void Escape(float deltaTime)
public override bool Escape(float deltaTime)
{
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
{
State = AIState.Idle;
return;
return false;
}
else if (SelectedTargetMemory is AITargetMemory targetMemory && SelectedAiTarget?.Entity is Character)
{
@@ -3653,6 +3653,7 @@ namespace Barotrauma
}
}
}
return isSteeringThroughGap;
void SteerAwayFromTheEnemy()
{
@@ -1364,10 +1364,7 @@ namespace Barotrauma
ObjectiveManager.WaitTimer = waitDuration;
}
public override void Escape(float deltaTime)
{
UpdateEscape(deltaTime, canAttackDoors: false);
}
public override bool Escape(float deltaTime) => UpdateEscape(deltaTime, canAttackDoors: false);
private void CheckCrouching(float deltaTime)
{
@@ -269,7 +269,7 @@ namespace Barotrauma
{
var waypoint = CurrentPath.Nodes[i];
float directDistance = Vector2.DistanceSquared(character.WorldPosition, waypoint.WorldPosition);
if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel) != null)
if (directDistance > (pathDistance * pathDistance) || Submarine.PickBody(host.SimPosition, waypoint.SimPosition, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
{
pathDistance -= CurrentPath.GetLength(startIndex: i - 1, endIndex: i);
continue;
@@ -9,6 +9,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "return";
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
private bool usingEscapeBehavior;
private bool isSteeringThroughGap;
public Submarine ReturnTarget { get; }
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
@@ -57,7 +58,7 @@ namespace Barotrauma
return;
}
bool shouldUseEscapeBehavior = false;
if (character.CurrentHull != null)
if (character.CurrentHull != null || isSteeringThroughGap)
{
if (character.Submarine == null || !character.Submarine.IsConnectedTo(ReturnTarget))
{
@@ -67,8 +68,8 @@ namespace Barotrauma
{
HumanAIController.ResetEscape();
}
HumanAIController.Escape(deltaTime);
if (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable)
isSteeringThroughGap = HumanAIController.Escape(deltaTime);
if (!isSteeringThroughGap && (HumanAIController.EscapeTarget == null || HumanAIController.IsCurrentPathUnreachable))
{
Abandon = true;
}
@@ -92,7 +93,10 @@ namespace Barotrauma
RemoveSubObjective(ref moveInCaveObjective);
RemoveSubObjective(ref moveOutsideObjective);
TryAddSubObjective(ref moveInsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager),
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveInsideObjective),
onAbandon: () => Abandon = true);
}
@@ -110,7 +114,7 @@ namespace Barotrauma
IsCompleted = true;
}
}
else if (moveInCaveObjective == null && moveOutsideObjective == null)
else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null)
{
if (HumanAIController.IsInsideCave)
{
@@ -134,7 +138,8 @@ namespace Barotrauma
TryAddSubObjective(ref moveInCaveObjective,
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
{
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint,
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective),
onAbandon: () => Abandon = true);
@@ -170,7 +175,10 @@ namespace Barotrauma
RemoveSubObjective(ref moveInsideObjective);
RemoveSubObjective(ref moveInCaveObjective);
TryAddSubObjective(ref moveOutsideObjective,
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager),
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
{
AllowGoingOutside = true
},
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
onAbandon: () => Abandon = true);
}
@@ -221,6 +229,7 @@ namespace Barotrauma
moveInCaveObjective = null;
moveOutsideObjective = null;
usingEscapeBehavior = false;
isSteeringThroughGap = false;
HumanAIController.ResetEscape();
}
@@ -236,6 +236,7 @@ namespace Barotrauma
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
if (body != null)
{
if (body.UserData is Submarine) { return false; }
if (body.UserData is Structure s && !s.IsPlatform) { return false; }
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; }
}
@@ -684,18 +684,15 @@ namespace Barotrauma
if (rightHand != null && !rightHand.Disabled)
{
HandIK(rightHand, torso.SimPosition + posAddition +
new Vector2(
-handPos.X,
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
HandIK(rightHand,
torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY),
CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
}
if (leftHand != null && !leftHand.Disabled)
{
HandIK(leftHand, torso.SimPosition + posAddition +
new Vector2(
handPos.X,
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY), CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
HandIK(leftHand,
torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY),
CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
}
}
else
@@ -705,9 +702,7 @@ namespace Barotrauma
Vector2 footPos = colliderPos;
if (Crouching)
{
footPos = new Vector2(
Math.Sign(stepSize.X * i) * Dir * 0.4f,
colliderPos.Y);
footPos = new Vector2(Math.Sign(stepSize.X * i) * Dir * 0.35f, colliderPos.Y);
if (Math.Sign(footPos.X) != Math.Sign(Dir))
{
//lift the foot at the back up a bit
@@ -728,9 +723,16 @@ namespace Barotrauma
{
foot.DebugRefPos = colliderPos;
foot.DebugTargetPos = footPos;
MoveLimb(foot, footPos, CurrentGroundedParams.FootMoveStrength);
FootIK(foot, footPos,
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
float footMoveForce = CurrentGroundedParams.FootMoveStrength;
float legBendTorque = CurrentGroundedParams.LegBendTorque;
if (Crouching)
{
// Keeps the pose
legBendTorque = 100;
footMoveForce *= 2;
}
MoveLimb(foot, footPos, footMoveForce);
FootIK(foot, footPos, legBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
}
}
@@ -760,6 +762,12 @@ namespace Barotrauma
forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength);
}
}
// Try to keep the wrist straight
LimbJoint wrist = GetJointBetweenLimbs(foreArmType, hand.type);
if (wrist != null)
{
hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * CurrentGroundedParams.HandMoveStrength);
}
}
}
}
@@ -1008,6 +1016,12 @@ namespace Barotrauma
speedMultiplier = Math.Min(speedMultiplier, 0.1f);
}
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
// Try to keep the wrist straight
LimbJoint wrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
if (wrist != null)
{
rightHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * rightHand.Mass * 100f * CurrentSwimParams.HandMoveStrength);
}
}
if (leftHand != null && !leftHand.Disabled)
@@ -1021,6 +1035,12 @@ namespace Barotrauma
speedMultiplier = Math.Min(speedMultiplier, 0.1f);
}
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.ArmMoveStrength * speedMultiplier, CurrentSwimParams.HandMoveStrength * speedMultiplier);
// Try to keep the wrist straight
LimbJoint wrist = GetJointBetweenLimbs(LimbType.LeftForearm, LimbType.LeftHand);
if (wrist != null)
{
leftHand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * leftHand.Mass * 100f * CurrentSwimParams.HandMoveStrength);
}
}
}
@@ -665,6 +665,7 @@ namespace Barotrauma
public float MaxVitality => CharacterHealth.MaxVitality;
public float MaxHealth => MaxVitality;
public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached;
public float Bloodloss
{
@@ -2751,7 +2752,8 @@ namespace Barotrauma
}
else if (this != Controlled)
{
IsRagdolled = IsKeyDown(InputType.Ragdoll);
wasRagdolled = IsRagdolled;
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll);
}
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll))
@@ -2765,13 +2767,18 @@ namespace Barotrauma
{
wasRagdolled = IsRagdolled;
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; }
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; }
}
}
if (!wasRagdolled && IsRagdolled && selfRagdolled)
if (!wasRagdolled && IsRagdolled)
{
CheckTalents(AbilityEffectType.OnSelfRagdoll);
if (selfRagdolled)
{
CheckTalents(AbilityEffectType.OnSelfRagdoll);
}
// currently does not work when you are stunned, like it should
CheckTalents(AbilityEffectType.OnRagdoll);
}
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
@@ -3535,11 +3542,6 @@ namespace Barotrauma
if (Removed) { return new AttackResult(); }
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{
if (attacker.TeamID == TeamID) { return new AttackResult(); }
}
float closestDistance = 0.0f;
foreach (Limb limb in AnimController.Limbs)
{
@@ -3602,7 +3604,11 @@ namespace Barotrauma
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{
if (attacker.TeamID == TeamID) { return new AttackResult(); }
if (attacker.TeamID == TeamID)
{
afflictions = afflictions.Where(a => !a.Prefab.IsBuff);
if (!afflictions.Any()) { return new AttackResult(); }
}
}
#if CLIENT
@@ -4385,14 +4391,14 @@ namespace Barotrauma
info.UnlockedTalents.Add(talentPrefab.Identifier);
if (characterTalents.Any(t => t.Prefab == talentPrefab)) { return false; }
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
#endif
CharacterTalent characterTalent = new CharacterTalent(talentPrefab, this);
characterTalent.ActivateTalent(addingFirstTime);
characterTalents.Add(characterTalent);
characterTalent.AddedThisRound = addingFirstTime;
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateTalents });
#endif
if (addingFirstTime)
{
OnTalentGiven(talentPrefab.Identifier);
@@ -216,6 +216,17 @@ namespace Barotrauma
public HashSet<string> UnlockedTalents { get; private set; } = new HashSet<string>();
/// <summary>
/// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to cull them from the selection
/// </summary>
public IEnumerable<string> GetUnlockedTalentsInTree()
{
if (!TalentTree.JobTalentTrees.TryGetValue(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty<string>(); }
return UnlockedTalents.Where(t => talentTree.TalentIsInTree(t));
}
public int AdditionalTalentPoints { get; set; }
private Sprite _headSprite;
@@ -1220,7 +1231,7 @@ namespace Barotrauma
var experienceGainMultiplier = new AbilityValue(1f);
if (isMissionExperience)
{
Character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
}
experienceGainMultiplier.Value += Character.GetStatValue(StatTypes.ExperienceGainMultiplier);
@@ -1252,7 +1263,7 @@ namespace Barotrauma
public int GetAvailableTalentPoints()
{
// hashset always has at least 1
return Math.Max(GetTotalTalentPoints() - UnlockedTalents.Count, 0);
return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0);
}
public float GetProgressTowardsNextLevel()
@@ -1297,6 +1308,8 @@ namespace Barotrauma
partial void OnExperienceChanged(int prevAmount, int newAmount);
partial void OnPermanentStatChanged(StatTypes statType);
public void Rename(string newName)
{
if (string.IsNullOrEmpty(newName)) { return; }
@@ -1362,7 +1375,7 @@ namespace Barotrauma
Job.Save(charElement);
XElement savedStatElement = new XElement("savedstatvalues");
foreach (var statValuePair in savedStatValues)
foreach (var statValuePair in SavedStatValues)
{
foreach (var savedStat in statValuePair.Value)
{
@@ -1708,26 +1721,42 @@ namespace Barotrauma
}
// This could maybe be a LookUp instead?
private readonly Dictionary<StatTypes, List<SavedStatValue>> savedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>();
public readonly Dictionary<StatTypes, List<SavedStatValue>> SavedStatValues = new Dictionary<StatTypes, List<SavedStatValue>>();
public void ResetSavedStatValues()
public void ClearSavedStatValues()
{
foreach (var savedStatValue in savedStatValues.SelectMany(s => s.Value))
foreach (StatTypes statType in SavedStatValues.Keys)
{
if (savedStatValue.RemoveOnDeath)
{
savedStatValue.StatValue = 0f;
}
OnPermanentStatChanged(statType);
}
SavedStatValues.Clear();
}
public void ClearSavedStatValues(StatTypes statType)
{
SavedStatValues.Remove(statType);
OnPermanentStatChanged(statType);
}
public void ResetSavedStatValue(string statIdentifier)
{
savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f);
foreach (StatTypes statType in SavedStatValues.Keys)
{
bool changed = false;
foreach (SavedStatValue savedStatValue in SavedStatValues[statType])
{
if (savedStatValue.StatIdentifier != statIdentifier) { continue; }
if (MathUtils.NearlyEqual(savedStatValue.StatValue, 0.0f)) { continue; }
savedStatValue.StatValue = 0.0f;
changed = true;
}
if (changed) { OnPermanentStatChanged(statType); }
}
}
public float GetSavedStatValue(StatTypes statType)
{
if (savedStatValues.TryGetValue(statType, out var statValues))
if (SavedStatValues.TryGetValue(statType, out var statValues))
{
return statValues.Sum(v => v.StatValue);
}
@@ -1738,7 +1767,7 @@ namespace Barotrauma
}
public float GetSavedStatValue(StatTypes statType, string statIdentifier)
{
if (savedStatValues.TryGetValue(statType, out var statValues))
if (SavedStatValues.TryGetValue(statType, out var statValues))
{
return statValues.Where(s => s.StatIdentifier.Equals(statIdentifier, StringComparison.OrdinalIgnoreCase)).Sum(v => v.StatValue);
}
@@ -1750,19 +1779,24 @@ namespace Barotrauma
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue, bool setValue = false)
{
if (!savedStatValues.ContainsKey(statType))
if (!SavedStatValues.ContainsKey(statType))
{
savedStatValues.Add(statType, new List<SavedStatValue>());
SavedStatValues.Add(statType, new List<SavedStatValue>());
}
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
bool changed = false;
if (SavedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{
float prevValue = savedStat.StatValue;
savedStat.StatValue = setValue ? value : MathHelper.Min(savedStat.StatValue + value, maxValue);
changed = !MathUtils.NearlyEqual(savedStat.StatValue, prevValue);
}
else
{
savedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound));
SavedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound));
changed = true;
}
if (changed) { OnPermanentStatChanged(statType); }
}
}
@@ -11,6 +11,7 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects;
private readonly bool nearbyCharactersAppliesToSelf;
private readonly bool nearbyCharactersAppliesToAllies;
private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -20,6 +21,7 @@ namespace Barotrauma.Abilities
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true);
}
protected void ApplyEffectSpecific(Character targetCharacter)
@@ -40,6 +42,10 @@ namespace Barotrauma.Abilities
{
targets.RemoveAll(c => c == Character);
}
if (!nearbyCharactersAppliesToAllies)
{
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character));
}
statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
@@ -56,17 +62,20 @@ namespace Barotrauma.Abilities
}
}
protected override void ApplyEffect()
{
ApplyEffectSpecific(Character);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter)
{
ApplyEffectSpecific(selectedCharacter);
}
else if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
else
{
ApplyEffectSpecific(Character);
}
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly bool setValue;
//private readonly float maximumValue;
public override bool AllowClientSimulation => true;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
@@ -6,6 +6,7 @@ namespace Barotrauma.Abilities
{
private readonly string statIdentifier;
public override bool AppliesEffectOnIntervalUpdate => true;
public override bool AllowClientSimulation => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -31,7 +31,7 @@ namespace Barotrauma.Abilities
}
}
if (closestCharacter.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; }
if (closestCharacter.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; }
if (closestDistance < squaredMaxDistance)
{
@@ -66,6 +66,11 @@ namespace Barotrauma
}
}
public bool TalentIsInTree(string talentIdentifier)
{
return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier);
}
public static void LoadFromFile(ContentFile file)
{
DebugConsole.Log("Loading talent tree: " + file.Path);
@@ -602,7 +602,7 @@ namespace Barotrauma
}
}));
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name] [limb type] [use relative strength]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
{
if (args.Length < 2) { return; }
@@ -622,14 +622,12 @@ namespace Barotrauma
}
bool relativeStrength = false;
if (args.Length > 2)
if (args.Length > 4)
{
bool.TryParse(args[2], out relativeStrength);
bool.TryParse(args[4], out relativeStrength);
}
Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] });
Character targetCharacter = args.Length <= 2 ? Character.Controlled : FindMatchingCharacter(new string[] { args[2] });
if (targetCharacter != null)
{
Limb targetLimb = targetCharacter.AnimController.MainLimb;
@@ -1889,9 +1887,9 @@ namespace Barotrauma
if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client))
{
#if DEBUG
AddWarning("You're not permitted to use the command \"{matchingCommand.Name}\". Executing the command anyway because this is a debug build.");
AddWarning($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\". Executing the command anyway because this is a debug build.");
#else
ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!");
ThrowError($"You're not permitted to use the command \"{splitCommand[0].ToLowerInvariant()}\"!");
return;
#endif
}
@@ -47,6 +47,7 @@
OnReduceAffliction,
OnAddDamageAffliction,
OnSelfRagdoll,
OnRagdoll,
OnRoundEnd,
OnAnyMissionCompleted,
OnAllMissionsCompleted,
@@ -179,7 +179,7 @@ namespace Barotrauma
if (eventSet == null) { return; }
if (eventSet.OncePerOutpost)
{
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.prefab))
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.Prefabs))
{
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
{
@@ -434,23 +434,31 @@ namespace Barotrauma
}
}
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e =>
string.IsNullOrEmpty(e.prefab.BiomeIdentifier) ||
e.prefab.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
bool isPrefabSuitable(EventPrefab p)
=> string.IsNullOrEmpty(p.BiomeIdentifier) ||
p.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase);
var suitablePrefabSubsets = eventSet.EventPrefabs
.FindAll(p => p.Prefabs.Any(isPrefabSuitable));
for (int i = 0; i < applyCount; i++)
{
if (eventSet.ChooseRandom)
{
if (suitablePrefabs.Count > 0)
if (suitablePrefabSubsets.Count > 0)
{
var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(suitablePrefabs);
var unusedEvents = suitablePrefabSubsets.ToList();
for (int j = 0; j < eventSet.EventCount; j++)
{
if (unusedEvents.All(e => CalculateCommonness(e.prefab, e.commonness) <= 0.0f)) { break; }
(EventPrefab eventPrefab, float commonness, float probability) = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e.prefab, e.commonness)).ToList(), rand);
if (eventPrefab != null && rand.NextDouble() <= probability)
if (unusedEvents.All(e => e.Prefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; }
EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Prefabs.Max(p => CalculateCommonness(p, e.Commonness))).ToList(), rand);
(IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) = subEventPrefab;
if (eventPrefabs != null && rand.NextDouble() <= probability)
{
var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray();
var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray();
var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand);
var newEvent = eventPrefab.CreateInstance();
if (newEvent == null) { continue; }
newEvent.Init(true);
@@ -465,7 +473,7 @@ namespace Barotrauma
selectedEvents.Add(eventSet, new List<Event>());
}
selectedEvents[eventSet].Add(newEvent);
unusedEvents.Remove((eventPrefab, commonness, probability));
unusedEvents.Remove(subEventPrefab);
}
}
}
@@ -480,9 +488,13 @@ namespace Barotrauma
}
else
{
foreach ((EventPrefab eventPrefab, float commonness, float probability) in suitablePrefabs)
foreach ((IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) in suitablePrefabSubsets)
{
if (rand.NextDouble() > probability) { continue; }
var finalPrefabs = eventPrefabs.Where(isPrefabSuitable).ToArray();
var finalPrefabCommonnesses = finalPrefabs.Select(p => p.Commonness).ToArray();
var eventPrefab = ToolBox.SelectWeightedRandom(finalPrefabs, finalPrefabCommonnesses, rand);
var newEvent = eventPrefab.CreateInstance();
if (newEvent == null) { continue; }
newEvent.Init(true);
@@ -866,7 +878,7 @@ namespace Barotrauma
private float CalculateDistanceTraveled()
{
if (level == null) { return 0.0f; }
if (level == null || pathFinder == null) { return 0.0f; }
var refEntity = GetRefEntity();
if (refEntity == null) { return 0.0f; }
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
@@ -40,7 +40,7 @@ namespace Barotrauma
BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty);
Commonness = element.GetAttributeFloat("commonness", 1.0f);
Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", EventType != typeof(ScriptedEvent));
UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false);
UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip");
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
@@ -48,10 +49,10 @@ namespace Barotrauma
List<EventPrefab> eventPrefabs = new List<EventPrefab>(PrefabList);
foreach (var eventSet in List)
{
eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.prefab));
eventPrefabs.AddRange(eventSet.EventPrefabs.SelectMany(ep => ep.Prefabs));
foreach (var childSet in eventSet.ChildSets)
{
eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.prefab));
eventPrefabs.AddRange(childSet.EventPrefabs.SelectMany(ep => ep.Prefabs));
}
}
return eventPrefabs;
@@ -98,7 +99,48 @@ namespace Barotrauma
public readonly Dictionary<string, float> Commonness;
public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs;
public struct SubEventPrefab
{
public SubEventPrefab(string debugIdentifier, string[] prefabIdentifiers, float? commonness, float? probability)
{
EventPrefab tryFindPrefab(string id)
{
var prefab = PrefabList.Find(p => p.Identifier.Equals(id, StringComparison.OrdinalIgnoreCase));
if (prefab is null)
{
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{id}\".");
}
return prefab;
}
this.Prefabs = prefabIdentifiers
.Select(tryFindPrefab)
.Where(p => p != null)
.ToImmutableArray();
this.Commonness = commonness ?? this.Prefabs.Select(p => p.Commonness).Max();
this.Probability = probability ?? this.Prefabs.Select(p => p.Probability).Max();
}
public SubEventPrefab(EventPrefab prefab, float commonness, float probability)
{
Prefabs = prefab.ToEnumerable().ToImmutableArray();
Commonness = commonness;
Probability = probability;
}
public readonly ImmutableArray<EventPrefab> Prefabs;
public readonly float Commonness;
public readonly float Probability;
public void Deconstruct(out IEnumerable<EventPrefab> prefabs, out float commonness, out float probability)
{
prefabs = Prefabs;
commonness = Commonness;
probability = Probability;
}
}
public readonly List<SubEventPrefab> EventPrefabs;
public readonly List<EventSet> ChildSets;
@@ -112,7 +154,7 @@ namespace Barotrauma
{
DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier;
Commonness = new Dictionary<string, float>();
EventPrefabs = new List<(EventPrefab prefab, float commonness, float probability)>();
EventPrefabs = new List<SubEventPrefab>();
ChildSets = new List<EventSet>();
BiomeIdentifier = element.GetAttributeString("biome", string.Empty);
@@ -178,23 +220,20 @@ namespace Barotrauma
//an element with just an identifier = reference to an event prefab
if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase))
{
string identifier = subElement.GetAttributeString("identifier", "");
var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (prefab == null)
{
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\".");
}
else
{
float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness);
float probability = subElement.GetAttributeFloat("probability", prefab.Probability);
EventPrefabs.Add((prefab, commonness, probability));
}
string[] identifiers = subElement.GetAttributeStringArray("identifier", Array.Empty<string>());
float commonness = subElement.GetAttributeFloat("commonness", -1f);
float probability = subElement.GetAttributeFloat("probability", -1f);
EventPrefabs.Add(new SubEventPrefab(
debugIdentifier,
identifiers,
commonness>=0f ? commonness : (float?)null,
probability>=0f ? probability : (float?)null));
}
else
{
var prefab = new EventPrefab(subElement);
EventPrefabs.Add((prefab, prefab.Commonness, prefab.Probability));
EventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability));
}
break;
}
@@ -346,13 +385,13 @@ namespace Barotrauma
{
if (thisSet.ChooseRandom)
{
var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(thisSet.EventPrefabs);
var unusedEvents = thisSet.EventPrefabs.ToList();
for (int i = 0; i < thisSet.EventCount; i++)
{
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab.prefab != null)
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab.Prefabs.Any(p => p != null))
{
AddEvent(stats, eventPrefab.prefab);
AddEvents(stats, eventPrefab.Prefabs);
unusedEvents.Remove(eventPrefab);
}
}
@@ -361,7 +400,7 @@ namespace Barotrauma
{
foreach (var eventPrefab in thisSet.EventPrefabs)
{
AddEvent(stats, eventPrefab.prefab);
AddEvents(stats, eventPrefab.Prefabs);
}
}
foreach (var childSet in thisSet.ChildSets)
@@ -370,6 +409,9 @@ namespace Barotrauma
}
}
static void AddEvents(EventDebugStats stats, IEnumerable<EventPrefab> eventPrefabs)
=> eventPrefabs.ForEach(p => AddEvent(stats, p));
static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab)
{
if (eventPrefab.EventType == typeof(MonsterEvent))
@@ -363,7 +363,7 @@ namespace Barotrauma
#if CLIENT
foreach (Character character in crewCharacters)
{
character.Info.GiveExperience(experienceGain, isMissionExperience: true);
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
}
#else
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
@@ -218,12 +218,12 @@ namespace Barotrauma
return validContainers;
}
private static readonly float[] qualityCommonnesses = new float[Quality.MaxQuality + 1]
private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
{
0.85f,
0.125f,
0.0225f,
0.0025f,
(0, 0.85f),
(1, 0.125f),
(2, 0.0225f),
(3, 0.0025f),
};
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
@@ -243,20 +243,15 @@ namespace Barotrauma
containers.Remove(validContainer.Key);
break;
}
if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; }
int quality = 0;
float qualityCommmonnessSum = qualityCommonnesses.Sum();
float randomNumber = Rand.Range(0f, qualityCommmonnessSum, Rand.RandSync.Server);
for (int k = qualityCommonnesses.Length - 1; k >= 0; k--)
{
if (randomNumber < qualityCommonnesses[k])
{
quality = k;
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.Server);
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine)
{
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
@@ -1,6 +1,4 @@
using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -29,65 +27,6 @@ namespace Barotrauma
private XElement healthData;
public XElement OrderData { get; private set; }
partial void InitProjSpecific(Client client);
public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false)
{
Name = client.Name;
InitProjSpecific(client);
healthData = new XElement("health");
client.Character?.CharacterHealth?.Save(healthData);
if (giveRespawnPenaltyAffliction)
{
var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction();
healthData.Add(new XElement("Affliction",
new XAttribute("identifier", respawnPenaltyAffliction.Identifier),
new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture))));
}
if (client.Character?.Inventory != null)
{
itemData = new XElement("inventory");
Character.SaveInventory(client.Character.Inventory, itemData);
}
OrderData = new XElement("orders");
if (client.Character != null)
{
CharacterInfo.SaveOrderData(client.Character.Info, OrderData);
}
}
public CharacterCampaignData(XElement element)
{
Name = element.GetAttributeString("name", "Unnamed");
ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", "");
string steamID = element.GetAttributeString("steamid", "");
if (!string.IsNullOrEmpty(steamID))
{
ulong.TryParse(steamID, out ulong parsedID);
SteamID = parsedID;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "character":
case "characterinfo":
CharacterInfo = new CharacterInfo(subElement);
break;
case "inventory":
itemData = subElement;
break;
case "health":
healthData = subElement;
break;
case "orders":
OrderData = subElement;
break;
}
}
}
public void Refresh(Character character)
{
healthData = new XElement("health");
@@ -141,10 +141,10 @@ namespace Barotrauma
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
}
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
{
return
base.CanBePutInSlot(itemPrefab, i, condition) &&
base.CanBePutInSlot(itemPrefab, i, condition, quality) &&
(SlotTypes[i] == InvSlotType.Any || slots[i].ItemCount < 1);
}
@@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components
{
string[] allSpecies = SpeciesName.Split(',');
string species = allSpecies.GetRandom().Trim();
Entity.Spawner.AddToSpawnQueue(species, pos);
Entity.Spawner?.AddToSpawnQueue(species, pos);
}
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
{
@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
pos -= sub.Position;
}
Entity.Spawner.AddToSpawnQueue(prefab, pos, item.Submarine);
Entity.Spawner?.AddToSpawnQueue(prefab, pos, item.Submarine);
}
}
}
@@ -642,10 +642,8 @@ namespace Barotrauma.Items.Components
item.Drop(character);
item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false);
}
AttachToWall();
}
AttachToWall();
return true;
}
@@ -291,13 +291,16 @@ namespace Barotrauma.Items.Components
int index = Inventory.FindIndex(containedItem);
if (index >= 0 && index < slotRestrictions.Length)
{
RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem));
if (ri != null)
if (slotRestrictions[index].ContainableItems != null)
{
activeContainedItems.RemoveAll(i => i.Item == containedItem);
foreach (StatusEffect effect in ri.statusEffects)
foreach (var containableItem in slotRestrictions[index].ContainableItems)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken));
if (!containableItem.MatchesItem(containedItem)) { continue; }
foreach (StatusEffect effect in containableItem.statusEffects)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken));
}
}
}
}
@@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components
public OxygenGenerator(Item item, XElement element)
: base(item, element)
{
//randomize update timer so all oxygen generators don't update at the same time
ventUpdateTimer = Rand.Range(0.0f, VentUpdateInterval);
IsActive = true;
}
@@ -78,6 +80,7 @@ namespace Barotrauma.Items.Components
private void GetVents()
{
totalHullVolume = 0.0f;
ventList ??= new List<(Vent vent, float hullVolume)>();
ventList.Clear();
foreach (MapEntity entity in item.linkedTo)
@@ -87,13 +90,14 @@ namespace Barotrauma.Items.Components
Vent vent = linkedItem.GetComponent<Vent>();
if (vent?.Item.CurrentHull == null) { continue; }
totalHullVolume += vent.Item.CurrentHull.Volume;
ventList.Add((vent, vent.Item.CurrentHull.Volume));
}
for (int i = 0; i < ventList.Count; i++)
{
Vent vent = ventList[i].vent;
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 5, ignoreClosedGaps: true))
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: false, searchDepth: 3, ignoreClosedGaps: true))
{
//another vent in the connected hull -> don't add it to this vent's total hull volume
if (ventList.Any(v => v.vent != vent && v.vent.Item.CurrentHull == connectedHull)) { continue; }
@@ -57,7 +57,7 @@ namespace Barotrauma
return true;
}
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null)
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null)
{
if (itemPrefab == null) { return false; }
if (items.Count > 0)
@@ -82,6 +82,11 @@ namespace Barotrauma
if (items.Any(it => !it.IsFullCondition)) { return false; }
}
if (quality.HasValue)
{
if (items[0].Quality != quality.Value) { return false; }
}
if (items[0].Prefab.Identifier != itemPrefab.Identifier ||
items.Count + 1 > itemPrefab.MaxStackSize)
{
@@ -172,6 +177,11 @@ namespace Barotrauma
items.Clear();
}
public void RemoveWhere(Func<Item, bool> predicate)
{
items.RemoveAll(it => predicate(it));
}
public bool Any()
{
return items.Count > 0;
@@ -422,16 +432,16 @@ namespace Barotrauma
return slots[i].CanBePut(item, ignoreCondition);
}
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null)
public bool CanBePut(ItemPrefab itemPrefab, float? condition = null, int? quality = null)
{
for (int i = 0; i < capacity; i++)
{
if (CanBePutInSlot(itemPrefab, i, condition)) { return true; }
if (CanBePutInSlot(itemPrefab, i, condition, quality)) { return true; }
}
return false;
}
public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null)
public virtual bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition = null, int? quality = null)
{
if (i < 0 || i >= slots.Length) { return false; }
return slots[i].CanBePut(itemPrefab, condition);
@@ -503,7 +513,7 @@ namespace Barotrauma
{
return true;
}
return
return
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: true) ||
TrySwapping(i, item, user, createNetworkEvent, swapWholeStack: false);
}
@@ -776,11 +786,17 @@ namespace Barotrauma
{
for (int j = 0; j < capacity; j++)
{
if (slots[j].Contains(item)) { slots[j].RemoveAllItems(); };
if (slots[j].Contains(item))
{
slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
}
}
for (int j = 0; j < otherInventory.capacity; j++)
{
if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault())) { otherInventory.slots[j].RemoveAllItems(); }
if (otherInventory.slots[j].Contains(existingItems.FirstOrDefault()))
{
otherInventory.slots[j].RemoveWhere(it => existingItems.Contains(it) || stackedItems.Contains(it));
}
}
}
@@ -51,11 +51,11 @@ namespace Barotrauma
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i);
}
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition, int? quality = null)
{
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(itemPrefab, i)) { return false; }
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i);
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition, quality) && slots[i].ItemCount < container.GetMaxStackSize(i);
}
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition)
@@ -864,6 +864,16 @@ namespace Barotrauma
foreach (AbyssIsland island in AbyssIslands)
{
island.Area = new Rectangle(borders.Width - island.Area.Right, island.Area.Y, island.Area.Width, island.Area.Height);
foreach (var cell in island.Cells)
{
if (!mirroredSites.Contains(cell.Site))
{
if (cell.Site.Coord.X % GridCellSize < 1.0f &&
cell.Site.Coord.X % GridCellSize >= 0.0f) { cell.Site.Coord.X += 1.0f; }
cell.Site.Coord.X = borders.Width - cell.Site.Coord.X;
mirroredSites.Add(cell.Site);
}
}
}
for (int i = 0; i < ruinPositions.Count; i++)
@@ -1972,7 +1982,7 @@ namespace Barotrauma
ConvertUnits.ToSimUnits(entranceWayPoint.WorldPosition), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) == null;
});
if (closestWp == null) { continue; }
entranceWayPoint.ConnectTo(closestWp);
ConnectWaypoints(entranceWayPoint, closestWp, outSideWaypointInterval);
}
//create a waypoint path from the ruin to the closest tunnel
@@ -1023,7 +1023,12 @@ namespace Barotrauma
#if CLIENT
if (playSound && damageAmount > 0)
{
SoundPlayer.PlayDamageSound(attack.StructureSoundType, damageAmount, worldPosition, tags: Tags);
string damageSound = Prefab.DamageSound;
if (string.IsNullOrWhiteSpace(damageSound))
{
damageSound = attack.StructureSoundType;
}
SoundPlayer.PlayDamageSound(damageSound, damageAmount, worldPosition, tags: Tags);
}
#endif
@@ -167,6 +167,9 @@ namespace Barotrauma
private set { size = value; }
}
[Serialize("", true)]
public string DamageSound { get; private set; }
public Vector2 ScaledSize => size * Scale;
protected Vector2 textureScale = Vector2.One;
@@ -26,6 +26,7 @@ namespace Barotrauma.Networking
UpdateExperience,
UpdateTalents,
UpdateMoney,
UpdatePermanentStats,
}
public readonly Entity Entity;
@@ -1284,68 +1284,66 @@ namespace Barotrauma
}
}
int i = 0;
foreach (int giveExperience in giveExperiences)
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{
targetCharacter?.Info?.GiveExperience(giveExperience);
i++;
}
}
// these effects do not need to be run clientside, as they are replicated from server to clients anyway
if (giveSkills.Any())
{
foreach ((string skillIdentifier, float amount) in giveSkills)
foreach (int giveExperience in giveExperiences)
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{
if (skillIdentifier?.ToLowerInvariant() == "randomskill")
targetCharacter?.Info?.GiveExperience(giveExperience);
}
}
if (giveSkills.Any())
{
foreach ((string skillIdentifier, float amount) in giveSkills)
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter != null && !targetCharacter.Removed)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
if (skillIdentifier?.ToLowerInvariant() == "randomskill")
{
// don't let clients simulate random skill gain
continue;
targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount);
string GetRandomSkill()
{
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom();
}
}
targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount);
string GetRandomSkill()
else
{
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom();
targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount);
}
}
else
{
targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount);
}
}
}
}
if (giveTalentInfos.Any() && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient))
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter?.Info == null) { continue; }
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
if (viableTalents.None()) { continue; }
if (giveTalentInfos.Any())
{
Character targetCharacter = CharacterFromTarget(target);
if (targetCharacter?.Info == null) { continue; }
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
if (giveTalentInfo.GiveRandom)
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
{
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
}
else
{
foreach (string talent in viableTalents)
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
if (viableTalents.None()) { continue; }
if (giveTalentInfo.GiveRandom)
{
targetCharacter.GiveTalent(talent, true);
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
}
else
{
foreach (string talent in viableTalents)
{
targetCharacter.GiveTalent(talent, true);
}
}
}
}