Unstable 0.16.2.0

This commit is contained in:
Markus Isberg
2022-02-02 00:55:02 +09:00
parent b259af5911
commit 6814a11520
106 changed files with 1634 additions and 793 deletions
@@ -531,8 +531,7 @@ namespace Barotrauma
selectedTargetingParams = targetingParams;
State = targetingParams.State;
}
if (SelectedAiTarget?.Entity != null &&
(LatchOntoAI == null || !LatchOntoAI.IsAttached || wallTarget != null) &&
if ((LatchOntoAI == null || !LatchOntoAI.IsAttached || wallTarget != null) &&
(State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive))
{
UpdateWallTarget(requiredHoleCount);
@@ -646,7 +645,7 @@ namespace Barotrauma
}
else
{
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
run = isBeingChased || squaredDistance < Math.Pow(halfReactDistance, 2);
State = AIState.Escape;
avoidTimer = AIParams.AvoidTime * 0.5f * Rand.Range(0.75f, 1.25f);
}
@@ -674,7 +673,8 @@ namespace Barotrauma
Character c = a.Character;
if (c.IsDead || c.Removed) { return false; }
if (!Character.IsFriendly(c)) { return true; }
// Only apply the threshold to friendly characters
if (!c.IsPlayer) { return false; }
// Only apply the threshold to players
return a.Damage >= selectedTargetingParams.Threshold;
}
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
@@ -686,6 +686,8 @@ namespace Barotrauma
// Attack the character that attacked the target we are protecting
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
SelectTarget(attacker.AiTarget);
State = AIState.Attack;
UpdateWallTarget(requiredHoleCount);
return;
}
}
@@ -2270,6 +2272,10 @@ namespace Barotrauma
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
{
State = AIState.Idle;
if (Character.SelectedCharacter != null)
{
Character.DeselectCharacter();
}
return;
}
if (SelectedAiTarget.Entity is Character || SelectedAiTarget.Entity is Item)
@@ -2285,7 +2291,16 @@ namespace Barotrauma
Vector2 attackSimPosition = Character.GetRelativeSimPosition(SelectedAiTarget.Entity);
Vector2 limbDiff = attackSimPosition - mouthPos;
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2);
if (limbDiff.LengthSquared() < extent * extent)
bool tooFar = Character.InWater ? limbDiff.LengthSquared() > extent * extent : limbDiff.X > extent;
if (tooFar)
{
steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), 2);
if (Character.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
else
{
if (SelectedAiTarget.Entity is Character targetCharacter)
{
@@ -2301,11 +2316,9 @@ namespace Barotrauma
{
item.body.LinearVelocity *= 0.9f;
item.body.LinearVelocity -= limbDiff * 0.25f;
bool wasBroken = item.Condition <= 0.0f;
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.1f), deltaTime);
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
if (item.Condition <= 0.0f)
{
if (!wasBroken) { PetBehavior?.OnEat(item); }
@@ -2317,14 +2330,6 @@ namespace Barotrauma
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(limbDiff) * 3);
Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos);
}
else
{
steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), 2);
if (Character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
}
else
{
@@ -2392,7 +2397,7 @@ namespace Barotrauma
selectedTargetMemory = null;
targetingParams = null;
bool isAnyTargetClose = false;
bool isBeingChased = IsBeingChased;
foreach (AITarget aiTarget in AITarget.List)
{
if (aiTarget.InDetectable) { continue; }
@@ -2515,11 +2520,12 @@ namespace Barotrauma
// Ignore inner walls when outside (walltargets still work)
continue;
}
valueModifier = 1;
if (!Character.AnimController.CanEnterSubmarine && IsWallDisabled(s))
{
continue;
}
// Prefer weaker walls (200 is the default for normal hull walls)
valueModifier = 200f / s.MaxHealth;
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
@@ -2674,6 +2680,10 @@ namespace Barotrauma
}
}
}
if (targetParams.State == AIState.Eat && Character.Params.Health.HealthRegenerationWhenEating > 0)
{
valueModifier *= MathHelper.Lerp(1f, 0.1f, Character.HealthPercentage / 100f);
}
valueModifier *= targetParams.Priority;
if (valueModifier == 0.0f) { continue; }
if (targetingTag != "decoy")
@@ -2720,9 +2730,21 @@ namespace Barotrauma
// Stick to the current target
valueModifier *= 1.1f;
}
if (!isBeingChased)
{
if (targetParams.State == AIState.Avoid || targetParams.State == AIState.PassiveAggressive || targetParams.State == AIState.Aggressive)
{
float reactDistance = targetParams.ReactDistance;
if (reactDistance > 0 && reactDistance < dist)
{
// The target is too far and should be ignored.
continue;
}
}
}
//if the target is very close, the distance doesn't make much difference
// -> just ignore the distance and attack whatever has the highest priority
// -> just ignore the distance and target whatever has the highest priority
dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null)
@@ -2801,9 +2823,9 @@ namespace Barotrauma
{
if (Character.CurrentHull != null && targetCharacter.CurrentHull != Character.CurrentHull)
{
if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe)
if (targetParams.State == AIState.Follow || targetParams.State == AIState.Protect || targetParams.State == AIState.Observe || targetParams.State == AIState.Eat)
{
// Ignore targets that cannot see
// Ignore targets that cannot be seen
if (!VisibleHulls.Contains(targetCharacter.CurrentHull))
{
continue;
@@ -3295,7 +3317,7 @@ namespace Barotrauma
{
if (priority.HasValue)
{
targetParams.Priority = priority.Value;
targetParams.Priority = Math.Max(targetParams.Priority, priority.Value);
}
targetParams.State = state;
if (!modifiedParams.ContainsKey(tag))
@@ -3314,6 +3336,7 @@ namespace Barotrauma
/// <summary>
/// Temporarily changes the predefined state for a target. Eg. Idle -> Attack.
/// Note: does not change the current AIState!
/// </summary>
private void ChangeTargetState(Character target, AIState state, float? priority = null)
{
@@ -3335,14 +3358,14 @@ namespace Barotrauma
// --> Target the submarine too.
if (target.Submarine != null && Character.Submarine == null && (canAttackDoors || canAttackWalls))
{
ChangeParams("room", state, priority * 0.1f);
ChangeParams("room", state, priority / 2);
if (canAttackWalls)
{
ChangeParams("wall", state, priority * 0.1f);
ChangeParams("wall", state, priority / 2);
}
if (canAttackDoors)
{
ChangeParams("door", state, priority * 0.1f);
ChangeParams("door", state, priority / 2);
}
}
ChangeParams("provocative", state, priority, onlyExisting: true);
@@ -3394,9 +3417,15 @@ namespace Barotrauma
private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1, bool checkVisibility = false)
{
if (target?.Entity == null) { return false; }
bool insideSightRange;
bool insideSoundRange;
checkVisibility = checkVisibility && Character.Submarine != null && target.Entity.Submarine == Character.Submarine;
if (checkVisibility)
{
// We only want to check the visibility when the target is in ruins/wreck/similiar place where sneaking should be possible.
// When the monsters attack the player sub, they wall hack so that they can be more aggressive.
checkVisibility = target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
}
if (dist > 0)
{
insideSightRange = IsInRange(dist, target.SightRange, Sight);
@@ -565,7 +565,7 @@ namespace Barotrauma
Character.AnimController.HeadInWater ||
Character.Submarine == null ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
!ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>() && ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOn) ||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOnAlsoWhenInactive) ||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10;
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
@@ -878,7 +878,7 @@ namespace Barotrauma
foreach (Character target in Character.CharacterList)
{
if (target.CurrentHull != hull || !target.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
if (AIObjectiveFightIntruders.IsValidTarget(target, Character, false))
{
if (!target.IsArrested && AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
{
@@ -1772,7 +1772,7 @@ namespace Barotrauma
foreach (var enemy in Character.CharacterList)
{
if (enemy.CurrentHull != hull) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(enemy, character))
if (AIObjectiveFightIntruders.IsValidTarget(enemy, character, false))
{
AddTargets<AIObjectiveFightIntruders, Character>(character, enemy);
}
@@ -9,10 +9,10 @@ namespace Barotrauma
{
class IndoorsSteeringManager : SteeringManager
{
private PathFinder pathFinder;
private readonly PathFinder pathFinder;
private SteeringPath currentPath;
private bool canOpenDoors;
private readonly bool canOpenDoors;
public bool CanBreakDoors { get; set; }
private bool ShouldBreakDoor(Door door) =>
@@ -20,7 +20,7 @@ namespace Barotrauma
!door.Item.Indestructible && !door.Item.InvulnerableToDamage &&
(door.Item.Submarine == null || door.Item.Submarine.TeamID != character.TeamID);
private Character character;
private readonly Character character;
private Vector2 currentTarget;
@@ -77,8 +77,10 @@ namespace Barotrauma
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) : base(host)
{
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
pathFinder.GetNodePenalty = GetNodePenalty;
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true)
{
GetNodePenalty = GetNodePenalty
};
this.canOpenDoors = canOpenDoors;
this.CanBreakDoors = canBreakDoors;
@@ -508,6 +510,16 @@ namespace Barotrauma
canAccessButtons = true;
}
}
foreach (var linked in door.Item.linkedTo)
{
if (!(linked is Item linkedItem)) { continue; }
var button = linkedItem.GetComponent<Controller>();
if (button == null) { continue; }
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
{
canAccessButtons = true;
}
}
return canAccessButtons || door.IsOpen || ShouldBreakDoor(door);
}
}
@@ -30,6 +30,8 @@ namespace Barotrauma
public virtual bool ConcurrentObjectives => false;
public virtual bool KeepDivingGearOn => false;
public virtual bool KeepDivingGearOnAlsoWhenInactive => false;
/// <summary>
/// There's a separate property for diving suit and mask: KeepDivingGearOn.
/// </summary>
@@ -12,10 +12,12 @@ namespace Barotrauma
protected override float TargetUpdateTimeMultiplier => 0.2f;
public bool TargetCharactersInOtherSubs { get; set; }
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }
protected override bool Filter(Character target) => IsValidTarget(target, character);
protected override bool Filter(Character target) => IsValidTarget(target, character, TargetCharactersInOtherSubs);
protected override IEnumerable<Character> GetList() => Character.CharacterList;
@@ -54,7 +56,7 @@ namespace Barotrauma
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
=> HumanAIController.RemoveTargets<AIObjectiveFightIntruders, Character>(character, target);
public static bool IsValidTarget(Character target, Character character)
public static bool IsValidTarget(Character target, Character character, bool targetCharactersInOtherSubs)
{
if (target == null || target.Removed) { return false; }
if (target.IsDead) { return false; }
@@ -65,7 +67,7 @@ namespace Barotrauma
if (target.CurrentHull == null) { return false; }
if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (target.IsArrested) { return false; }
return true;
@@ -629,18 +629,20 @@ namespace Barotrauma
{
get
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
if (character.IsClimbing)
{
// Climbing a ladder
if (Target.WorldPosition.Y > character.WorldPosition.Y)
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder)
{
// The target is still above us
return false;
}
if (!character.AnimController.IsAboveFloor)
{
// Going through a hatch
return false;
if (Target.WorldPosition.Y > character.WorldPosition.Y)
{
// The target is still above us
return false;
}
if (!character.AnimController.IsAboveFloor)
{
// Going through a hatch
return false;
}
}
}
if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
@@ -422,7 +422,7 @@ namespace Barotrauma
case "wait":
newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
{
AllowGoingOutside = character.Submarine == null || (order.TargetSpatialEntity != null && character.Submarine != order.TargetSpatialEntity.Submarine)
AllowGoingOutside = true
};
break;
case "return":
@@ -468,6 +468,12 @@ namespace Barotrauma
case "fightintruders":
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
break;
case "assaultenemy":
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier)
{
TargetCharactersInOtherSubs = true
};
break;
case "steer":
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "prepare";
public override string DebugTag => $"{Identifier}";
public override bool KeepDivingGearOn => true;
public override bool KeepDivingGearOnAlsoWhenInactive => true;
public override bool PrioritizeIfSubObjectivesActive => true;
private AIObjectiveGetItem getSingleItemObjective;
@@ -155,6 +155,9 @@ namespace Barotrauma
public OrderCategory? Category { get; private set; }
//legacy support
/// <summary>
/// If defined, the order can only be quick-assigned to characters with these jobs. Or if it's a report, the icon will only be displayed to characters with these jobs.
/// </summary>
public readonly string[] AppropriateJobs;
public readonly string[] Options;
public readonly string[] HiddenOptions;
@@ -177,6 +180,10 @@ namespace Barotrauma
public bool IsPrefab { get; private set; }
public readonly bool MustManuallyAssign;
public readonly bool AutoDismiss;
/// <summary>
/// If defined, the order will be quick-assigned to characters with these jobs before characters with other jobs.
/// </summary>
public string[] PreferredJobs { get; }
public readonly OrderTarget TargetPosition;
@@ -327,6 +334,7 @@ namespace Barotrauma
ControllerTags = orderElement.GetAttributeStringArray("controllertags", new string[0]);
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
PreferredJobs = orderElement.GetAttributeStringArray("preferredjobs", new string[0]);
Options = orderElement.GetAttributeStringArray("options", new string[0]);
HiddenOptions = orderElement.GetAttributeStringArray("hiddenoptions", new string[0]);
AllOptions = Options.Concat(HiddenOptions).ToArray();
@@ -407,7 +415,7 @@ namespace Barotrauma
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
IsIgnoreOrder = Identifier == "ignorethis" || Identifier == "unignorethis";
DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Operate || Category == OrderCategory.Movement);
AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100);
ColoredWhenControllingGiver = orderElement.GetAttributeBool("coloredwhencontrollinggiver", false);
DisplayGiverInTooltip = orderElement.GetAttributeBool("displaygiverintooltip", false);
@@ -435,6 +443,7 @@ namespace Barotrauma
ControllerTags = prefab.ControllerTags;
TargetAllCharacters = prefab.TargetAllCharacters;
AppropriateJobs = prefab.AppropriateJobs;
PreferredJobs = prefab.PreferredJobs;
FadeOutTime = prefab.FadeOutTime;
MustSetTarget = prefab.MustSetTarget;
CanBeGeneralized = prefab.CanBeGeneralized;
@@ -446,8 +455,9 @@ namespace Barotrauma
Hidden = prefab.Hidden;
IgnoreAtOutpost = prefab.IgnoreAtOutpost;
AssignmentPriority = prefab.AssignmentPriority;
ColoredWhenControllingGiver = prefab.ColoredWhenControllingGiver;
AutoDismiss = prefab.AutoDismiss;
DisplayGiverInTooltip = prefab.DisplayGiverInTooltip;
ColoredWhenControllingGiver = prefab.ColoredWhenControllingGiver;
OrderGiver = orderGiver;
TargetEntity = targetEntity;
@@ -488,30 +498,37 @@ namespace Barotrauma
WallSectionIndex = sectionIndex;
TargetType = OrderTargetType.WallSection;
}
public bool HasAppropriateJob(Character character)
{
if (character.Info == null || character.Info.Job == null) { return false; }
if (character.Info.Job.Prefab.AppropriateOrders.Any(appropriateOrderId => Identifier == appropriateOrderId)) { return true; }
if (!JobPrefab.Prefabs.Any(jp => jp.AppropriateOrders.Contains(Identifier)) &&
(AppropriateJobs == null || AppropriateJobs.Length == 0))
private bool HasSpecifiedJob(Character character, string[] jobs)
{
if (jobs == null || jobs.Length == 0) { return false; }
string jobIdentifier = character?.Info?.Job?.Prefab?.Identifier;
if (string.IsNullOrEmpty(jobIdentifier)) { return false; }
for (int i = 0; i < jobs.Length; i++)
{
return true;
}
for (int i = 0; i < AppropriateJobs.Length; i++)
{
if (character.Info.Job.Prefab.Identifier.Equals(AppropriateJobs[i], StringComparison.OrdinalIgnoreCase)) { return true; }
if (jobIdentifier.Equals(jobs[i], StringComparison.OrdinalIgnoreCase)) { return true; }
}
return false;
}
public bool HasAppropriateJob(Character character) => HasSpecifiedJob(character, AppropriateJobs);
public bool HasPreferredJob(Character character) => HasSpecifiedJob(character, PreferredJobs);
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", bool isNewOrder = true)
{
if (!TargetAllCharacters && !isNewOrder && Identifier != "dismissed")
{
// Use special dialogue when we're rearranging character orders
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
if (!givingOrderToSelf)
{
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
}
else
{
// Say nothing when rearranging the orders of the character you're controlling
return string.Empty;
}
}
string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}.{Identifier}";
if (!string.IsNullOrEmpty(orderOption))
@@ -346,7 +346,12 @@ namespace Barotrauma
Vector2 limbDiff = attackSimPosition - mouthPos;
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 1);
if (limbDiff.LengthSquared() < extent * extent)
bool tooFar = character.InWater ? limbDiff.LengthSquared() > extent * extent : limbDiff.X > extent;
if (tooFar)
{
character.SelectedCharacter = null;
}
else
{
//pull the target character to the position of the mouth
//(+ make the force fluctuate to waggle the character a bit)
@@ -383,7 +388,7 @@ namespace Barotrauma
mouthLimb.body.ApplyTorque(-force * 50);
}
if (Character.CanEat)
if (Character.CanEat && target.IsDead)
{
var jaw = GetLimb(LimbType.Jaw);
if (jaw != null)
@@ -432,10 +437,6 @@ namespace Barotrauma
}
}
}
else
{
character.SelectedCharacter = null;
}
}
public bool reverse;
@@ -533,7 +533,7 @@ namespace Barotrauma
bool onSlope = Math.Abs(movement.X) > 0.01f && Math.Abs(floorNormal.X) > 0.1f && Math.Sign(floorNormal.X) != Math.Sign(movement.X);
bool movingHorizontally = !MathUtils.NearlyEqual(targetMovement.X, 0.0f);
bool movingHorizontally = !MathUtils.NearlyEqual(TargetMovement.X, 0.0f);
if (Stairs != null || onSlope)
{
@@ -304,7 +304,27 @@ namespace Barotrauma
public abstract float? TorsoPosition { get; }
public abstract float? TorsoAngle { get; }
public float ImpactTolerance => RagdollParams.ImpactTolerance;
float? impactTolerance;
public float ImpactTolerance
{
get
{
if (impactTolerance == null)
{
impactTolerance = RagdollParams.ImpactTolerance;
if (character.Params.VariantFile != null)
{
float? tolerance = character.Params.VariantFile.Root.GetChildElement("ragdoll")?.GetAttributeFloat("impacttolerance", impactTolerance.Value);
if (tolerance.HasValue)
{
impactTolerance = tolerance;
}
}
}
return impactTolerance.Value;
}
}
public bool Draggable => RagdollParams.Draggable;
public bool CanEnterSubmarine => RagdollParams.CanEnterSubmarine;
@@ -1833,7 +1853,7 @@ namespace Barotrauma
float sin = (float)Math.Sin(mouthLimb.Rotation);
Vector2 bodySize = mouthLimb.body.GetSize();
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * mouthLimb.Scale * RagdollParams.LimbScale;
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos);
}
public Vector2 GetColliderBottom()
@@ -101,11 +101,21 @@ namespace Barotrauma
[Serialize(false, true, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
public bool Retreat { get; private set; }
private float _range;
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
public float Range { get; set; }
public float Range
{
get => _range * RangeMultiplier;
set => _range = value;
}
private float _damageRange;
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target to do damage. In distance-based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
public float DamageRange { get; set; }
public float DamageRange
{
get => _damageRange * RangeMultiplier;
set => _damageRange = value;
}
[Serialize(0.25f, true, description: "An approximation of the attack duration. Effectively defines the time window in which the hit can be registered. If set to too low value, it's possible that the attack won't hit the target in time."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, DecimalCount = 2)]
public float Duration { get; private set; }
@@ -145,10 +155,20 @@ namespace Barotrauma
public float Penetration { get; private set; }
/// <summary>
/// Currently only used with variants. Used for multiplying all the damage.
/// Used for multiplying all the damage.
/// </summary>
public float DamageMultiplier { get; set; } = 1;
/// <summary>
/// Used for multiplying all the ranges.
/// </summary>
public float RangeMultiplier { get; set; } = 1;
/// <summary>
/// Used for multiplying the physics forces.
/// </summary>
public float ImpactMultiplier { get; set; } = 1;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float LevelWallDamage { get; set; }
@@ -1249,6 +1249,10 @@ namespace Barotrauma
Info.HairElement?.Elements("sprite").ForEach(s => head.OtherWearables.Add(new WearableSprite(s, WearableType.Hair)));
#if CLIENT
if (info.Head?.HairWithHatElement != null)
{
head.HairWithHatSprite = new WearableSprite(info.Head?.HairWithHatElement.Element("sprite"), WearableType.Hair);
}
head.EnableHuskSprite = Params.Husk;
head.LoadHerpesSprite();
head.UpdateWearableTypesToHide();
@@ -3499,7 +3503,7 @@ namespace Barotrauma
Limb limbHit = targetLimb;
float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime;
float attackImpulse = attack.TargetImpulse + attack.TargetForce * attack.ImpactMultiplier * deltaTime;
AbilityAttackData attackData = new AbilityAttackData(attack, this);
if (attacker != null)
@@ -3537,7 +3541,7 @@ namespace Barotrauma
}
if (limbHit == null) { return new AttackResult(); }
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld * attack.ImpactMultiplier;
if (attacker != null)
{
forceWorld.X *= attacker.AnimController.Dir;
@@ -3845,40 +3849,49 @@ namespace Barotrauma
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
statusEffect.Apply(actionType, deltaTime, this, targets);
}
else
else if (statusEffect.targetLimbs != null)
{
statusEffect.Apply(actionType, deltaTime, this, this);
if (statusEffect.targetLimbs != null)
foreach (var limbType in statusEffect.targetLimbs)
{
foreach (var limbType in statusEffect.targetLimbs)
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
// Target all matching limbs
foreach (var limb in AnimController.Limbs)
{
// Target all matching limbs
foreach (var limb in AnimController.Limbs)
if (limb.IsSevered) { continue; }
if (limb.type == limbType)
{
if (limb.IsSevered) { continue; }
if (limb.type == limbType)
{
statusEffect.Apply(actionType, deltaTime, this, limb);
}
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
{
// Target just the first matching limb
Limb limb = AnimController.GetLimb(limbType);
if (limb != null)
{
// Target just the first matching limb
Limb limb = AnimController.GetLimb(limbType);
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
{
// Target just the last matching limb
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
if (limb != null)
{
// Target just the last matching limb
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
statusEffect.sourceBody = limb.body;
statusEffect.Apply(actionType, deltaTime, this, limb);
}
}
}
}
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
{
statusEffect.Apply(actionType, deltaTime, this, this);
}
}
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
{
@@ -43,6 +43,7 @@ namespace Barotrauma
public int FaceAttachmentIndex { get; set; } = -1;
public XElement HairElement { get; set; }
public XElement HairWithHatElement { get; set; }
public XElement BeardElement { get; set; }
public XElement MoustacheElement { get; set; }
public XElement FaceAttachment { get; set; }
@@ -1125,6 +1126,16 @@ namespace Barotrauma
Head.HairElement = GetRandomElement(hairs);
Head.HairIndex = hairs.IndexOf(Head.HairElement);
}
if (Head.HairElement != null)
{
int thisHairIndex = hairs.IndexOf(head.HairElement);
int hairWithHatIndex = head.HairElement.GetAttributeInt("replacewhenwearinghat", thisHairIndex);
if (thisHairIndex != hairWithHatIndex && hairWithHatIndex > -1 && hairWithHatIndex < hairs.Count)
{
head.HairWithHatElement = hairs[hairWithHatIndex];
}
}
if (IsValidIndex(Head.BeardIndex, beards))
{
Head.BeardElement = beards[Head.BeardIndex];
@@ -649,6 +649,8 @@ namespace Barotrauma
if (attackElement != null)
{
attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f);
attack.RangeMultiplier = attackElement.GetAttributeFloat("rangemultiplier", 1f);
attack.ImpactMultiplier = attackElement.GetAttributeFloat("impactmultiplier", 1f);
}
}
break;
@@ -14,9 +14,9 @@ namespace Barotrauma
NotDefined = 0,
Walk = 1,
Run = 2,
Crouch = 3,
SwimSlow = 4,
SwimFast = 5
SwimSlow = 3,
SwimFast = 4,
Crouch = 5
}
abstract class GroundedMovementParams : AnimationParams
@@ -42,7 +42,7 @@ namespace Barotrauma
}
private float skillIncreasePerRepairedStructureDamage;
[Serialize(0.005f, true)]
[Serialize(0.0025f, true)]
public float SkillIncreasePerRepairedStructureDamage
{
get { return skillIncreasePerRepairedStructureDamage * GetCurrentSkillGainMultiplier(); }
@@ -521,6 +521,7 @@ namespace Barotrauma
if (targetCharacter == null) { return; }
targetCharacter.GodMode = !targetCharacter.GodMode;
NewMessage((targetCharacter.GodMode ? "Enabled godmode on " : "Disabled godmode on " + targetCharacter.Name), Color.White);
},
() =>
{
@@ -1042,6 +1043,20 @@ namespace Barotrauma
throw new Exception("crash command issued");
}));
commands.Add(new Command("fastforward", "fastforward [seconds]: Fast forwards the game by x seconds. Note that large numbers may cause a long freeze.", (string[] args) =>
{
float seconds = 0;
if (args.Length > 0) { float.TryParse(args[0], out seconds); }
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i < seconds * Timing.FixedUpdateRate; i++)
{
Screen.Selected?.Update(Timing.Step);
}
sw.Stop();
NewMessage($"Fast-forwarded by {seconds} seconds (took {sw.ElapsedMilliseconds / 1000.0f} s).");
}));
commands.Add(new Command("removecharacter", "removecharacter [character name]: Immediately deletes the specified character.", (string[] args) =>
{
if (args.Length == 0) { return; }
@@ -351,7 +351,7 @@ namespace Barotrauma
}
}
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter = null)
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter = null, bool fullLog = false)
{
List<string> debugLines = new List<string>();
@@ -365,7 +365,7 @@ namespace Barotrauma
stats.Add(newStats);
}
debugLines.Add($"Event stats ({eventSet.DebugIdentifier}): ");
LogEventStats(stats, debugLines);
LogEventStats(stats, debugLines, fullLog);
}
return debugLines;
@@ -415,14 +415,19 @@ namespace Barotrauma
if (eventPrefab.EventType == typeof(MonsterEvent) && eventPrefab.TryCreateInstance(out MonsterEvent monsterEvent))
{
if (filter != null && !filter(monsterEvent)) { return; }
float spawnProbability = monsterEvent.Prefab.Probability;
if (Rand.Value() > spawnProbability) { return; }
string character = monsterEvent.speciesName;
int count = Rand.Range(monsterEvent.MinAmount, monsterEvent.MaxAmount + 1);
if (count <= 0) { return; }
if (!stats.MonsterCounts.ContainsKey(character)) { stats.MonsterCounts[character] = 0; }
string character = monsterEvent.speciesName;
if (stats.MonsterCounts.TryGetValue(character, out int currentCount))
{
if (currentCount >= monsterEvent.MaxAmountPerLevel) { return; }
}
else
{
stats.MonsterCounts[character] = 0;
}
stats.MonsterCounts[character] += count;
var aiElement = CharacterPrefab.FindBySpeciesName(character)?.XDocument?.Root?.GetChildElement("ai");
@@ -433,7 +438,7 @@ namespace Barotrauma
}
}
static void LogEventStats(List<EventDebugStats> stats, List<string> debugLines)
static void LogEventStats(List<EventDebugStats> stats, List<string> debugLines, bool fullLog)
{
if (stats.Count == 0 || stats.All(s => s.MonsterCounts.Values.Sum() == 0))
{
@@ -442,28 +447,42 @@ namespace Barotrauma
}
else
{
var allMonsters = new Dictionary<string, int>();
foreach (var stat in stats)
{
foreach (var monster in stat.MonsterCounts)
{
if (!allMonsters.TryAdd(monster.Key, monster.Value))
{
allMonsters[monster.Key] += monster.Value;
}
}
}
allMonsters = allMonsters.OrderBy(m => m.Key).ToDictionary(m => m.Key, m => m.Value);
stats.Sort((s1, s2) => s1.MonsterCounts.Values.Sum().CompareTo(s2.MonsterCounts.Values.Sum()));
debugLines.Add($" Minimum monster count: {stats.First().MonsterCounts.Values.Sum()}");
debugLines.Add($" {LogMonsterCounts(stats.First())}");
debugLines.Add($" Median monster count: {stats[stats.Count / 2].MonsterCounts.Values.Sum()}");
debugLines.Add($" {LogMonsterCounts(stats[stats.Count / 2])}");
debugLines.Add($" Maximum monster count: {stats.Last().MonsterCounts.Values.Sum()}");
debugLines.Add($" {LogMonsterCounts(stats.Last())}");
debugLines.Add($" Average monster count: {StringFormatter.FormatZeroDecimal((float)stats.Average(s => s.MonsterCounts.Values.Sum()))}");
debugLines.Add($" ");
debugLines.Add($" Average monster count: {StringFormatter.FormatZeroDecimal((float)stats.Average(s => s.MonsterCounts.Values.Sum()))} (Min: {stats.First().MonsterCounts.Values.Sum()}, Max: {stats.Last().MonsterCounts.Values.Sum()})");
debugLines.Add($" {LogMonsterCounts(allMonsters, divider: stats.Count)}");
if (fullLog)
{
debugLines.Add($" All samples:");
stats.ForEach(s => debugLines.Add($" {LogMonsterCounts(s.MonsterCounts)}"));
}
stats.Sort((s1, s2) => s1.MonsterStrength.CompareTo(s2.MonsterStrength));
debugLines.Add($" Minimum monster strength: {StringFormatter.FormatZeroDecimal(stats.First().MonsterStrength)}");
debugLines.Add($" Median monster strength: {StringFormatter.FormatZeroDecimal(stats[stats.Count / 2].MonsterStrength)}");
debugLines.Add($" Maximum monster strength: {StringFormatter.FormatZeroDecimal(stats.Last().MonsterStrength)}");
debugLines.Add($" Average monster strength: {StringFormatter.FormatZeroDecimal(stats.Average(s => s.MonsterStrength))}");
debugLines.Add($" Average monster strength: {StringFormatter.FormatZeroDecimal(stats.Average(s => s.MonsterStrength))} (Min: {StringFormatter.FormatZeroDecimal(stats.First().MonsterStrength)}, Max: {StringFormatter.FormatZeroDecimal(stats.Last().MonsterStrength)})");
debugLines.Add($" ");
}
}
static string LogMonsterCounts(EventDebugStats stats)
static string LogMonsterCounts(Dictionary<string, int> stats, float divider = 0)
{
return string.Join(", ", stats.MonsterCounts.Select(mc => mc.Key + " x " + mc.Value));
if (divider > 0)
{
return string.Join("\n ", stats.Select(mc => mc.Key + " x " + (mc.Value / divider).FormatSingleDecimal()));
}
else
{
return string.Join(", ", stats.Select(mc => mc.Key + " x " + mc.Value));
}
}
}
}
@@ -1,3 +1,4 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -58,6 +59,18 @@ namespace Barotrauma
if (IsClient) { return; }
if (!swarmSpawned && level.CheckBeaconActive())
{
List<Submarine> connectedSubs = level.BeaconStation.GetConnectedSubs();
foreach (Item item in Item.ItemList)
{
if (!connectedSubs.Contains(item.Submarine)) { continue; }
if (item.GetComponent<PowerTransfer>() != null ||
item.GetComponent<PowerContainer>() != null ||
item.GetComponent<Reactor>() != null)
{
item.Indestructible = true;
}
}
State = 1;
Vector2 spawnPos = level.BeaconStation.WorldPosition;
@@ -60,8 +60,16 @@ namespace Barotrauma
}
else
{
string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", "");
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", null);
if (itemIdentifier != null)
{
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
}
if (itemPrefab == null)
{
string itemTag = prefab.ConfigElement.GetAttributeString("itemtag", "");
itemPrefab = MapEntityPrefab.GetRandom(p => p.Tags.Contains(itemTag), Rand.RandSync.Unsynced) as ItemPrefab;
}
if (itemPrefab == null)
{
DebugConsole.ThrowError("Error in SalvageMission - couldn't find an item prefab with the identifier " + itemIdentifier);
@@ -150,8 +158,8 @@ namespace Barotrauma
if (item == null)
{
item = new Item(itemPrefab, position, null);
item.body.SetTransformIgnoreContacts(item.body.SimPosition, item.body.Rotation);
item.body.FarseerBody.BodyType = BodyType.Kinematic;
item.FindHull();
}
for (int i = 0; i < statusEffects.Count; i++)
@@ -192,7 +200,7 @@ namespace Barotrauma
}
if (validContainers.Any())
{
var selectedContainer = validContainers.GetRandom();
var selectedContainer = validContainers.GetRandom(Rand.RandSync.Unsynced);
if (selectedContainer.Combine(item, user: null))
{
#if SERVER
@@ -26,7 +26,7 @@ namespace Barotrauma
private bool spawnPending;
private readonly int maxAmountPerLevel = int.MaxValue;
public readonly int MaxAmountPerLevel = int.MaxValue;
public List<Character> Monsters => monsters;
public Vector2? SpawnPos => spawnPos;
@@ -74,7 +74,7 @@ namespace Barotrauma
minAmount = prefab.ConfigElement.GetAttributeInt("minamount", defaultAmount);
maxAmount = Math.Max(prefab.ConfigElement.GetAttributeInt("maxamount", 1), minAmount);
maxAmountPerLevel = prefab.ConfigElement.GetAttributeInt("maxamountperlevel", int.MaxValue);
MaxAmountPerLevel = prefab.ConfigElement.GetAttributeInt("maxamountperlevel", int.MaxValue);
var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
@@ -367,9 +367,9 @@ namespace Barotrauma
if (spawnPos == null)
{
if (maxAmountPerLevel < int.MaxValue)
if (MaxAmountPerLevel < int.MaxValue)
{
if (Character.CharacterList.Count(c => c.SpeciesName == speciesName) >= maxAmountPerLevel)
if (Character.CharacterList.Count(c => c.SpeciesName == speciesName) >= MaxAmountPerLevel)
{
disallowed = true;
return;
@@ -507,20 +507,25 @@ namespace Barotrauma
return;
}
var allPackages = GameMain.Config?.AllEnabledPackages.ToList();
if (allPackages?.Count > 0)
if (GameMain.Config != null)
{
List<string> packageNames = new List<string>();
foreach (ContentPackage cp in allPackages)
var allPackages = GameMain.Config.AllEnabledPackages.ToList();
if (allPackages?.Count > 0)
{
string sanitizedName = cp.Name.Replace(":", "").Replace(" ", "");
sanitizedName = sanitizedName.Substring(0, Math.Min(32, sanitizedName.Length));
packageNames.Add(sanitizedName);
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
List<string> packageNames = new List<string>();
foreach (ContentPackage cp in allPackages)
{
string sanitizedName = cp.Name.Replace(":", "").Replace(" ", "");
sanitizedName = sanitizedName.Substring(0, Math.Min(32, sanitizedName.Length));
packageNames.Add(sanitizedName);
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
}
packageNames.Sort();
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames));
}
packageNames.Sort();
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames));
loadedImplementation?.AddDesignEvent("Language:" + GameMain.Config.Language);
}
}
static partial void InitKeys();
@@ -27,21 +27,85 @@ namespace Barotrauma
class SoldItem
{
public ItemPrefab ItemPrefab { get; }
public ushort ID { get; }
public ushort ID { get; private set; }
public bool Removed { get; set; }
public byte SellerID { get; }
public SellOrigin Origin { get; }
public SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId)
public enum SellOrigin
{
Character,
Submarine
}
public SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId, SellOrigin origin)
{
ItemPrefab = itemPrefab;
ID = id;
Removed = removed;
SellerID = sellerId;
Origin = origin;
}
public void SetItemId(ushort id)
{
if (ID != Entity.NullEntityID)
{
DebugConsole.ShowError("Error setting SoldItem.ID: ID has already been set and should not be changed.");
return;
}
ID = id;
}
}
partial class CargoManager
{
private class SoldEntity
{
public enum SellStatus
{
/// <summary>
/// Entity sold in SP. Or, entity sold by client and confirmed by server in MP.
/// </summary>
Confirmed,
/// <summary>
/// Entity sold by client in MP. Client has received at least one update from server after selling, but this entity wasn't yet confirmed.
/// </summary>
Unconfirmed,
/// <summary>
/// Entity sold by client in MP. Client hasn't yet received an update from server after selling.
/// </summary>
Local
}
public Item Item { get; private set; }
public ItemPrefab ItemPrefab { get; }
public SellStatus Status { get; set; }
public SoldEntity(Item item, SellStatus status)
{
Item = item;
ItemPrefab = item?.Prefab;
Status = status;
}
public SoldEntity(ItemPrefab itemPrefab, SellStatus status)
{
ItemPrefab = itemPrefab;
Status = status;
}
public void SetItem(Item item)
{
if (Item != null)
{
DebugConsole.ShowError($"Trying to set SoldEntity.Item, but it's already set!\n{Environment.StackTrace.CleanupStackTrace()}");
return;
}
Item = item;
}
}
public const int MaxQuantity = 100;
public List<PurchasedItem> ItemsInBuyCrate { get; } = new List<PurchasedItem>();
@@ -92,7 +156,7 @@ namespace Barotrauma
public void ModifyItemQuantityInBuyCrate(ItemPrefab itemPrefab, int changeInQuantity)
{
PurchasedItem itemInCrate = ItemsInBuyCrate.Find(i => i.ItemPrefab == itemPrefab);
var itemInCrate = ItemsInBuyCrate.Find(i => i.ItemPrefab == itemPrefab);
if (itemInCrate != null)
{
itemInCrate.Quantity += changeInQuantity;
@@ -109,6 +173,25 @@ namespace Barotrauma
OnItemsInBuyCrateChanged?.Invoke();
}
public void ModifyItemQuantityInSubSellCrate(ItemPrefab itemPrefab, int changeInQuantity)
{
var itemInCrate = ItemsInSellFromSubCrate.Find(i => i.ItemPrefab == itemPrefab);
if (itemInCrate != null)
{
itemInCrate.Quantity += changeInQuantity;
if (itemInCrate.Quantity < 1)
{
ItemsInSellFromSubCrate.Remove(itemInCrate);
}
}
else if (changeInQuantity > 0)
{
itemInCrate = new PurchasedItem(itemPrefab, changeInQuantity);
ItemsInSellFromSubCrate.Add(itemInCrate);
}
OnItemsInSellFromSubCrateChanged?.Invoke();
}
public void PurchaseItems(List<PurchasedItem> itemsToPurchase, bool removeFromCrate)
{
// Check all the prices before starting the transaction
@@ -185,6 +268,82 @@ namespace Barotrauma
OnPurchasedItemsChanged?.Invoke();
}
private Dictionary<ItemPrefab, int> UndeterminedSoldEntities { get; } = new Dictionary<ItemPrefab, int>();
public IEnumerable<Item> GetSellableItemsFromSub()
{
if (Submarine.MainSub == null) { return new List<Item>(); }
var confirmedSoldEntities = Enumerable.Empty<SoldEntity>();
UndeterminedSoldEntities.Clear();
#if CLIENT
confirmedSoldEntities = GetConfirmedSoldEntities();
foreach (var soldEntity in SoldEntities)
{
if (soldEntity.Item != null) { continue; }
if (UndeterminedSoldEntities.TryGetValue(soldEntity.ItemPrefab, out int count))
{
UndeterminedSoldEntities[soldEntity.ItemPrefab] = count + 1;
}
else
{
UndeterminedSoldEntities.Add(soldEntity.ItemPrefab, 1);
}
}
#endif
return Submarine.MainSub.GetItems(true).FindAll(item =>
{
if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
if (item.GetRootInventoryOwner() is Character) { return false; }
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
if (!ItemAndAllContainersInteractable(item)) { return false; }
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("donttakeitems")) { return false; }
return true;
}).Distinct();
static bool ItemAndAllContainersInteractable(Item item)
{
do
{
if (!item.IsPlayerTeamInteractable) { return false; }
item = item.Container;
} while (item != null);
return true;
}
}
private bool IsItemSellable(Item item, IEnumerable<SoldEntity> confirmedItems)
{
if (item.Removed) { return false; }
if (!item.Prefab.CanBeSold) { return false; }
if (item.SpawnedInCurrentOutpost) { return false; }
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
if (confirmedItems.Any(ci => ci.Item == item)) { return false; }
if (UndeterminedSoldEntities.TryGetValue(item.Prefab, out int count))
{
int newCount = count - 1;
if (newCount > 0)
{
UndeterminedSoldEntities[item.Prefab] = newCount;
}
else
{
UndeterminedSoldEntities.Remove(item.Prefab);
}
return false;
}
if (item.OwnInventory?.Container is ItemContainer itemContainer)
{
var containedItems = item.ContainedItems;
if (containedItems.None()) { return true; }
// Allow selling the item if contained items are unsellable and set to be removed on deconstruct
if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; }
// Otherwise there must be no contained items or the contained items must be confirmed as sold
if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; }
}
return true;
}
public static void CreateItems(List<PurchasedItem> itemsToSpawn, Submarine sub)
{
if (itemsToSpawn.Count == 0) { return; }
@@ -448,19 +448,21 @@ namespace Barotrauma
filteredCharacters = filteredCharacters.Union(extraCharacters);
}
return filteredCharacters
// 1. Prioritize those who are on the same submarine than the controlled character
// Prioritize those who are on the same submarine as the controlled character
.OrderByDescending(c => Character.Controlled == null || c.Submarine == Character.Controlled.Submarine)
// 2. Prioritize those who are already ordered to operate the device
// Prioritize those who are already ordered to operate the device
.ThenByDescending(c => order.Category == OrderCategory.Operate && c.CurrentOrders.Any(o => o.Order != null && o.Order.Identifier == order.Identifier && o.Order.TargetEntity == order.TargetEntity))
// 3. Prioritize those with the appropriate job for the order
// Prioritize those with the appropriate job for the order
.ThenByDescending(c => order.HasAppropriateJob(c))
// 4. Prioritize those who don't yet have another Operate order of the same kind (which allows quick-assigning multiple Operate orders to different characters)
.ThenByDescending(c => order.Category == OrderCategory.Operate && c.CurrentOrders.None(o => o.Order != null && o.Order.Identifier == order.Identifier))
// 5. Prioritize bots over player controlled characters
// Prioritize those who don't yet have the same order (which allows quick-assigning the order to different characters)
.ThenByDescending(c => c.CurrentOrders.None(o => o.Order != null && o.Order.Identifier == order.Identifier))
// Prioritize those with the preferred job for the order
.ThenByDescending(c => order.HasPreferredJob(c))
// Prioritize bots over player-controlled characters
.ThenByDescending(c => c.IsBot)
// 6. Use the priority value of the current objective
// Prioritize those with a lower current objective priority
.ThenBy(c => c.AIController is HumanAIController humanAI ? humanAI.ObjectiveManager.CurrentObjective?.Priority : 0)
// 7. Prioritize those with the best skill for the order
// Prioritize those with a higher order skill level
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
}
@@ -74,6 +74,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, false, description: "Should the OnUse StatusEffects trigger when docking (on vanilla docking ports these effects emit particles and play a sound).)")]
public bool ApplyEffectsOnDocking
{
get;
set;
}
[Editable, Serialize(DirectionType.None, false, description: "Which direction the port is allowed to dock in. For example, \"Top\" would mean the port can dock to another port above it.\n"+
"Normally there's no need to touch this setting, but if you notice the docking position is incorrect (for example due to some unusual docking port configuration without hulls or doors), you can use this to enforce the direction.")]
public DirectionType ForceDockingDirection { get; set; }
@@ -261,7 +268,7 @@ namespace Barotrauma.Items.Components
DockingDir = GetDir(DockingTarget);
DockingTarget.DockingDir = -DockingDir;
if (applyEffects)
if (applyEffects && ApplyEffectsOnDocking)
{
ApplyStatusEffects(ActionType.OnUse, 1.0f);
}
@@ -53,7 +53,7 @@ namespace Barotrauma.Items.Components
{
if (holdable.Attached)
{
GameAnalyticsManager.AddDesignEvent("ResourceCollected:" + (GameMain.GameSession?.GameMode?.Name ?? "none") + ":" + item.Prefab.Identifier);
GameAnalyticsManager.AddDesignEvent("ResourceCollected:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + item.Prefab.Identifier);
holdable.DeattachFromWall();
}
trigger.Enabled = false;
@@ -841,9 +841,9 @@ namespace Barotrauma.Items.Components
if (statusEffectLists == null) { return; }
if (!statusEffectLists.TryGetValue(actionType, out List<StatusEffect> statusEffects)) { return; }
currentTargets.Clear();
foreach (StatusEffect effect in statusEffects)
{
currentTargets.Clear();
effect.SetUser(user);
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
@@ -300,7 +300,7 @@ namespace Barotrauma.Items.Components
}
}
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Name ?? "none") + ":" + targetItem.prefab.Identifier);
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + targetItem.prefab.Identifier);
if (targetItem.AllowDeconstruct && allowRemove)
{
@@ -112,7 +112,7 @@ namespace Barotrauma.Items.Components
prevVoltage = Voltage;
hasPower = Voltage > MinVoltage;
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f);
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, deltaTime * 10.0f);
if (Math.Abs(Force) > 1.0f)
{
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
@@ -137,18 +137,19 @@ namespace Barotrauma.Items.Components
currForce *= MathHelper.Lerp(0.5f, 2.0f, condition);
if (item.Submarine.FlippedX) { currForce *= -1; }
Vector2 forceVector = new Vector2(currForce, 0);
item.Submarine.ApplyForce(forceVector);
item.Submarine.ApplyForce(forceVector * deltaTime * Timing.FixedUpdateRate);
UpdatePropellerDamage(deltaTime);
#if CLIENT
particleTimer -= deltaTime;
if (particleTimer <= 0.0f)
float particleInterval = 1.0f / particlesPerSec;
particleTimer += deltaTime;
while (particleTimer > particleInterval)
{
Vector2 particleVel = -forceVector.ClampLength(5000.0f) / 5.0f;
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale,
particleVel * Rand.Range(0.9f, 1.1f),
particleVel * Rand.Range(0.8f, 1.1f),
0.0f, item.CurrentHull);
particleTimer = 1.0f / particlesPerSec;
}
particleTimer -= particleInterval;
}
#endif
}
}
@@ -397,7 +397,7 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < (int)fabricationitemAmount.Value; i++)
{
float outCondition = fabricatedItem.OutCondition;
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Name ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
if (i < amountFittingContainer)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition, quality,
@@ -103,7 +103,20 @@ namespace Barotrauma.Items.Components
if (TargetLevel != null)
{
float hullPercentage = 0.0f;
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
if (item.CurrentHull != null)
{
float hullWaterVolume = item.CurrentHull.WaterVolume;
float totalHullVolume = item.CurrentHull.Volume;
foreach (var linked in item.CurrentHull.linkedTo)
{
if ((linked is Hull linkedHull))
{
hullWaterVolume += linkedHull.WaterVolume;
totalHullVolume += linkedHull.Volume;
}
}
hullPercentage = hullWaterVolume / totalHullVolume * 100.0f;
}
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
}
@@ -131,8 +144,8 @@ namespace Barotrauma.Items.Components
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
item.CurrentHull.WaterVolume += currFlow * deltaTime * Timing.FixedUpdateRate;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 30.0f * deltaTime; }
Voltage -= deltaTime;
}
@@ -738,8 +738,9 @@ namespace Barotrauma.Items.Components
}
lastTarget = target;
float projectileNewSpeed = 0.5f;
float projectileDeflectedNewSpeed = 0.1f;
int remainingHits = Math.Max(MaxTargetsToHit - hits.Count, 0);
float speedMultiplier = Math.Min(0.4f + remainingHits * 0.1f, 1.0f);
float deflectedSpeedMultiplier = 0.1f;
AttackResult attackResult = new AttackResult();
Character character = null;
@@ -755,8 +756,8 @@ namespace Barotrauma.Items.Components
// when hitting limbs with piercing ammo, don't lose as much speed
if (MaxTargetsToHit > 1)
{
projectileNewSpeed = 1f;
projectileDeflectedNewSpeed = 0.8f;
speedMultiplier = 1f;
deflectedSpeedMultiplier = 0.8f;
}
if (limb.IsSevered || limb.character == null || limb.character.Removed) { return false; }
@@ -869,7 +870,7 @@ namespace Barotrauma.Items.Components
if (attackResult.AppliedDamageModifiers != null &&
(attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles) && !StickToDeflective))
{
item.body.LinearVelocity *= projectileDeflectedNewSpeed;
item.body.LinearVelocity *= deflectedSpeedMultiplier;
}
else if ( // When hitting characters the collision normal seems to sometimes point into wrong direction, resulting in a failed attempt to stick
//Vector2.Dot(Vector2.Normalize(velocity), collisionNormal) < 0.0f &&
@@ -901,13 +902,13 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
#endif
item.body.LinearVelocity *= projectileNewSpeed;
item.body.LinearVelocity *= speedMultiplier;
return Hitscan;
}
else
{
item.body.LinearVelocity *= projectileNewSpeed;
item.body.LinearVelocity *= speedMultiplier;
}
var containedItems = item.OwnInventory?.AllItems;
@@ -272,7 +272,7 @@ namespace Barotrauma.Items.Components
ic.ReceiveSignal(signal, connection);
}
if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
if (recipient.Effects != null && signal.value != "0")
{
foreach (StatusEffect effect in recipient.Effects)
{
@@ -241,7 +241,7 @@ namespace Barotrauma.Items.Components
public override void OnMapLoaded()
{
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null &&
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null && IsOn &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
@@ -235,6 +235,13 @@ namespace Barotrauma
/// </summary>
public bool IsInteractable(Character character)
{
#if CLIENT
if (Screen.Selected is EditorScreen)
{
return true;
}
#endif
if (character != null && character.IsOnPlayerTeam)
{
return IsPlayerTeamInteractable;
@@ -638,6 +645,10 @@ namespace Barotrauma
}
}
private float buoyancySineMagnitude;
private float buoyancySineFrequency;
private float buoyancyRandomForce;
public bool FireProof
{
get { return Prefab.FireProof; }
@@ -866,9 +877,11 @@ namespace Barotrauma
}
}
}
body.FarseerBody.AngularDamping = element.GetAttributeFloat("angulardamping", 0.2f);
body.FarseerBody.LinearDamping = element.GetAttributeFloat("lineardamping", 0.1f);
body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f);
body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f);
buoyancySineMagnitude = subElement.GetAttributeFloat("buoyancysinemagnitude", 0f);
buoyancySineFrequency = subElement.GetAttributeFloat("buoyancysinefrequency", 0f);
buoyancyRandomForce = subElement.GetAttributeFloat("buoyancyrandom", 0f);
body.UserData = this;
break;
case "trigger":
@@ -1716,7 +1729,7 @@ namespace Barotrauma
UpdateNetPosition(deltaTime);
if (inWater)
{
ApplyWaterForces();
ApplyWaterForces(deltaTime);
CurrentHull?.ApplyFlowForces(deltaTime, this);
}
}
@@ -1805,16 +1818,24 @@ namespace Barotrauma
transformDirty = false;
}
private float sineTime;
/// <summary>
/// Applies buoyancy, drag and angular drag caused by water
/// </summary>
private void ApplyWaterForces()
private void ApplyWaterForces(float deltaTime)
{
if (body.Mass <= 0.0f || body.Density <= 0.0f)
{
return;
}
if (buoyancySineFrequency > 0)
{
if (sineTime >= float.MaxValue)
{
sineTime = float.MinValue;
}
sineTime += deltaTime * buoyancySineFrequency;
}
float forceFactor = 1.0f;
if (CurrentHull != null)
{
@@ -1833,7 +1854,10 @@ namespace Barotrauma
Vector2 drag = body.LinearVelocity * volume;
body.ApplyForce((uplift - drag) * 10.0f);
float sine = (float)Math.Sin(sineTime) * buoyancySineMagnitude;
Vector2 sineForce = Vector2.UnitY * sine * volume;
Vector2 randomForce = Vector2.UnitY * Rand.Range(-buoyancyRandomForce, buoyancyRandomForce, Rand.RandSync.Unsynced) * volume;
body.ApplyForce((uplift - drag) * 10.0f + sineForce + randomForce);
//apply simple angular drag
body.ApplyTorque(body.AngularVelocity * volume * -0.05f);
@@ -1971,7 +1995,7 @@ namespace Barotrauma
return connectedComponents;
}
private void GetConnectedComponentsRecursive<T>(HashSet<Connection> alreadySearched, List<T> connectedComponents) where T : ItemComponent
private void GetConnectedComponentsRecursive<T>(HashSet<Connection> alreadySearched, List<T> connectedComponents, bool ignoreInactiveRelays = false) where T : ItemComponent
{
ConnectionPanel connectionPanel = GetComponent<ConnectionPanel>();
if (connectionPanel == null) { return; }
@@ -1980,18 +2004,18 @@ namespace Barotrauma
{
if (alreadySearched.Contains(c)) { continue; }
alreadySearched.Add(c);
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents);
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents, ignoreInactiveRelays);
}
}
/// <summary>
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
/// </summary>
public List<T> GetConnectedComponentsRecursive<T>(Connection c) where T : ItemComponent
public List<T> GetConnectedComponentsRecursive<T>(Connection c, bool ignoreInactiveRelays = false) where T : ItemComponent
{
List<T> connectedComponents = new List<T>();
HashSet<Connection> alreadySearched = new HashSet<Connection>();
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents);
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents, ignoreInactiveRelays);
return connectedComponents;
}
@@ -2008,7 +2032,7 @@ namespace Barotrauma
("signal_in2", "signal_out")
};
private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents) where T : ItemComponent
private void GetConnectedComponentsRecursive<T>(Connection c, HashSet<Connection> alreadySearched, List<T> connectedComponents, bool ignoreInactiveRelays) where T : ItemComponent
{
alreadySearched.Add(c);
@@ -2033,12 +2057,18 @@ namespace Barotrauma
foreach (Connection wifiOutput in receiverConnections)
{
if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; }
GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents);
GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents, ignoreInactiveRelays);
}
}
}
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents);
recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents, ignoreInactiveRelays);
}
if (ignoreInactiveRelays)
{
var relay = GetComponent<RelayComponent>();
if (relay != null && !relay.IsOn) { return; }
}
foreach ((string input, string output) in connectionPairs)
@@ -2049,7 +2079,7 @@ namespace Barotrauma
if (pairedConnection != null)
{
if (alreadySearched.Contains(pairedConnection)) { continue; }
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents);
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents, ignoreInactiveRelays);
}
}
else if (output == c.Name)
@@ -2058,7 +2088,7 @@ namespace Barotrauma
if (pairedConnection != null)
{
if (alreadySearched.Contains(pairedConnection)) { continue; }
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents);
GetConnectedComponentsRecursive(pairedConnection, alreadySearched, connectedComponents, ignoreInactiveRelays);
}
}
}
@@ -301,7 +301,7 @@ namespace Barotrauma
Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
if (hull1 == hull2) { return; }
UpdateOxygen(hull1, hull2);
UpdateOxygen(hull1, hull2, deltaTime);
if (linkedTo.Count == 1)
{
@@ -316,7 +316,7 @@ namespace Barotrauma
flowForce.X = MathHelper.Clamp(flowForce.X, -MaxFlowForce, MaxFlowForce);
flowForce.Y = MathHelper.Clamp(flowForce.Y, -MaxFlowForce, MaxFlowForce);
if (openedTimer > 0.0f && flowForce.Length() > lerpedFlowForce.Length())
if (openedTimer > 0.0f && flowForce.LengthSquared() > lerpedFlowForce.LengthSquared())
{
//if the gap has just been opened/created, allow it to exert a large force instantly without any smoothing
lerpedFlowForce = flowForce;
@@ -344,7 +344,7 @@ namespace Barotrauma
subOffset = hull2.Submarine.Position - Submarine.Position;
}
if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) return;
if (hull1.WaterVolume <= 0.0 && hull2.WaterVolume <= 0.0) { return; }
float size = IsHorizontal ? rect.Height : rect.Width;
@@ -366,7 +366,7 @@ namespace Barotrauma
//water flowing from the righthand room to the lefthand room
if (dir == -1)
{
if (!(hull2.WaterVolume > 0.0f)) return;
if (!(hull2.WaterVolume > 0.0f)) { return; }
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
//delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
//delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
@@ -374,10 +374,10 @@ namespace Barotrauma
flowTargetHull = hull1;
//make sure not to move more than what the room contains
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, Math.Min(hull2.WaterVolume, hull2.Volume));
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 300.0f * sizeModifier * deltaTime, Math.Min(hull2.WaterVolume, hull2.Volume));
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - (hull1.WaterVolume));
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
hull1.WaterVolume += delta;
hull2.WaterVolume -= delta;
if (hull1.WaterVolume > hull1.Volume)
@@ -389,16 +389,16 @@ namespace Barotrauma
}
else if (dir == 1)
{
if (!(hull1.WaterVolume > 0.0f)) return;
if (!(hull1.WaterVolume > 0.0f)) { return; }
lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
flowTargetHull = hull2;
//make sure not to move more than what the room contains
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 5.0f * sizeModifier, Math.Min(hull1.WaterVolume, hull1.Volume));
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 300.0f * sizeModifier * deltaTime, Math.Min(hull1.WaterVolume, hull1.Volume));
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull2.Volume * Hull.MaxCompress - (hull2.WaterVolume));
delta = Math.Min(delta, hull2.Volume * Hull.MaxCompress - hull2.WaterVolume);
hull1.WaterVolume -= delta;
hull2.WaterVolume += delta;
if (hull2.WaterVolume > hull2.Volume)
@@ -409,7 +409,7 @@ namespace Barotrauma
flowForce = new Vector2(delta, 0.0f);
}
if (delta > 100.0f && subOffset == Vector2.Zero)
if (delta > 1.5f && subOffset == Vector2.Zero)
{
float avg = (hull1.Surface + hull2.Surface) / 2.0f;
@@ -516,7 +516,7 @@ namespace Barotrauma
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
hull1.WaterVolume += delta;
if (hull1.WaterVolume > hull1.Volume) hull1.Pressure += 0.5f;
if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 30.0f * deltaTime; }
flowTargetHull = hull1;
@@ -541,24 +541,24 @@ namespace Barotrauma
{
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
{
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f;
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 6.0f;
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
hull1.WaveVel[hull1.WaveY.Length - 1] += vel;
hull1.WaveVel[hull1.WaveY.Length - 2] += vel;
hull1.WaveVel[hull1.WaveY.Length - 1] += vel * deltaTime;
hull1.WaveVel[hull1.WaveY.Length - 2] += vel * deltaTime;
}
else
{
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[0])) * 0.1f;
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[0])) * 6.0f;
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
hull1.WaveVel[0] += vel;
hull1.WaveVel[1] += vel;
hull1.WaveVel[0] += vel * deltaTime;
hull1.WaveVel[1] += vel * deltaTime;
}
}
else
{
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : 10.0f) * deltaTime;
}
}
else
@@ -573,7 +573,7 @@ namespace Barotrauma
}
if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
{
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : 10.0f) * deltaTime;
}
}
}
@@ -639,7 +639,7 @@ namespace Barotrauma
}
}
private void UpdateOxygen(Hull hull1, Hull hull2)
private void UpdateOxygen(Hull hull1, Hull hull2, float deltaTime)
{
if (hull1 == null || hull2 == null) { return; }
@@ -650,10 +650,10 @@ namespace Barotrauma
}
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
float totalVolume = (hull1.Volume + hull2.Volume);
float totalVolume = hull1.Volume + hull2.Volume;
float deltaOxygen = (totalOxygen * hull1.Volume / totalVolume) - hull1.Oxygen;
deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed, Hull.OxygenDistributionSpeed);
deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed * deltaTime, Hull.OxygenDistributionSpeed * deltaTime);
hull1.Oxygen += deltaOxygen;
hull2.Oxygen -= deltaOxygen;
@@ -107,7 +107,7 @@ namespace Barotrauma
public static bool ShowHulls = true;
public static bool EditWater, EditFire;
public const float OxygenDistributionSpeed = 500.0f;
public const float OxygenDistributionSpeed = 30000.0f;
public const float OxygenDeteriorationSpeed = 0.3f;
public const float OxygenConsumptionSpeed = 700.0f;
@@ -132,7 +132,7 @@ namespace Barotrauma
private float lethalPressure;
private float surface, drawSurface;
private float surface;
private float waterVolume;
private float pressure;
@@ -241,7 +241,10 @@ namespace Barotrauma
}
OxygenPercentage = prevOxygenPercentage;
surface = drawSurface = rect.Y - rect.Height + WaterVolume / rect.Width;
surface = rect.Y - rect.Height + WaterVolume / rect.Width;
#if CLIENT
drawSurface = surface;
#endif
Pressure = surface;
CreateBackgroundSections();
@@ -275,17 +278,6 @@ namespace Barotrauma
get { return surface; }
}
public float DrawSurface
{
get { return drawSurface; }
set
{
if (Math.Abs(drawSurface - value) < 0.00001f) return;
drawSurface = MathHelper.Clamp(value, rect.Y - rect.Height, rect.Y);
update = true;
}
}
public float WorldSurface
{
get { return Submarine == null ? surface : surface + Submarine.Position.Y; }
@@ -628,7 +620,10 @@ namespace Barotrauma
Gap.UpdateHulls();
}
surface = drawSurface = rect.Y - rect.Height + WaterVolume / rect.Width;
surface = rect.Y - rect.Height + WaterVolume / rect.Width;
#if CLIENT
drawSurface = surface;
#endif
Pressure = surface;
}
@@ -753,8 +748,6 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
BallastFlora?.Update(deltaTime);
UpdateProjSpecific(deltaTime, cam);
@@ -808,11 +801,6 @@ namespace Barotrauma
surface,
rect.Y - rect.Height + waterDepth,
deltaTime * 10.0f), rect.Y - rect.Height);
//interpolate the position of the rendered surface towards the "target surface"
drawSurface = Math.Max(MathHelper.Lerp(
drawSurface,
rect.Y - rect.Height + waterDepth,
deltaTime * 10.0f), rect.Y - rect.Height);
for (int i = 0; i < waveY.Length; i++)
{
@@ -900,10 +888,10 @@ namespace Barotrauma
}
}
//0.01 increase every ~1000 frames = reaches full dirtiness in ~27 minutes
if (submergedSections.Count > 0 && Submarine != null && Submarine.Info.Type == SubmarineType.Player && Rand.Int(1000) == 1)
//0.016 increase every ~2000 frames = reaches full dirtiness in ~35 minutes
if (submergedSections.Count > 0 && Submarine != null && Submarine.Info.Type == SubmarineType.Player && Rand.Int(2000) == 1)
{
DirtySections(submergedSections, 0.01f);
DirtySections(submergedSections, deltaTime);
}
if (waterVolume < Volume)
@@ -911,11 +899,13 @@ namespace Barotrauma
LethalPressure -= 10.0f * deltaTime;
if (WaterVolume <= 0.0f)
{
#if CLIENT
//wait for the surface to be lerped back to bottom and the waves to settle until disabling update
if (drawSurface > rect.Y - rect.Height + 1) return;
if (drawSurface > rect.Y - rect.Height + 1) { return; }
#endif
for (int i = 1; i < waveY.Length - 1; i++)
{
if (waveY[i] > 0.1f) return;
if (waveY[i] > 0.1f) { return; }
}
update = false;
@@ -147,8 +147,6 @@ namespace Barotrauma
{
List<Vector2> points = new List<Vector2>();
var wallPrefabs = StructurePrefab.Prefabs.Where(mp => mp.Body);
foreach (XElement element in rootElement.Elements())
{
if (element.Name != "Structure") { continue; }
@@ -159,8 +157,12 @@ namespace Barotrauma
StructurePrefab prefab = Structure.FindPrefab(name, identifier);
if (prefab == null) { continue; }
float scale = element.GetAttributeFloat("scale", prefab.Scale);
var rect = element.GetAttributeVector4("rect", Vector4.Zero);
rect.Z *= scale / prefab.Scale;
rect.W *= scale / prefab.Scale;
points.Add(new Vector2(rect.X, rect.Y));
points.Add(new Vector2(rect.X + rect.Z, rect.Y));
points.Add(new Vector2(rect.X, rect.Y - rect.W));
@@ -486,7 +486,8 @@ namespace Barotrauma
{
location.LevelData = new LevelData(location)
{
Difficulty = MathHelper.Clamp(GetLevelDifficulty(location.MapPosition.X / Width), 0.0f, 100.0f)
Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f)
//Difficulty = MathHelper.Clamp(GetLevelDifficulty(location.MapPosition.X / Width), 0.0f, 100.0f)
};
location.UnlockInitialMissions();
}
@@ -3,12 +3,10 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -21,6 +19,9 @@ namespace Barotrauma
protected List<ushort> linkedToID;
public List<ushort> unresolvedLinkedToID;
private const int GapUpdateInterval = 4;
private static int gapUpdateTimer;
/// <summary>
/// List of upgrades this item has
/// </summary>
@@ -410,6 +411,7 @@ namespace Barotrauma
}
//connect clone wires to the clone items and refresh links between doors and gaps
List<Wire> orphanedWires = new List<Wire>();
for (int i = 0; i < clones.Count; i++)
{
if (!(clones[i] is Item cloneItem)) { continue; }
@@ -442,7 +444,7 @@ namespace Barotrauma
}
var connectedItem = originalWire.Connections[n].Item;
if (connectedItem == null) { continue; }
if (connectedItem == null || !entitiesToClone.Contains(connectedItem)) { continue; }
//index of the item the wire is connected to
int itemIndex = entitiesToClone.IndexOf(connectedItem);
@@ -469,6 +471,20 @@ namespace Barotrauma
(clones[itemIndex] as Item).Connections[connectionIndex].TryAddLink(cloneWire);
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], false);
}
if (cloneWire.Connections[0] == null || cloneWire.Connections[1] == null)
{
if (!clones.Any(c => (c as Item)?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(cloneWire) ?? false))
{
orphanedWires.Add(cloneWire);
}
}
}
foreach (var orphanedWire in orphanedWires)
{
orphanedWire.Item.Remove();
clones.Remove(orphanedWire.Item);
}
return clones;
@@ -548,20 +564,27 @@ namespace Barotrauma
{
hull.Update(deltaTime, cam);
}
#if CLIENT
Hull.UpdateCheats(deltaTime, cam);
#endif
foreach (Structure structure in Structure.WallList)
{
structure.Update(deltaTime, cam);
}
//update gaps in random order, because otherwise in rooms with multiple gaps
//the water/air will always tend to flow through the first gap in the list,
//which may lead to weird behavior like water draining down only through
//one gap in a room even if there are several
foreach (Gap gap in Gap.GapList.OrderBy(g => Rand.Int(int.MaxValue)))
gapUpdateTimer++;
if (gapUpdateTimer >= GapUpdateInterval)
{
gap.Update(deltaTime, cam);
foreach (Gap gap in Gap.GapList.OrderBy(g => Rand.Int(int.MaxValue)))
{
gap.Update(deltaTime * GapUpdateInterval, cam);
}
gapUpdateTimer = 0;
}
Powered.UpdatePower(deltaTime);
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -317,6 +318,11 @@ namespace Barotrauma
return null;
}
public static MapEntityPrefab GetRandom(Predicate<MapEntityPrefab> predicate, Rand.RandSync sync)
{
return List.GetRandom(p => predicate(p), sync);
}
/// <summary>
/// Find a matching map entity prefab
/// </summary>
@@ -85,7 +85,9 @@ namespace Barotrauma.Networking
get
{
if (customTextColor != null) { return customTextColor.Value; }
return MessageColor[(int)Type];
int intType = (int)Type;
if (intType < 0 || intType >= MessageColor.Length) { return Color.White; }
return MessageColor[intType];
}
set
@@ -230,13 +232,13 @@ namespace Barotrauma.Networking
break;
case ChatMessageType.Radio:
case ChatMessageType.Order:
if (receiver != null && !receiver.IsDead)
if (receiver?.Inventory != null && !receiver.IsDead)
{
foreach (Item receiverItem in receiver.Inventory?.AllItems.Where(i => i.GetComponent<WifiComponent>()?.LinkToChat ?? false))
foreach (Item receiverItem in receiver.Inventory.AllItems.Where(i => i.GetComponent<WifiComponent>()?.LinkToChat ?? false))
{
if (!receiver.HasEquippedItem(receiverItem)) { continue; }
if (sender.Inventory == null || !receiver.HasEquippedItem(receiverItem)) { continue; }
foreach (Item senderItem in sender.Inventory?.AllItems.Where(i => i.GetComponent<WifiComponent>()?.LinkToChat ?? false))
foreach (Item senderItem in sender.Inventory.AllItems.Where(i => i.GetComponent<WifiComponent>()?.LinkToChat ?? false))
{
if (!sender.HasEquippedItem(senderItem)) { continue; }
@@ -22,7 +22,11 @@ namespace Barotrauma.Networking
ManageSettings = 0x200,
ManagePermissions = 0x400,
KarmaImmunity = 0x800,
All = 0xFFF
BuyItems = 0x1000,
SellInventoryItems = 0x2000,
SellSubItems = 0x4000,
CampaignStore = 0x8000,
All = 0xFFFF
}
class PermissionPreset
@@ -1591,8 +1591,10 @@ namespace Barotrauma
if (rope != null && sourceBody.UserData is Limb sourceLimb)
{
rope.Attach(sourceLimb, newItem);
#if SERVER
newItem.CreateServerEvent(rope);
#endif
}
float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread));
var worldPos = sourceBody.Position;
float rotation = chosenItemSpawnInfo.Rotation;
@@ -1625,8 +1627,27 @@ namespace Barotrauma
}
else
{
newItem.body?.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed);
newItem.Rotation = chosenItemSpawnInfo.Rotation;
var body = newItem.body;
if (body != null)
{
float rotation = MathHelper.ToRadians(chosenItemSpawnInfo.Rotation);
if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Limb)
{
if (sourceBody != null)
{
rotation += sourceBody.Rotation;
}
}
else if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Collider)
{
if (entity is Character character)
{
rotation += character.AnimController.Collider.Rotation;
}
}
body.SetTransform(newItem.SimPosition, rotation);
body.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed);
}
}
});
break;