Release v0.15.13.0

This commit is contained in:
Joonas Rikkonen
2021-11-02 13:19:48 +02:00
parent a52a23f8c2
commit 3a35dbfe6c
77 changed files with 509 additions and 224 deletions
@@ -104,6 +104,7 @@ namespace Barotrauma
!pathSteering.CurrentPath.Unreachable &&
(!requireNonDirty || !pathSteering.IsPathDirty);
public bool IsCurrentPathNullOrUnreachable => IsCurrentPathUnreachable || steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath == null;
public bool IsCurrentPathUnreachable => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable;
public bool IsCurrentPathFinished => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Finished;
@@ -431,7 +432,7 @@ namespace Barotrauma
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
float sqrDist = diff.LengthSquared();
bool isClose = sqrDist < MathUtils.Pow2(100);
if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathNullOrUnreachable || IsCurrentPathFinished)
{
// Very close to the target, outside, or at the end of the path -> try to steer through the gap
SteeringManager.Reset();
@@ -3659,15 +3659,15 @@ namespace Barotrauma
{
if (SelectedAiTarget == null) { return; }
Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition);
if (!MathUtils.IsValid(escapeDir))
{
escapeDir = Vector2.UnitY;
}
if (Character.CurrentHull != null && !Character.AnimController.InWater)
{
// Inside
escapeDir = new Vector2(Math.Sign(escapeDir.X), 0);
}
if (!MathUtils.IsValid(escapeDir))
{
escapeDir = Vector2.UnitY;
}
SteeringManager.Reset();
SteeringManager.SteeringManual(deltaTime, escapeDir);
}
@@ -250,7 +250,17 @@ namespace Barotrauma
{
rayEnd += SelectedAiTarget.Entity.Submarine.SimPosition;
}
UseIndoorSteeringOutside = Submarine.PickBody(SimPosition, rayEnd, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null;
IEnumerable<FarseerPhysics.Dynamics.Body> ignoredBodies = null;
if (SelectedAiTarget.Entity is ISpatialEntity spatialTarget)
{
Submarine targetSub = spatialTarget.Submarine;
if (targetSub != null)
{
ignoredBodies = targetSub.PhysicsBody.FarseerBody.ToEnumerable();
}
}
var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
UseIndoorSteeringOutside = obstacle != null;
}
}
else
@@ -340,14 +350,21 @@ namespace Barotrauma
IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave;
}
if (UseIndoorSteeringOutside || IsInsideCave || Character.CurrentHull?.Submarine != null || hasValidPath && IsCloseEnoughToTarget(maxSteeringBuffer) || IsCloseEnoughToTarget(steeringBuffer))
if (UseIndoorSteeringOutside || IsInsideCave || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer))
{
if (steeringManager != insideSteering)
{
insideSteering.Reset();
steeringManager = insideSteering;
}
steeringBuffer += steeringBufferIncreaseSpeed * deltaTime;
if (IsCloseEnoughToTarget(maxSteeringBuffer))
{
steeringBuffer += steeringBufferIncreaseSpeed * deltaTime;
}
else
{
steeringBuffer = minSteeringBuffer;
}
}
else
{
@@ -201,9 +201,9 @@ namespace Barotrauma
currentTarget = target;
Vector2 currentPos = host.SimPosition;
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0;
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0;
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.X) <= 0;
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
if (newPath.Unreachable || newPath.Nodes.None())
{
useNewPath = false;
@@ -220,10 +220,12 @@ namespace Barotrauma
// Use the new path if it has significantly lower cost (don't change the path if it has marginally smaller cost. This reduces navigating backwards due to new path that is calculated from the node just behind us).
float t = (float)currentPath.CurrentIndex / (currentPath.Nodes.Count - 1);
useNewPath = newPath.Cost < currentPath.Cost * MathHelper.Lerp(0.95f, 0, t);
if (!useNewPath && character.Submarine != null)
if (!useNewPath && character.Submarine != null && !character.IsClimbing)
{
// It's possible that the current path was calculated from a start point that is no longer valid.
// Therefore, let's accept also paths with a greater cost than the current, if the current node is much farther than the new start node.
// This is a special case for cases e.g. where the character falls and thus needs a new path.
// Don't do this outside or when climbing ladders, because both cause issues.
useNewPath = Vector2.DistanceSquared(character.WorldPosition, currentPath.CurrentNode.WorldPosition) > Math.Pow(Vector2.Distance(character.WorldPosition, newPath.Nodes.First().WorldPosition) * 3, 2);
}
}
@@ -310,8 +312,7 @@ namespace Barotrauma
if (currentPath.Finished)
{
Vector2 pos2 = host.SimPosition;
if (character != null && character.Submarine == null &&
CurrentPath.Nodes.Count > 0 && CurrentPath.Nodes.Last().Submarine != null)
if (character != null && character.Submarine == null && CurrentPath.Nodes.Count > 0 && CurrentPath.Nodes.Last().Submarine != null)
{
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
}
@@ -323,29 +324,7 @@ namespace Barotrauma
CheckDoorsInPath();
doorsChecked = true;
}
Vector2 pos = host.SimPosition;
if (character != null && CurrentPath.CurrentNode != null)
{
var nodeSub = CurrentPath.CurrentNode.Submarine;
if (nodeSub != null)
{
if (character.Submarine == null)
{
// Going inside
pos -= ConvertUnits.ToSimUnits(nodeSub.Position);
}
else if (character.Submarine != nodeSub)
{
// Different subs
pos -= ConvertUnits.ToSimUnits(nodeSub.Position - character.Submarine.Position);
}
}
else if (character.Submarine != null)
{
// Going outside
pos += ConvertUnits.ToSimUnits(character.Submarine.Position);
}
}
Vector2 pos = host.WorldPosition;
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
// Only humanoids can climb ladders
bool canClimb = character.AnimController is HumanoidAnimController && !character.LockHands;
@@ -384,7 +363,7 @@ namespace Barotrauma
}
if (character.IsClimbing && useLadders)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
Vector2 diff = currentPath.CurrentNode.WorldPosition - pos;
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
if (nextLadderSameAsCurrent)
{
@@ -397,7 +376,7 @@ namespace Barotrauma
float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY;
if (heightFromFloor <= 0.0f)
{
diff.Y = Math.Max(diff.Y, 1.0f);
diff.Y = Math.Max(diff.Y, 100);
}
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
bool isAboveFloor = heightFromFloor > -0.1f;
@@ -430,7 +409,7 @@ namespace Barotrauma
NextNode(!doorsChecked);
}
}
return diff;
return ConvertUnits.ToSimUnits(diff);
}
else if (character.AnimController.InWater)
{
@@ -481,7 +460,7 @@ namespace Barotrauma
{
return Vector2.Zero;
}
return currentPath.CurrentNode.SimPosition - pos;
return ConvertUnits.ToSimUnits(currentPath.CurrentNode.WorldPosition - pos);
}
private void NextNode(bool checkDoors)
@@ -45,6 +45,10 @@ namespace Barotrauma
/// </summary>
public bool AllowStealing { get; set; }
public bool TakeWholeStack { get; set; }
/// <summary>
/// Are variants of the specified item allowed
/// </summary>
public bool AllowVariants { get; set; }
public bool Equip { get; set; }
public bool Wear { get; set; }
@@ -265,7 +269,7 @@ namespace Barotrauma
}
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.followControlledCharacter);
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.isFollowOrderObjective);
bool hasCalledPathFinder = false;
int itemsPerFrame = (int)priority;
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
@@ -408,7 +412,7 @@ namespace Barotrauma
if (ignoredItems.Contains(item)) { return false; };
if (item.Condition < TargetCondition) { return false; }
if (ItemFilter != null && !ItemFilter(item)) { return false; }
return identifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id));
return identifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id));
}
public override void Reset()
@@ -24,7 +24,7 @@ namespace Barotrauma
public Func<float> priorityGetter;
public bool followControlledCharacter;
public bool isFollowOrderObjective;
public bool mimic;
public bool SpeakIfFails { get; set; } = true;
public bool UsePathingOutside { get; set; } = true;
@@ -165,17 +165,10 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (followControlledCharacter)
if (Target == null)
{
if (Character.Controlled != null && HumanAIController.IsFriendly(Character.Controlled))
{
Target = Character.Controlled;
}
if (Target == null)
{
Abandon = true;
return;
}
Abandon = true;
return;
}
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
{
@@ -205,7 +198,7 @@ namespace Barotrauma
}
}
Hull targetHull = GetTargetHull();
if (!followControlledCharacter)
if (!isFollowOrderObjective)
{
// Abandon if going through unsafe paths. Note ignores unsafe nodes when following an order or when the objective is set to ignore unsafe hulls.
bool containsUnsafeNodes = character.IsDismissed && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls
@@ -223,7 +216,7 @@ namespace Barotrauma
{
Abandon = true;
}
else if (HumanAIController.IsCurrentPathUnreachable)
else if (HumanAIController.IsCurrentPathNullOrUnreachable)
{
waitUntilPathUnreachable -= deltaTime;
SteeringManager.Reset();
@@ -317,8 +310,8 @@ namespace Barotrauma
Character targetCharacter = Target as Character;
if (character.AnimController.InWater)
{
if (character.CurrentHull == null ||
followControlledCharacter &&
if (character.CurrentHull == null ||
isFollowOrderObjective &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{
@@ -361,7 +354,7 @@ namespace Barotrauma
}
if (TargetGap != null)
{
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, followControlledCharacter ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, isFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return;
@@ -595,7 +588,7 @@ namespace Barotrauma
{
if (gap.Open < 1) { continue; }
if (gap.Submarine == null) { continue; }
if (!followControlledCharacter)
if (!isFollowOrderObjective)
{
if (gap.FlowTargetHull == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; }
@@ -452,7 +452,7 @@ namespace Barotrauma
extraDistanceWhileSwimming = 100,
AllowGoingOutside = true,
IgnoreIfTargetDead = true,
followControlledCharacter = true,
isFollowOrderObjective = true,
mimic = true,
DialogueIdentifier = "dialogcannotreachplace"
};
@@ -107,7 +107,10 @@ namespace Barotrauma
{
foreach (RelatedItem requiredItem in kvp.Value)
{
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true);
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true)
{
AllowVariants = requiredItem.AllowVariants
};
if (objectiveManager.IsCurrentOrder<AIObjectiveRepairItems>())
{
if (character.IsOnPlayerTeam)
@@ -408,7 +408,7 @@ namespace Barotrauma
}
bool isCompleted =
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold);
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Prefab.IsBuff || a.Strength <= a.Prefab.TreatmentThreshold);
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
{
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.AIController is HumanAIController humanAI)
{
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target) ||
target.CharacterHealth.GetAllAfflictions().All(a => a.Strength <= a.Prefab.TreatmentThreshold))
target.CharacterHealth.GetAllAfflictions().All(a => a.Prefab.IsBuff || a.Strength <= a.Prefab.TreatmentThreshold))
{
return false;
}
@@ -1367,7 +1367,7 @@ namespace Barotrauma
info.Job.GiveJobItems(this, spawnPoint);
}
public void GiveIdCardTags(WayPoint spawnPoint)
public void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent = false)
{
if (info?.Job == null || spawnPoint == null) { return; }
@@ -1378,6 +1378,10 @@ namespace Barotrauma
{
item.AddTag(s);
}
if (createNetworkEvent && (GameMain.NetworkMember?.IsServer ?? false))
{
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ChangeProperty, item.SerializableProperties["tags"] });
}
}
}
@@ -1756,6 +1760,7 @@ namespace Barotrauma
}
else if (IsPlayer)
{
float dist = -1;
Vector2 attackPos = SimPosition + ConvertUnits.ToSimUnits(cursorPosition - Position);
List<Body> ignoredBodies = AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
ignoredBodies.Add(AnimController.Collider.FarseerBody);
@@ -1787,13 +1792,13 @@ namespace Barotrauma
}
else
{
if (body.UserData is IDamageable)
if (body.UserData is IDamageable damageable)
{
attackTarget = (IDamageable)body.UserData;
attackTarget = damageable;
}
else if (body.UserData is Limb)
else if (body.UserData is Limb limb)
{
attackTarget = ((Limb)body.UserData).character;
attackTarget = limb.character;
}
}
}
@@ -1822,7 +1827,20 @@ namespace Barotrauma
var attackLimb = sortedLimbs.FirstOrDefault();
if (attackLimb != null)
{
attackLimb.UpdateAttack(deltaTime, attackPos, attackTarget, out AttackResult attackResult);
if (attackTarget is Character targetCharacter)
{
dist = ConvertUnits.ToDisplayUnits(Vector2.Distance(Submarine.LastPickedPosition, attackLimb.SimPosition));
foreach (Limb limb in targetCharacter.AnimController.Limbs)
{
if (limb.IsSevered || limb.Removed) { continue; }
float tempDist = ConvertUnits.ToDisplayUnits(Vector2.Distance(limb.SimPosition, attackLimb.SimPosition));
if (tempDist < dist)
{
dist = tempDist;
}
}
}
attackLimb.UpdateAttack(deltaTime, attackPos, attackTarget, out AttackResult attackResult, dist);
if (!attackLimb.attack.IsRunning)
{
attackCoolDown = 1.0f;
@@ -3438,7 +3456,7 @@ namespace Barotrauma
var attackResult = targetLimb == null ?
AddDamage(worldPosition, attackAfflictions, attack.Stun, playSound, attackImpulse, out limbHit, attacker, attack.DamageMultiplier * attackData.DamageMultiplier) :
DamageLimb(worldPosition, targetLimb, attackAfflictions, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier * attackData.DamageMultiplier, penetration: penetration + attackData.AddedPenetration);
DamageLimb(worldPosition, targetLimb, attackAfflictions, attack.Stun, playSound, attackImpulse, attacker, attack.DamageMultiplier * attackData.DamageMultiplier, penetration: penetration + attackData.AddedPenetration, shouldImplode: attackData.ShouldImplode);
if (attacker != null)
{
@@ -3558,12 +3576,12 @@ namespace Barotrauma
public void RecordKill(Character target)
{
var abilityCharacter = new AbilityCharacter(target);
var abilityCharacterKill = new AbilityCharacterKill(target, this);
foreach (Character attackerCrewmember in GetFriendlyCrew(this))
{
attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, abilityCharacter);
attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, abilityCharacterKill);
}
CheckTalents(AbilityEffectType.OnKillCharacter, abilityCharacter);
CheckTalents(AbilityEffectType.OnKillCharacter, abilityCharacterKill);
if (!IsOnPlayerTeam) { return; }
if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; }
@@ -3579,7 +3597,7 @@ namespace Barotrauma
GameMain.Config.RecentlyEncounteredCreatures.Add(other.SpeciesName);
}
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f)
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false)
{
if (Removed) { return new AttackResult(); }
@@ -3638,6 +3656,12 @@ namespace Barotrauma
float prevVitality = CharacterHealth.Vitality;
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier, penetration: penetration, attacker: attacker);
CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking);
if (shouldImplode)
{
// Only used by assistant's True Potential talent. Has to run here in order to properly give kill credit when it activates.
Implode();
}
if (attacker != this)
{
OnAttacked?.Invoke(attacker, attackResult);
@@ -3786,7 +3810,7 @@ namespace Barotrauma
}
}
public void Implode(bool isNetworkMessage = false)
private void Implode(bool isNetworkMessage = false)
{
if (CharacterHealth.Unkillable || GodMode || IsDead) { return; }
@@ -4626,4 +4650,16 @@ namespace Barotrauma
AggressiveBehavior = aggressiveBehavior;
}
}
class AbilityCharacterKill : AbilityObject, IAbilityCharacter
{
public AbilityCharacterKill(Character character, Character killer)
{
Character = character;
Killer = killer;
}
public Character Character { get; set; }
public Character Killer { get; set; }
}
}
@@ -291,7 +291,7 @@ namespace Barotrauma
husk.SetStun(5);
yield return new WaitForSeconds(5, false);
#if CLIENT
husk.PlaySound(CharacterSound.SoundType.Idle);
husk?.PlaySound(CharacterSound.SoundType.Idle);
#endif
yield return CoroutineStatus.Success;
}
@@ -844,6 +844,8 @@ namespace Barotrauma
FaceTint = DefaultFaceTint;
BodyTint = Color.TransparentBlack;
if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; }
for (int i = 0; i < limbHealths.Count; i++)
{
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
@@ -466,6 +466,9 @@ namespace Barotrauma
[Serialize(false, true), Editable]
public bool StunImmunity { get; set; }
[Serialize(false, true, description: "Can afflictions affect the face/body tint of the character."), Editable]
public bool ApplyAfflictionColors { get; private set; }
// TODO: limbhealths, sprite?
public HealthParams(XElement element, CharacterParams character) : base(element, character) { }
@@ -14,8 +14,9 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityCharacter)?.Character is Character character)
if (abilityObject is IAbilityCharacter abilityCharacter)
{
if (!(abilityCharacter.Character is Character character)) { return false; }
if (!IsViableTarget(targetTypes, character)) { return false; }
return true;
@@ -0,0 +1,23 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionGeneHarvester : AbilityConditionData
{
public AbilityConditionGeneHarvester(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
{
if (abilityObject is AbilityCharacterKill abilityCharacterKill)
{
return abilityCharacterKill.Killer.Submarine == null || abilityCharacterKill.Killer.TeamID != abilityCharacterKill.Killer.Submarine.TeamID;
}
else
{
LogAbilityConditionError(abilityObject, typeof(AbilityCharacterKill));
return false;
}
}
}
}
@@ -136,6 +136,7 @@ namespace Barotrauma.Abilities
public float DamageMultiplier { get; set; } = 1f;
public float AddedPenetration { get; set; } = 0f;
public List<Affliction> Afflictions { get; set; }
public bool ShouldImplode { get; set; } = false;
public Attack SourceAttack { get; }
public Character Character { get; set; }
public Character Attacker { get; set; }
@@ -37,12 +37,7 @@ namespace Barotrauma.Abilities
attackData.DamageMultiplier += addedDamageMultiplier;
attackData.AddedPenetration += addedPenetration;
if (implode)
{
// might have issues, as the method used to be private and only used for pressure death
attackData.Character?.Implode();
}
attackData.ShouldImplode = implode;
}
else
{
@@ -8,6 +8,7 @@ namespace Barotrauma.Abilities
private readonly AbilityFlags abilityFlag;
private bool lastState;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyFlag(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -6,6 +6,7 @@ namespace Barotrauma.Abilities
class CharacterAbilityModifyReduceAffliction : CharacterAbility
{
float addedAmountMultiplier;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyReduceAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -7,6 +7,7 @@ namespace Barotrauma.Abilities
private readonly string resistanceId;
private readonly float resistance;
bool lastState;
public override bool AllowClientSimulation => true;
// should probably be split to different classes
public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
@@ -7,6 +7,7 @@ namespace Barotrauma.Abilities
private readonly StatTypes statType;
private readonly float value;
bool lastState;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -8,6 +8,7 @@ namespace Barotrauma.Abilities
private readonly StatTypes statType;
private readonly float maxValue;
private float lastValue = 0f;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyStatToFlooding(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -9,6 +9,7 @@ namespace Barotrauma.Abilities
private readonly float statPerLevel;
private readonly int maxLevel;
private float lastValue = 0f;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyStatToLevel(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -10,6 +10,7 @@ namespace Barotrauma.Abilities
private readonly string skillIdentifier;
private readonly bool useAll;
private float lastValue = 0f;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyStatToSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -19,11 +19,10 @@ namespace Barotrauma.Abilities
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
}
protected override void ApplyEffect()
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (Character?.Info is CharacterInfo info)
{
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
}
}
@@ -8,6 +8,7 @@ namespace Barotrauma.Abilities
private float maxValue;
private string afflictionIdentifier;
private float lastValue = 0f;
public override bool AllowClientSimulation => true;
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -45,6 +45,20 @@ namespace Barotrauma
{
wifiComponent.TeamID = TeamTag;
}
var idCard = item.GetComponent<Items.Components.IdCard>();
if (idCard != null)
{
idCard.TeamID = TeamTag;
idCard.SubmarineSpecificID = 0;
}
}
WayPoint subWaypoint =
WayPoint.WayPointList.Find(wp => wp.Submarine == Submarine.MainSub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info.Job?.Prefab) ??
WayPoint.WayPointList.Find(wp => wp.Submarine == Submarine.MainSub && wp.SpawnType == SpawnType.Human);
if (subWaypoint != null)
{
npc.GiveIdCardTags(subWaypoint, createNetworkEvent: true);
}
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, TeamTag, npc.Inventory.AllItems.Select(it => it.ID).ToArray() });
@@ -112,13 +112,13 @@ namespace Barotrauma
}
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();
this.Commonness = commonness ?? this.Prefabs.Select(p => p.Commonness).MaxOrNull() ?? 0.0f;
this.Probability = probability ?? this.Prefabs.Select(p => p.Probability).MaxOrNull() ?? 0.0f;
}
public SubEventPrefab(EventPrefab prefab, float commonness, float probability)
@@ -390,7 +390,7 @@ namespace Barotrauma
private bool DeadOrCaptured(Character character)
{
return character != null && !character.Removed && (character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub));
return character == null || character.Removed || character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub);
}
public override void End()
@@ -171,5 +171,21 @@ namespace Barotrauma.Extensions
}
return false;
}
/// <summary>
/// Returns the maximum element in a given enumerable, or null if there
/// aren't any elements in the input.
/// </summary>
/// <param name="enumerable">Input collection</param>
/// <returns>Maximum element or null</returns>
public static T? MaxOrNull<T>(this IEnumerable<T> enumerable) where T : struct, IComparable<T>
{
T? retVal = null;
foreach (T v in enumerable)
{
if (!retVal.HasValue || v.CompareTo(retVal.Value) > 0) { retVal = v; }
}
return retVal;
}
}
}
@@ -80,6 +80,8 @@ namespace Barotrauma.Items.Components
if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue)
{
#if SERVER
// Set active picker before creating the server event to make sure it's set correctly
activePicker = picker;
item.CreateServerEvent(this);
#endif
pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, abilityPickingTime.Value));
@@ -72,9 +72,9 @@ namespace Barotrauma.Items.Components
{
get
{
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
Matrix bodyTransform = Matrix.CreateRotationZ(item.body == null ? MathHelper.ToRadians(item.Rotation) : item.body.Rotation);
Vector2 flippedPos = barrelPos;
if (item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; }
if (item.body != null && item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; }
return Vector2.Transform(flippedPos, bodyTransform) * item.Scale;
}
}
@@ -202,7 +202,7 @@ namespace Barotrauma.Items.Components
{
float percentageHealth = targetItem.Condition / targetItem.MaxCondition;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
if (percentageHealth < deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab))
{
@@ -347,22 +347,20 @@ namespace Barotrauma.Items.Components
float outCondition = fabricatedItem.OutCondition;
if (i < amountFittingContainer)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition,
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition, quality,
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
});
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * outCondition,
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * outCondition, quality,
onSpawned: (Item spawnedItem) =>
{
onItemSpawned(spawnedItem, tempUser);
spawnedItem.Quality = quality;
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
});
@@ -75,6 +75,7 @@ namespace Barotrauma.Items.Components
float closestDist = float.PositiveInfinity;
foreach (Item targetItem in Item.ItemList)
{
if (targetItem.NonInteractable || targetItem.NonPlayerTeamInteractable || targetItem.HiddenInGame) { continue; }
if (OnlyInOwnSub)
{
if (targetItem.Submarine != item.Submarine) { continue; }
@@ -547,6 +547,11 @@ namespace Barotrauma.Items.Components
//power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power
if (Math.Abs(pt.CurrPowerConsumption) > 0.1f) { return true; }
}
else if (ic is PowerContainer pc)
{
//batteries don't deteriorate if they're not charging/discharging
if (Math.Abs(pc.CurrPowerConsumption) > 0.1f || Math.Abs(pc.CurrPowerOutput) > 0.1f) { return true; }
}
else if (ic is Engine engine)
{
//engines don't deteriorate if they're not running
@@ -555,7 +560,7 @@ namespace Barotrauma.Items.Components
else if (ic is Pump pump)
{
//pumps don't deteriorate if they're not running
if (Math.Abs(pump.FlowPercentage) > 1.0f && pump.IsActive) { return true; }
if (Math.Abs(pump.FlowPercentage) > 1.0f && pump.IsActive && pump.HasPower) { return true; }
}
else if (ic is Reactor reactor)
{
@@ -127,6 +127,8 @@ namespace Barotrauma.Items.Components
if (source == null || target == null || target.Removed ||
(source is Entity sourceEntity && sourceEntity.Removed))
{
source = null;
target = null;
IsActive = false;
return;
}
@@ -225,10 +225,23 @@ namespace Barotrauma.Items.Components
//no electrocution in sub editor
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
var powered = item.GetComponent<Powered>();
if (powered != null)
var reactor = item.GetComponent<Reactor>();
if (reactor != null)
{
//unpowered panels can be rewired without a risk of electrical shock
//reactors that arent generating power atm can be rewired without the risk of electrical shock
if (MathUtils.NearlyEqual(reactor.CurrPowerConsumption, 0.0f)) { return true; }
}
var powerContainer = item.GetComponent<PowerContainer>();
if (powerContainer != null)
{
//empty batteries/supercapacitors can be rewired without the risk of electrical shock
//non-empty ones always have a chance of zapping the user
if (powerContainer.Charge <= 0.0f) { return true; }
}
var powered = item.GetComponent<Powered>();
if (powered != null && powerContainer == null)
{
//unpowered panels can be rewired without the risk of electrical shock
if (powered.Voltage < 0.1f) { return true; }
}
@@ -13,7 +13,7 @@ namespace Barotrauma
struct DeconstructItem
{
public readonly string ItemIdentifier;
//minCondition does <= check, meaning that below or equeal to min condition will be skipped.
//minCondition does <= check, meaning that below or equal to min condition will be skipped.
public readonly float MinCondition;
//maxCondition does > check, meaning that above this max the deconstruct item will be skipped.
public readonly float MaxCondition;
@@ -50,7 +50,6 @@ namespace Barotrauma
ActivateButtonText = element.GetAttributeString("activatebuttontext", string.Empty);
InfoText = element.GetAttributeString("infotext", string.Empty);
InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty);
}
}
@@ -36,7 +36,7 @@ namespace Barotrauma
/// </summary>
public bool ExcludeBroken { get; private set; }
private bool allowVariants = true;
public bool AllowVariants { get; private set; } = true;
public RelationType Type
{
@@ -84,13 +84,13 @@ namespace Barotrauma
{
if (item == null) { return false; }
if (excludedIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; }
return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id) || (allowVariants && item.Prefab.VariantOf?.Identifier == id));
return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id));
}
public bool MatchesItem(ItemPrefab itemPrefab)
{
if (itemPrefab == null) { return false; }
if (excludedIdentifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id))) { return false; }
return Identifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id) || (allowVariants && itemPrefab.VariantOf?.Identifier == id));
return Identifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id) || (AllowVariants && itemPrefab.VariantOf?.Identifier == id));
}
public RelatedItem(string[] identifiers, string[] excludedIdentifiers)
@@ -171,7 +171,7 @@ namespace Barotrauma
new XAttribute("ignoreineditor", IgnoreInEditor),
new XAttribute("excludebroken", ExcludeBroken),
new XAttribute("targetslot", TargetSlot),
new XAttribute("allowvariants", allowVariants));
new XAttribute("allowvariants", AllowVariants));
if (excludedIdentifiers.Length > 0)
{
@@ -235,7 +235,7 @@ namespace Barotrauma
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers)
{
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
allowVariants = element.GetAttributeBool("allowvariants", true)
AllowVariants = element.GetAttributeBool("allowvariants", true)
};
string typeStr = element.GetAttributeString("type", "");
if (string.IsNullOrEmpty(typeStr))
@@ -510,14 +510,14 @@ namespace Barotrauma.MapCreatures.Behavior
List<BallastFloraBranch> list = branches[hull];
if (!list.Any(HasAcidEmitter))
{
BallastFloraBranch randomBranh = branches[hull].GetRandom();
randomBranh.SpawningItem = true;
BallastFloraBranch randomBranch = branches[hull].GetRandom();
randomBranch.SpawningItem = true;
ItemPrefab prefab = ItemPrefab.Find(null, AttackItemPrefab);
Entity.Spawner?.AddToSpawnQueue(prefab, Parent.Position + Offset + randomBranh.Position, Parent.Submarine, null, item =>
Entity.Spawner?.AddToSpawnQueue(prefab, Parent.Position + Offset + randomBranch.Position, Parent.Submarine, onSpawned: item =>
{
randomBranh.AttackItem = item;
randomBranh.SpawningItem = false;
randomBranch.AttackItem = item;
randomBranch.SpawningItem = false;
});
}
@@ -213,9 +213,8 @@ namespace Barotrauma
};
PhysicsBody.FarseerBody.OnCollision += PhysicsBody_OnCollision;
PhysicsBody.FarseerBody.OnSeparation += PhysicsBody_OnSeparation;
PhysicsBody.FarseerBody.SetIsSensor(true);
PhysicsBody.FarseerBody.SetIsSensor(element.GetAttributeBool("sensor", true));
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
PhysicsBody.FarseerBody.BodyType = BodyType.Kinematic;
ColliderRadius = ConvertUnits.ToDisplayUnits(Math.Max(Math.Max(PhysicsBody.radius, PhysicsBody.width / 2.0f), PhysicsBody.height / 2.0f));
@@ -532,10 +531,9 @@ namespace Barotrauma
}
}
if (triggerOnce)
if (triggerOnce && triggeredOnce)
{
if (triggeredOnce) { return; }
if (triggerers.Count > 0) { triggeredOnce = true; }
return;
}
foreach (Entity triggerer in triggerers)
@@ -577,6 +575,12 @@ namespace Barotrauma
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, cameraShake);
}
}
if (triggerOnce && triggerers.Count > 0)
{
PhysicsBody.Enabled = false;
triggeredOnce = true;
}
}
public static void RemoveDistantTriggerers(PhysicsBody physicsBody, HashSet<Entity> triggerers, Vector2 calculateDistanceTo)
@@ -26,6 +26,7 @@ namespace Barotrauma
public readonly Inventory Inventory;
public readonly Submarine Submarine;
public readonly float Condition;
public readonly int Quality;
public bool SpawnIfInventoryFull = true;
public bool IgnoreLimbSlots = false;
@@ -33,28 +34,31 @@ namespace Barotrauma
private readonly Action<Item> onSpawned;
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action<Item> onSpawned, float? condition = null)
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action<Item> onSpawned, float? condition = null, int? quality = null)
{
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Position = worldPosition;
Condition = condition ?? prefab.Health;
Quality = quality ?? 0;
this.onSpawned = onSpawned;
}
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action<Item> onSpawned, float? condition = null)
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action<Item> onSpawned, float? condition = null, int? quality = null)
{
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Position = position;
Submarine = sub;
Condition = condition ?? prefab.Health;
Quality = quality ?? 0;
this.onSpawned = onSpawned;
}
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action<Item> onSpawned, float? condition = null)
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action<Item> onSpawned, float? condition = null, int? quality = null)
{
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Inventory = inventory;
Condition = condition ?? prefab.Health;
Quality = quality ?? 0;
this.onSpawned = onSpawned;
}
@@ -73,7 +77,8 @@ namespace Barotrauma
}
spawnedItem = new Item(Prefab, Vector2.Zero, null)
{
Condition = Condition
Condition = Condition,
Quality = Quality
};
var slot = Slot != InvSlotType.None ? Slot.ToEnumerable() : spawnedItem.AllowedSlots;
if (!Inventory.Owner.Removed && !Inventory.TryPutItem(spawnedItem, null, slot))
@@ -94,7 +99,11 @@ namespace Barotrauma
}
else
{
spawnedItem = new Item(Prefab, Position, Submarine);
spawnedItem = new Item(Prefab, Position, Submarine)
{
Condition = Condition,
Quality = Quality
};
}
return spawnedItem;
}
@@ -241,7 +250,7 @@ namespace Barotrauma
return "EntitySpawner";
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, Action<Item> onSpawned = null)
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
@@ -251,10 +260,10 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition));
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition, quality));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, Action<Item> onSpawned = null)
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
@@ -264,10 +273,10 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition));
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition, quality));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, Action<Item> onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false, InvSlotType slot = InvSlotType.None)
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, int? quality = null, Action<Item> onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false, InvSlotType slot = InvSlotType.None)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
@@ -277,7 +286,7 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition)
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition, quality)
{
SpawnIfInventoryFull = spawnIfInventoryFull,
IgnoreLimbSlots = ignoreLimbSlots,
@@ -395,10 +404,6 @@ namespace Barotrauma
{
CreateNetworkEventProjSpecific(spawnedEntity, false);
}
if (spawnedEntity is Item item)
{
item.Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition;
}
entitySpawnInfo.OnSpawned(spawnedEntity);
}
}
@@ -244,7 +244,7 @@ namespace Barotrauma
cam.TargetPos = targetPos;
}
cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null);
cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory);
#endif
foreach (Submarine sub in Submarine.Loaded)