(ad567dea) v0.9.7.1

This commit is contained in:
Juan Pablo Arce
2020-03-04 19:54:29 -03:00
parent 3c09ebe02f
commit 3e99a49383
212 changed files with 1970 additions and 3265 deletions
@@ -25,7 +25,7 @@ namespace Barotrauma
/// <summary>
/// How long does it take for the ai target to fade out if not kept alive.
/// </summary>
public float FadeOutTime { get; private set; } = 2;
public float FadeOutTime { get; private set; } = 1;
public bool Static { get; private set; }
public bool StaticSound { get; private set; }
@@ -92,7 +92,7 @@ namespace Barotrauma
public string SonarLabel;
public string SonarIconIdentifier;
public bool Enabled => SoundRange > 0 || SightRange > 0;
public bool Enabled = true;
public float MinSoundRange, MinSightRange;
public float MaxSoundRange = 100000, MaxSightRange = 100000;
@@ -195,7 +195,7 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (Enabled && !Static && FadeOutTime > 0)
if (!Static && FadeOutTime > 0)
{
// The aitarget goes silent/invisible if the components don't keep it active
if (!StaticSight)
@@ -1017,63 +1017,58 @@ namespace Barotrauma
return;
}
if (AttackingLimb != null && AttackingLimb.attack.Retreat)
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
if (SteeringManager is IndoorsSteeringManager pathSteering)
{
UpdateFallBack(attackWorldPos, deltaTime, false);
}
else
{
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
if (SteeringManager is IndoorsSteeringManager pathSteering)
if (pathSteering.CurrentPath != null)
{
if (pathSteering.CurrentPath != null)
// Attack doors
if (canAttackSub)
{
// Attack doors
if (canAttackSub)
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
{
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
}
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
}
}
}
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
}
}
}
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
SteeringManager.SteeringSeek(steerPos, 5);
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
}
}
}
else
{
SteeringManager.SteeringSeek(steerPos, 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
SteeringManager.SteeringSeek(steerPos, 5);
}
}
else
{
SteeringManager.SteeringSeek(steerPos, 10);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
if (canAttack)
{
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
@@ -1277,9 +1272,12 @@ namespace Barotrauma
if (attackResult.Damage > 0.0f)
{
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
if (Character.Params.AI.AttackWhenProvoked && canAttack)
if (Character.Params.AI.AttackWhenProvoked)
{
ChangeTargetState(attacker, AIState.Attack, 100);
if (canAttack)
{
ChangeTargetState(attacker, AIState.Attack, 100);
}
}
else if (!AIParams.HasTag(attacker.SpeciesName))
{
@@ -1289,14 +1287,14 @@ namespace Barotrauma
{
if (!AIParams.HasTag("stronger"))
{
ChangeTargetState(attacker, canAttack ? AIState.PassiveAggressive : AIState.Escape, 100);
ChangeTargetState(attacker, AIState.Escape, 100);
}
}
else if (enemyAI.CombatStrength < CombatStrength)
{
if (!AIParams.HasTag("weaker"))
{
ChangeTargetState(attacker, canAttack ? AIState.PassiveAggressive : AIState.Escape, 100);
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
else
@@ -1307,7 +1305,7 @@ namespace Barotrauma
}
else
{
ChangeTargetState(attacker, canAttack ? AIState.PassiveAggressive : AIState.Escape, 100);
ChangeTargetState(attacker, AIState.Escape, 100);
}
}
}
@@ -323,7 +323,7 @@ namespace Barotrauma
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
AIObjectiveGoTo gotoObjective = ObjectiveManager.CurrentOrder as AIObjectiveGoTo;
if (!removeDivingSuit)
{
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
@@ -536,16 +536,14 @@ namespace Barotrauma
Hull targetHull = null;
if (Character.CurrentHull != null)
{
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
bool isFleeing = ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>();
foreach (var hull in VisibleHulls)
{
foreach (Character target in Character.CharacterList)
foreach (Character c in Character.CharacterList)
{
if (target.CurrentHull != hull || !target.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
if (c.CurrentHull != hull || !c.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
{
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -562,48 +560,42 @@ namespace Barotrauma
targetHull = hull;
}
}
if (!isFighting)
foreach (Character c in Character.CharacterList)
{
foreach (var gap in hull.ConnectedGaps)
if (c.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(c, Character))
{
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
{
var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
if (!isFleeing)
}
foreach (var gap in hull.ConnectedGaps)
{
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{
foreach (Character target in Character.CharacterList)
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
{
if (target.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(target, Character))
{
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
foreach (Item item in Item.ItemList)
}
}
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{
var orderPrefab = Order.GetPrefab("reportbrokendevices");
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
targetHull = hull;
}
}
var orderPrefab = Order.GetPrefab("reportbrokendevices");
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
targetHull = hull;
}
}
}
@@ -658,7 +650,7 @@ namespace Barotrauma
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return;
}
if (!attacker.IsPlayer && attacker.AIController != null && attacker.AIController.Enabled)
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
{
// Don't retaliate on damage done by friendly ai, because we know that it's accidental
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -672,8 +664,9 @@ namespace Barotrauma
}
else
{
float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality);
if (dmgPercentage < 10)
float currentVitality = Character.CharacterHealth.Vitality;
float dmgPercentage = damage / currentVitality * 100;
if (dmgPercentage < currentVitality / 10)
{
// Don't retaliate on minor (accidental) dmg done by characters that are in the same team
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
@@ -718,6 +711,7 @@ namespace Barotrauma
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
{
SetOrderProjSpecific(order, option);
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option, orderGiver);
@@ -758,6 +752,8 @@ namespace Barotrauma
}
}
partial void SetOrderProjSpecific(Order order, string option);
public override void SelectTarget(AITarget target)
{
SelectedAiTarget = target;
@@ -810,11 +806,11 @@ namespace Barotrauma
/// </summary>
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage);
public static bool HasItem(Character character, string tagOrIdentifier, string containedTag = null, float conditionPercentage = 0)
public static bool HasItem(Character character, string identifier, string containedTag, float conditionPercentage = 0)
{
if (character == null) { return false; }
if (character.Inventory == null) { return false; }
var item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier);
var item = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
return item != null &&
item.ConditionPercentage > conditionPercentage &&
character.HasEquippedItem(item) &&
@@ -938,7 +934,7 @@ namespace Barotrauma
visibleHulls = VisibleHulls;
}
// TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy.
bool ignoreFire = objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
@@ -1026,23 +1022,15 @@ namespace Barotrauma
return false;
}
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false)
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null)
{
if (character == null) { return 0; }
int count = 0;
foreach (var other in Character.CharacterList)
foreach (var c in Character.CharacterList)
{
if (onlyActive && !IsActive(other))
if (FilterCrewMember(character, c))
{
continue;
}
if (onlyBots && other.IsPlayer)
{
continue;
}
if (FilterCrewMember(character, other))
{
if (predicate == null || predicate(other.AIController as HumanAIController))
if (predicate == null || predicate(c.AIController as HumanAIController))
{
count++;
}
@@ -1070,7 +1058,7 @@ namespace Barotrauma
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate);
public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate);
public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
public int CountCrew(Func<HumanAIController, bool> predicate = null) => CountCrew(Character, predicate);
#endregion
}
}
@@ -56,7 +56,7 @@ namespace Barotrauma
/// <summary>
/// Returns true if any node in the path is in stairs
/// </summary>
public bool PathHasStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
public bool InStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
public bool IsNextNodeLadder => GetNextLadder() != null;
@@ -134,7 +134,7 @@ namespace Barotrauma
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null)
{
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || Vector2.DistanceSquared(target, currentTarget) > 1);
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.NextNode == null || Vector2.DistanceSquared(target, currentTarget) > 1);
//find a new path if one hasn't been found yet or the target is different from the current target
if (needsNewPath || findPathTimer < -1.0f)
{
@@ -308,7 +308,7 @@ namespace Barotrauma
currentPath.SkipToNextNode();
}
}
else if (!IsNextLadderSameAsCurrent)
else
{
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize();
@@ -530,6 +530,7 @@ namespace Barotrauma
if (node.Waypoint != null && node.Waypoint.CurrentHull != null)
{
var hull = node.Waypoint.CurrentHull;
if (hull.FireSources.Count > 0)
{
foreach (FireSource fs in hull.FireSources)
@@ -537,14 +538,9 @@ namespace Barotrauma
penalty += fs.Size.X * 10.0f;
}
}
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f)
{
penalty += 500.0f;
}
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
{
penalty += 1000.0f;
}
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f) penalty += 500.0f;
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume) penalty += 1000.0f;
}
return penalty;
@@ -136,10 +136,9 @@ namespace Barotrauma
string allowedJobsStr = element.GetAttributeString("allowedjobs", "");
foreach (string allowedJobIdentifier in allowedJobsStr.Split(','))
{
string key = allowedJobIdentifier.ToLowerInvariant();
if (JobPrefab.Prefabs.ContainsKey(key))
if (JobPrefab.Prefabs.ContainsKey(allowedJobIdentifier.ToLowerInvariant()))
{
AllowedJobs.Add(JobPrefab.Prefabs[key]);
AllowedJobs.Add(JobPrefab.Prefabs[allowedJobIdentifier.ToLowerInvariant()]);
}
}
@@ -32,18 +32,6 @@ namespace Barotrauma
public virtual bool UnequipItems => false;
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
private float _cumulatedDevotion;
protected float CumulatedDevotion
{
get { return _cumulatedDevotion; }
set { _cumulatedDevotion = MathHelper.Clamp(value, 0, MaxDevotion); }
}
protected virtual float MaxDevotion => 10;
/// <summary>
/// Final priority value after all calculations.
/// </summary>
public float Priority { get; set; }
public float PriorityModifier { get; private set; } = 1;
public readonly Character character;
@@ -71,7 +59,6 @@ namespace Barotrauma
/// </summary>
public virtual bool IsLoop { get; set; }
public IEnumerable<AIObjective> SubObjectives => subObjectives;
public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
private readonly List<AIObjective> all = new List<AIObjective>();
public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false)
@@ -99,7 +86,7 @@ namespace Barotrauma
public AIObjective GetActiveObjective()
{
var subObjective = CurrentSubObjective;
var subObjective = SubObjectives.FirstOrDefault();
return subObjective == null ? this : subObjective.GetActiveObjective();
}
@@ -170,8 +157,7 @@ namespace Barotrauma
{
if (!AllowSubObjectiveSorting) { return; }
if (subObjectives.None()) { return; }
subObjectives.ForEach(so => so.GetPriority());
subObjectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
if (ConcurrentObjectives)
{
subObjectives.ForEach(so => so.SortSubObjectives());
@@ -182,23 +168,7 @@ namespace Barotrauma
}
}
/// <summary>
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
/// </summary>
public virtual float GetPriority()
{
Priority = CumulatedDevotion * PriorityModifier;
return Priority;
}
private void UpdateDevotion(float deltaTime)
{
var currentObjective = objectiveManager.CurrentObjective;
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
{
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
}
}
public virtual float GetPriority() => Priority * PriorityModifier;
public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
@@ -210,7 +180,14 @@ namespace Barotrauma
}
else if (objectiveManager.WaitTimer <= 0)
{
UpdateDevotion(deltaTime);
if (objectiveManager.CurrentObjective != null)
{
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
{
Priority += Devotion * PriorityModifier * deltaTime;
}
}
Priority = MathHelper.Clamp(Priority, 0, 100);
}
subObjectives.ForEach(so => so.Update(deltaTime));
}
@@ -287,7 +264,6 @@ namespace Barotrauma
public virtual void OnDeselected()
{
CumulatedDevotion = 0;
Deselected?.Invoke();
}
@@ -306,7 +282,6 @@ namespace Barotrauma
isCompleted = false;
hasBeenChecked = false;
_abandon = false;
CumulatedDevotion = 0;
}
protected abstract void Act(float deltaTime);
@@ -100,11 +100,7 @@ namespace Barotrauma
}
}
public override float GetPriority()
{
Priority = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
return Priority;
}
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
public override void Update(float deltaTime)
{
@@ -143,7 +139,7 @@ namespace Barotrauma
}
if (seekAmmunition == null)
{
if (Mode != CombatMode.Retreat && TryArm() && Enemy != null && !Enemy.Removed)
if (TryArm() && Enemy != null && !Enemy.Removed)
{
OperateWeapon(deltaTime);
}
@@ -74,6 +74,15 @@ namespace Barotrauma
}
}
public override float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage > ConditionLevel;
protected override void Act(float deltaTime)
@@ -54,6 +54,15 @@ namespace Barotrauma
protected override bool Check() => IsCompleted;
public override float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
protected override void Act(float deltaTime)
{
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)), recursive: false);
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -28,44 +29,32 @@ namespace Barotrauma
public override float GetPriority()
{
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetHull == character.CurrentHull)
{
Priority = 0;
distanceFactor = 1;
}
else
{
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 3 : 0;
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetHull == character.CurrentHull)
{
distanceFactor = 1;
}
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severityFactor * distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
}
protected override bool Check() => targetHull.FireSources.None();
private float sinTime;
protected override void Act(float deltaTime)
{
var extinguisherItem = character.Inventory.FindItemByIdentifier("fireextinguisher") ?? character.Inventory.FindItemByTag("fireextinguisher");
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
TryAddSubObjective(ref getExtinguisherObjective, () =>
{
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
return new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
{
// If the item is inside an unsafe hull, decrease the priority
GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
};
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
});
}
else
@@ -90,12 +79,8 @@ namespace Barotrauma
{
useExtinquisherTimer = 0.0f;
}
// Aim
character.CursorPosition = fs.Position;
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
float dist = fromCharacterToFireSource.Length();
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
if (extinguisherItem.RequireAimToUse)
if (extinguisher.Item.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
@@ -110,9 +95,8 @@ namespace Barotrauma
{
character.SetInput(InputType.Aim, false, true);
}
sinTime += deltaTime * 10;
}
character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
@@ -126,7 +110,7 @@ namespace Barotrauma
if (move)
{
//go to the first firesource
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager)
{
DialogueIdentifier = "dialogcannotreachfire",
TargetName = fs.Hull.DisplayName
@@ -9,6 +9,7 @@ namespace Barotrauma
{
public override string DebugTag => "extinguish fires";
public override bool ForceRun => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
@@ -9,6 +9,7 @@ namespace Barotrauma
public override string DebugTag => $"find diving gear ({gearTag})";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
private readonly string gearTag;
private readonly string fallbackTag;
@@ -33,8 +33,6 @@ namespace Barotrauma
private bool resetPriority;
public override float GetPriority() => Priority;
public override void Update(float deltaTime)
{
if (resetPriority)
@@ -254,7 +252,7 @@ namespace Barotrauma
else
{
// Outside
if (hull.RoomName != null && hull.RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase))
if (hull.RoomName != null && hull.RoomName.ToLowerInvariant().Contains("airlock"))
{
hullSafety = 100;
}
@@ -29,23 +29,16 @@ namespace Barotrauma
public override float GetPriority()
{
if (Leak.Removed || Leak.Open <= 0)
{
Priority = 0;
}
else
{
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
}
return Priority;
if (Leak.Removed || Leak.Open <= 0) { return 0; }
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
}
protected override void Act(float deltaTime)
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string DebugTag => "fix leaks";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
@@ -35,7 +36,7 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>());
int totalLeaks = Targets.Count();
if (totalLeaks == 0) { return 0; }
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
@@ -43,13 +44,13 @@ namespace Barotrauma
bool anyFixers = otherFixers > 0;
if (objectiveManager.CurrentOrder == this)
{
float ratio = anyFixers ? totalLeaks / (float)otherFixers : 1;
float ratio = anyFixers ? totalLeaks / otherFixers : 1;
return Targets.Sum(t => GetLeakSeverity(t)) * ratio;
}
else
{
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / otherFixers : 1;
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
{
// Enough fixers
return 0;
@@ -31,6 +31,15 @@ namespace Barotrauma
public bool AllowToFindDivingGear { get; set; } = true;
public override float GetPriority()
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
{
@@ -51,19 +51,14 @@ namespace Barotrauma
public override float GetPriority()
{
if (followControlledCharacter && Character.Controlled == null)
if (followControlledCharacter && Character.Controlled == null) { return 0.0f; }
if (Target is Entity e && e.Removed) { return 0.0f; }
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
if (objectiveManager.CurrentOrder == this)
{
Priority = 0;
return AIObjectiveManager.OrderPriority;
}
if (Target is Entity e && e.Removed)
{
Priority = 0;
}
if (IgnoreIfTargetDead && Target is Character character && character.IsDead)
{
Priority = 0;
}
return objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : Priority;
return 1.0f;
}
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
@@ -223,7 +218,7 @@ namespace Barotrauma
if (n.Waypoint.isObstructed) { return false; }
return (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null);
}, endNodeFilter, nodeFilter);
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
if (!isInside && PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
@@ -270,11 +265,14 @@ namespace Barotrauma
{
get
{
if (character.IsClimbing && SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished)
if (SteeringManager == PathSteering && PathSteering.CurrentPath?.CurrentNode?.Ladders != null)
{
// Still in ladders and the path is not finished -> don't release
return false;
//don't consider the character to be close enough to the target while climbing ladders,
//UNLESS the last node in the path has been reached
//otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; }
}
bool closeEnough = Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
if (closeEnough)
{
@@ -45,17 +45,20 @@ namespace Barotrauma
private float randomUpdateInterval = 5;
public float Random { get; private set; }
public void CalculatePriority()
public void SetRandom()
{
Random = Rand.Range(0.5f, 1.5f);
randomTimer = randomUpdateInterval;
}
public override float GetPriority()
{
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
float initiative = character.GetSkillLevel("initiative");
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
return Priority;
}
public override float GetPriority() => Priority;
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
@@ -66,7 +69,7 @@ namespace Barotrauma
}
else
{
CalculatePriority();
SetRandom();
}
}
}
@@ -179,7 +182,7 @@ namespace Barotrauma
if (!character.IsClimbing)
{
if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
(PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
(PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
{
Wander(deltaTime);
return;
@@ -261,9 +264,9 @@ namespace Barotrauma
public static bool IsForbidden(Hull hull)
{
if (hull == null) { return true; }
string hullName = hull.RoomName;
string hullName = hull.RoomName?.ToLowerInvariant();
if (hullName == null) { return false; }
return hullName.Contains("ballast", StringComparison.OrdinalIgnoreCase) || hullName.Contains("airlock", StringComparison.OrdinalIgnoreCase);
return hullName.Contains("ballast") || hullName.Contains("airlock");
}
}
}
@@ -45,7 +45,6 @@ namespace Barotrauma
public override bool CanBeCompleted => true;
public override bool AbandonWhenCannotCompleteSubjectives => false;
public override bool AllowSubObjectiveSorting => true;
public virtual bool InverseTargetEvaluation => false;
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
@@ -108,46 +107,21 @@ namespace Barotrauma
public override float GetPriority()
{
if (character.LockHands || character.Submarine == null || Targets.None())
if (character.LockHands) { return 0; }
if (character.Submarine == null) { return 0; }
if (Targets.None()) { return 0; }
// Allow the target value to be more than 100.
float targetValue = TargetEvaluation();
// If the target value is less than 1% of the max value, let's just treat it as zero.
if (targetValue < 1) { return 0; }
if (objectiveManager.CurrentOrder == this)
{
Priority = 0;
return AIObjectiveManager.OrderPriority;
}
else
{
// Allow the target value to be more than 100.
float targetValue = TargetEvaluation();
if (InverseTargetEvaluation)
{
targetValue = 100 - targetValue;
}
var currentSubObjective = CurrentSubObjective;
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
{
// If the priority is higher than the target value, let's just use it.
// The priority calculation is more precise, but it takes into account things like distances,
// so it's better not to use it if it's lower than the rougher targetValue.
targetValue = Priority;
}
// If the target value is less than 1% of the max value, let's just treat it as zero.
if (targetValue < 1)
{
Priority = 0;
}
else
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
}
else
{
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
Priority = MathHelper.Lerp(0, max, value);
}
}
}
return Priority;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
float devotion = MathHelper.Min(10, Priority);
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1);
return MathHelper.Lerp(0, max, value);
}
protected void UpdateTargets()
@@ -41,15 +41,6 @@ namespace Barotrauma
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
/// <summary>
/// Returns the last active objective of the specific type.
/// </summary>
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
/// <summary>
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
/// </summary>
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
@@ -155,7 +146,7 @@ namespace Barotrauma
{
var previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault();
if (CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority)
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
{
CurrentObjective = CurrentOrder;
}
@@ -167,14 +158,14 @@ namespace Barotrauma
{
previousObjective?.OnDeselected();
CurrentObjective?.OnSelected();
GetObjective<AIObjectiveIdle>().CalculatePriority();
GetObjective<AIObjectiveIdle>().SetRandom();
}
return CurrentObjective;
}
public float GetCurrentPriority()
{
return CurrentObjective == null ? 0.0f : CurrentObjective.Priority;
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
}
public void UpdateObjectives(float deltaTime)
@@ -214,8 +205,7 @@ namespace Barotrauma
{
if (Objectives.Any())
{
Objectives.ForEach(o => o.GetPriority());
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
}
GetCurrentObjective()?.SortSubObjectives();
}
@@ -307,7 +297,7 @@ namespace Barotrauma
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && orderGiver.IsPlayer
Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
};
break;
default:
@@ -316,7 +306,7 @@ namespace Barotrauma
{
IsLoop = true,
// Don't override unless it's an order by a player
Override = orderGiver != null && orderGiver.IsPlayer
Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
};
break;
}
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -32,32 +31,19 @@ namespace Barotrauma
public override float GetPriority()
{
if (component.Item.ConditionPercentage <= 0)
if (component.Item.ConditionPercentage <= 0) { return 0; }
if (objectiveManager.CurrentOrder == this)
{
Priority = 0;
return AIObjectiveManager.OrderPriority;
}
else
{
if (objectiveManager.CurrentOrder == this)
{
Priority = AIObjectiveManager.OrderPriority;
}
if (component.Item.CurrentHull == null || component.Item.CurrentHull.FireSources.None() || IsOperatedByAnother(GetTarget()))
{
Priority = 0;
}
else if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
Priority = 0;
}
else
{
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
Priority = MathHelper.Clamp(value, 0, max);
}
}
return Priority;
if (component.Item.CurrentHull == null) { return 0; }
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
if (IsOperatedByAnother(GetTarget())) { return 0; }
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
float devotion = MathHelper.Min(10, Priority);
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
return MathHelper.Clamp(value, 0, max);
}
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
@@ -12,6 +12,7 @@ namespace Barotrauma
public override string DebugTag => "pump water";
public override bool KeepDivingGearOn => true;
public override bool UnequipItems => true;
public override bool IgnoreUnsafeHulls => true;
private IEnumerable<Pump> pumpList;
@@ -30,28 +30,21 @@ namespace Barotrauma
{
// TODO: priority list?
// Ignore items that are being repaired by someone else.
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
if (Item.CurrentHull == character.CurrentHull)
{
Priority = 0;
distanceFactor = 1;
}
else
{
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
yDist = yDist > 100 ? yDist * 5 : 0;
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
if (Item.CurrentHull == character.CurrentHull)
{
distanceFactor = 1;
}
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = IsRepairing ? 50 : 0;
float devotion = (CumulatedDevotion + isSelected) / 100;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
}
return Priority;
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = IsRepairing ? 50 : 0;
float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
}
protected override bool Check()
@@ -165,7 +158,7 @@ namespace Barotrauma
}
repairable.StopRepairing(character);
}
else if (repairable.CurrentFixer != character)
else
{
repairable.StartRepairing(character, Repairable.FixActions.Repair);
}
@@ -81,17 +81,18 @@ namespace Barotrauma
// Don't stop fixing until done
return 100;
}
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>(), onlyBots: true);
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>());
int items = Targets.Count;
bool anyFixers = otherFixers > 0;
float ratio = anyFixers ? items / (float)otherFixers : 1;
float ratio = anyFixers ? items / otherFixers : 1;
var result = ratio;
if (objectiveManager.CurrentOrder == this)
{
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
}
else
{
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
{
// Enough fixers
return 0;
@@ -14,7 +14,7 @@ namespace Barotrauma
const float TreatmentDelay = 0.5f;
const float CloseEnoughToTreat = 100.0f;
const float CloseEnoughToTreat = 150.0f;
private readonly Character targetCharacter;
@@ -44,15 +44,6 @@ namespace Barotrauma
Abandon = true;
return;
}
if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
{
var otherCharacter = character.SelectedBy;
if (otherCharacter != null)
{
// Someone else is rescuing/holding the target.
Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
}
}
if (targetCharacter != character)
{
@@ -76,11 +67,7 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
onAbandon: () => RemoveSubObjective(ref goToObjective));
}
else
{
@@ -99,11 +86,7 @@ namespace Barotrauma
RemoveSubObjective(ref goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
safeHull = character.CurrentHull;
});
onAbandon: () => RemoveSubObjective(ref goToObjective));
}
}
}
@@ -122,11 +105,7 @@ namespace Barotrauma
TargetName = targetCharacter.DisplayName
},
onCompleted: () => RemoveSubObjective(ref goToObjective),
onAbandon: () =>
{
RemoveSubObjective(ref goToObjective);
Abandon = true;
});
onAbandon: () => RemoveSubObjective(ref goToObjective));
}
else
{
@@ -148,11 +127,6 @@ namespace Barotrauma
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
private void GiveTreatment(float deltaTime)
{
if (!targetCharacter.IsPlayer)
{
// If the target is a bot, don't let it move
targetCharacter.AIController.SteeringManager.Reset();
}
if (treatmentTimer > 0.0f)
{
treatmentTimer -= deltaTime;
@@ -163,8 +137,9 @@ namespace Barotrauma
//find which treatments are the most suitable to treat the character's current condition
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false);
var allAfflictions = GetVitalityReducingAfflictions(targetCharacter).OrderByDescending(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth));
//check if we already have a suitable treatment for any of the afflictions
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
foreach (Affliction affliction in allAfflictions)
{
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
@@ -225,12 +200,10 @@ namespace Barotrauma
onAbandon: () => RemoveSubObjective(ref getItemObjective));
}
}
if (character != targetCharacter)
{
character.AnimController.Anim = AnimController.Animation.CPR;
}
character.AnimController.Anim = AnimController.Animation.CPR;
}
private void ApplyTreatment(Affliction affliction, Item item)
{
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
@@ -267,7 +240,7 @@ namespace Barotrauma
Abandon = true;
return false;
}
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager);
if (isCompleted && targetCharacter != character)
{
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
@@ -280,24 +253,20 @@ namespace Barotrauma
{
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
{
Priority = 0;
return 0;
}
else
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetCharacter.CurrentHull == character.CurrentHull)
{
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
if (targetCharacter.CurrentHull == character.CurrentHull)
{
distanceFactor = 1;
}
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
float devotion = CumulatedDevotion / 100;
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
distanceFactor = 1;
}
return Priority;
float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
}
public static IEnumerable<Affliction> GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
public static IEnumerable<Affliction> GetVitalityReducingAfflictions(Character character) => character.CharacterHealth.GetAllAfflictions(a => a.GetVitalityDecrease(character.CharacterHealth) > 0);
}
}
@@ -8,11 +8,11 @@ namespace Barotrauma
{
public override string DebugTag => "rescue all";
public override bool ForceRun => true;
public override bool InverseTargetEvaluation => true;
public override bool IgnoreUnsafeHulls => true;
private const float vitalityThreshold = 80;
private const float vitalityThresholdForOrders = 100;
public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
private const float vitalityThresholdForOrders = 95;
public static float GetVitalityThreshold(AIObjectiveManager manager)
{
if (manager == null)
{
@@ -20,7 +20,7 @@ namespace Barotrauma
}
else
{
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
return manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
}
}
@@ -31,50 +31,9 @@ namespace Barotrauma
protected override IEnumerable<Character> GetList() => Character.CharacterList;
protected override float TargetEvaluation()
{
int otherRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRescueAll>(), onlyBots: true);
int targetCount = Targets.Count;
bool anyRescuers = otherRescuers > 0;
float ratio = anyRescuers ? targetCount / (float)otherRescuers : 1;
if (objectiveManager.CurrentOrder == this)
{
return Targets.Min(t => GetVitalityFactor(t)) / ratio;
}
else
{
float multiplier = 1;
if (anyRescuers)
{
float mySkill = character.GetSkillLevel("medical");
int betterRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.Character.Info.Job.GetSkillLevel("medical") >= mySkill, onlyBots: true);
if (targetCount / (float)betterRescuers <= 1)
{
// Enough rescuers
return 100;
}
else
{
bool foundOtherMedics = HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.Info.Job.Prefab.Identifier == "medicaldoctor");
if (foundOtherMedics)
{
if (character.Info.Job.Prefab.Identifier != "medicaldoctor")
{
// Double the vitality factor -> less likely to take action
multiplier = 2;
}
}
}
}
return Targets.Min(t => GetVitalityFactor(t)) / ratio * multiplier;
}
}
protected override float TargetEvaluation() => Targets.Max(t => GetVitalityFactor(t));
public static float GetVitalityFactor(Character character)
{
float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
return Math.Clamp(vitality, 0, 100);
}
public static float GetVitalityFactor(Character character) => Math.Min(character.HealthPercentage - character.Bleeding - character.Bloodloss - Math.Min(character.Oxygen, 0), 100);
protected override AIObjective ObjectiveConstructor(Character target)
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
@@ -88,34 +47,16 @@ namespace Barotrauma
if (!HumanAIController.IsFriendly(character, target)) { return false; }
if (character.AIController is HumanAIController humanAI)
{
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
{
// Ignore unsafe hulls, unless ordered
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
{
return false;
}
}
if (GetVitalityFactor(target) > GetVitalityThreshold(humanAI.ObjectiveManager)) { return false; }
}
else
{
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
if (GetVitalityFactor(target) > vitalityThreshold) { return false; }
}
if (target.Submarine == null || character.Submarine == null) { return false; }
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
if (target.CurrentHull == null) { return false; }
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
{
// Ignore all concious targets that are currently fighting, fleeing or treating characters
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
return false;
}
}
// Don't go into rooms that have enemies
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
return true;
@@ -291,7 +291,7 @@ namespace Barotrauma
}
for (int i = 0; i < AppropriateJobs.Length; i++)
{
if (character.Info.Job.Prefab.Identifier.Equals(AppropriateJobs[i], StringComparison.OrdinalIgnoreCase)) { return true; }
if (character.Info.Job.Prefab.Identifier.ToLowerInvariant() == AppropriateJobs[i].ToLowerInvariant()) { return true; }
}
return false;
}
@@ -732,20 +732,14 @@ namespace Barotrauma
limbJoint.IsSevered = true;
limbJoint.Enabled = false;
Vector2 limbDiff = limbJoint.LimbA.SimPosition - limbJoint.LimbB.SimPosition;
if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
limbDiff = Vector2.Normalize(limbDiff);
float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
List<Limb> connectedLimbs = new List<Limb>();
List<LimbJoint> checkedJoints = new List<LimbJoint>();
GetConnectedLimbs(connectedLimbs, checkedJoints, MainLimb);
foreach (Limb limb in Limbs)
{
if (connectedLimbs.Contains(limb)) { continue; }
if (connectedLimbs.Contains(limb)) continue;
limb.IsSevered = true;
}
@@ -78,7 +78,7 @@ namespace Barotrauma
[Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable]
public AttackTarget TargetType { get; private set; }
[Serialize(LimbType.None, true, description: "To which limb is the attack aimed at? If not defined or set to none, the closest limb is used (default)."), Editable]
[Serialize(LimbType.None, true, description: "If not defined or set to none, the closest limb is used (default)."), Editable]
public LimbType TargetLimbType { get; private set; }
[Serialize(HitDetection.Distance, true, description: "Collision detection is more accurate, but it only affects targets that are in contact with the limb."), Editable]
@@ -87,12 +87,9 @@ namespace Barotrauma
[Serialize(AIBehaviorAfterAttack.FallBack, true, description: "The preferred AI behavior after the attack."), Editable]
public AIBehaviorAfterAttack AfterAttack { get; set; }
[Serialize(false, true, description: "Should the AI try to turn around when aiming with this attack?"), Editable]
[Serialize(false, true, description: "Should the AI try to reverse when aiming with this attack?"), Editable]
public bool Reverse { get; private set; }
[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; }
[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; }
@@ -282,7 +279,7 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names.");
string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, System.StringComparison.OrdinalIgnoreCase));
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.ToLowerInvariant() == afflictionName);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
@@ -292,7 +289,7 @@ namespace Barotrauma
else
{
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.");
@@ -327,7 +324,7 @@ namespace Barotrauma
AfflictionPrefab afflictionPrefab;
Affliction affliction;
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
if (afflictionPrefab != null)
{
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
@@ -57,9 +57,6 @@ namespace Barotrauma
public Hull CurrentHull = null;
public bool IsRemotePlayer;
public bool IsPlayer => Controlled == this || IsRemotePlayer;
public readonly Dictionary<string, SerializableProperty> Properties;
public Dictionary<string, SerializableProperty> SerializableProperties
{
@@ -131,40 +128,13 @@ namespace Barotrauma
set => Params.Noise = value;
}
public float Visibility
{
get => Params.Visibility;
set => Params.Visibility = value;
}
public bool IsTraitor;
public string TraitorCurrentObjective = "";
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
private float attackCoolDown;
public Order CurrentOrder
{
get
{
return Info?.CurrentOrder;
}
private set
{
if (Info != null) { Info.CurrentOrder = value; }
}
}
public string CurrentOrderOption
{
get
{
return Info?.CurrentOrderOption;
}
private set
{
if (Info != null) { Info.CurrentOrderOption = value; }
}
}
public Order CurrentOrder { get; private set; }
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
private readonly List<float> speedMultipliers = new List<float>();
@@ -788,7 +758,7 @@ namespace Barotrauma
var matchingAffliction = AfflictionPrefab.List
.Where(p => p.AfflictionType == "huskinfection")
.Select(p => p as AfflictionPrefabHusk)
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase)));
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.InvariantCultureIgnoreCase)));
string nonHuskedSpeciesName = string.Empty;
if (matchingAffliction == null)
{
@@ -2042,8 +2012,8 @@ namespace Barotrauma
HideFace = false;
UpdateSightRange(deltaTime);
UpdateSoundRange(deltaTime);
UpdateSightRange();
UpdateSoundRange();
if (IsDead) { return; }
@@ -2192,8 +2162,6 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam);
partial void SetOrderProjSpecific(Order order, string orderOption);
private void UpdateOxygen(float deltaTime)
{
PressureProtection -= deltaTime * 100.0f;
@@ -2296,12 +2264,7 @@ namespace Barotrauma
if (itemContainer == null) { return; }
foreach (Item inventoryItem in Inventory.Items)
{
if (inventoryItem == null) { continue; }
if (!itemContainer.Inventory.TryPutItem(inventoryItem, user: null))
{
//if the item couldn't be put inside the despawn container, just drop it
inventoryItem.Drop(dropper: this);
}
itemContainer.Inventory.TryPutItem(inventoryItem, user: null);
}
}
}
@@ -2327,39 +2290,18 @@ namespace Barotrauma
}
}
private readonly float maxAIRange = 10000;
private readonly float aiTargetChangeSpeed = 5;
private void UpdateSightRange(float deltaTime)
private void UpdateSightRange()
{
if (aiTarget == null) { return; }
float minRange = Math.Clamp((float)Math.Sqrt(Mass) * Visibility, 250, 1000);
float massFactor = (float)Math.Sqrt(Mass / 20);
float targetRange = Math.Min(minRange + massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Visibility, maxAIRange);
float newRange = MathHelper.SmoothStep(aiTarget.SightRange, targetRange, deltaTime * aiTargetChangeSpeed);
if (!float.IsNaN(newRange))
{
aiTarget.SightRange = newRange;
}
float range = (float)Math.Sqrt(Mass) * 250 + AnimController.Collider.LinearVelocity.Length() * 500;
aiTarget.SightRange = MathHelper.Clamp(range, 0, 10000);
}
private void UpdateSoundRange(float deltaTime)
private void UpdateSoundRange()
{
if (aiTarget == null) { return; }
if (IsDead)
{
aiTarget.SoundRange = 0;
}
else
{
float massFactor = (float)Math.Sqrt(Mass / 10);
float targetRange = Math.Min(massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Noise, maxAIRange);
float newRange = MathHelper.SmoothStep(aiTarget.SoundRange, targetRange, deltaTime * aiTargetChangeSpeed);
if (!float.IsNaN(newRange))
{
aiTarget.SoundRange = newRange;
}
}
float range = ((float)Math.Sqrt(Mass) / 3) * (AnimController.TargetMovement.Length() * 2) * Noise;
aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000);
}
public bool CanHearCharacter(Character speaker)
@@ -2374,17 +2316,24 @@ namespace Barotrauma
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true)
{
//set the character order only if the character is close enough to hear the message
if (orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
if (orderGiver != null)
{
//set the character order only if the character is close enough to hear the message
if (!CanHearCharacter(orderGiver)) { return; }
}
if (AIController is HumanAIController humanAI)
{
humanAI.SetOrder(order, orderOption, orderGiver, speak);
}
#if CLIENT
else
{
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
}
#endif
SetOrderProjSpecific(order, orderOption);
CurrentOrder = order;
CurrentOrderOption = orderOption;
}
private readonly List<AIChatMessage> aiChatMessageQueue = new List<AIChatMessage>();
@@ -2653,7 +2602,6 @@ namespace Barotrauma
mainLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
bool wasDead = IsDead;
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound);
CharacterHealth.ApplyDamage(hitLimb, attackResult);
@@ -2661,10 +2609,6 @@ namespace Barotrauma
{
OnAttacked?.Invoke(attacker, attackResult);
OnAttackedProjSpecific(attacker, attackResult);
if (!wasDead)
{
TryAdjustAttackerSkill(attacker, -attackResult.Damage);
}
};
if (attacker != null && attackResult.Damage > 0.0f)
@@ -2677,30 +2621,6 @@ namespace Barotrauma
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult);
public void TryAdjustAttackerSkill(Character attacker, float healthChange)
{
if (attacker == null) { return; }
bool isEnemy = AIController is EnemyAIController || TeamID != attacker.TeamID;
if (isEnemy)
{
if (healthChange < 0.0f)
{
float attackerSkillLevel = attacker.GetSkillLevel("weapons");
attacker.Info?.IncreaseSkillLevel("weapons",
-healthChange * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, 1.0f),
attacker.WorldPosition + Vector2.UnitY * 100.0f);
}
}
else if (healthChange > 0.0f)
{
float attackerSkillLevel = attacker.GetSkillLevel("medical");
attacker.Info?.IncreaseSkillLevel("medical",
healthChange * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, 1.0f),
attacker.WorldPosition + Vector2.UnitY * 100.0f);
}
}
public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
@@ -293,9 +293,6 @@ namespace Barotrauma
private NPCPersonalityTrait personalityTrait;
public Order CurrentOrder { get; set;}
public string CurrentOrderOption { get; set; }
//unique ID given to character infos in MP
//used by clients to identify which infos are the same to prevent duplicate characters in round summary
public ushort ID;
@@ -527,11 +524,9 @@ namespace Barotrauma
}
foreach (XElement subElement in infoElement.Elements())
{
if (subElement.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase))
{
Job = new Job(subElement);
break;
}
if (subElement.Name.ToString().ToLowerInvariant() != "job") continue;
Job = new Job(subElement);
break;
}
LoadHeadAttachments();
}
@@ -666,7 +661,7 @@ namespace Barotrauma
{
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
{
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
if (limbElement.GetAttributeString("type", "").ToLowerInvariant() != "head") { continue; }
XElement spriteElement = limbElement.Element("sprite");
if (spriteElement == null) { continue; }
@@ -682,7 +677,7 @@ namespace Barotrauma
//go through the files in the directory to find a matching sprite
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
{
if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
if (!file.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
@@ -833,11 +828,6 @@ namespace Barotrauma
{
if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; }
if (Job.Prefab.Identifier == "assistant")
{
increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier;
}
float prevLevel = Job.GetSkillLevel(skillIdentifier);
Job.IncreaseSkillLevel(skillIdentifier, increase);
@@ -965,7 +955,7 @@ namespace Barotrauma
foreach (XElement childInvElement in itemElement.Elements())
{
if (itemContainerIndex >= itemContainers.Count) break;
if (!childInvElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
if (childInvElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement);
itemContainerIndex++;
}
@@ -220,7 +220,7 @@ namespace Barotrauma
var element = appendageDefinition;
if (element == null)
{
element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeString("affliction", string.Empty).Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
element = mainElement.GetChildElements("huskappendage").FirstOrDefault(e => e.GetAttributeString("affliction", string.Empty).Equals(afflictionIdentifier));
}
if (element == null)
{
@@ -216,7 +216,7 @@ namespace Barotrauma
limbHealths.Clear();
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("limb", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "limb") continue;
limbHealths.Add(new LimbHealth(subElement, this));
}
if (limbHealths.Count == 0)
@@ -408,10 +408,11 @@ namespace Barotrauma
return resistance;
}
private List<Affliction> matchingAfflictions = new List<Affliction>();
public void ReduceAffliction(Limb targetLimb, string affliction, float amount)
{
matchingAfflictions.Clear();
affliction = affliction.ToLowerInvariant();
List<Affliction> matchingAfflictions = new List<Affliction>(afflictions);
if (targetLimb != null)
{
@@ -425,8 +426,8 @@ namespace Barotrauma
}
}
matchingAfflictions.RemoveAll(a =>
!a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) &&
!a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase));
a.Prefab.Identifier.ToLowerInvariant() != affliction &&
a.Prefab.AfflictionType.ToLowerInvariant() != affliction);
if (matchingAfflictions.Count == 0) return;
@@ -690,15 +691,13 @@ namespace Barotrauma
foreach (Affliction affliction in limbHealth.Afflictions)
{
float vitalityDecrease = affliction.GetVitalityDecrease(this);
string identifier = affliction.Prefab.Identifier.ToLowerInvariant();
string type = affliction.Prefab.AfflictionType.ToLowerInvariant();
if (limbHealth.VitalityMultipliers.ContainsKey(identifier))
if (limbHealth.VitalityMultipliers.ContainsKey(affliction.Prefab.Identifier.ToLowerInvariant()))
{
vitalityDecrease *= limbHealth.VitalityMultipliers[identifier];
vitalityDecrease *= limbHealth.VitalityMultipliers[affliction.Prefab.Identifier.ToLowerInvariant()];
}
if (limbHealth.VitalityTypeMultipliers.ContainsKey(type))
if (limbHealth.VitalityTypeMultipliers.ContainsKey(affliction.Prefab.AfflictionType.ToLowerInvariant()))
{
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type];
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[affliction.Prefab.AfflictionType.ToLowerInvariant()];
}
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease;
@@ -792,7 +791,8 @@ namespace Barotrauma
/// </summary>
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f)
{
//key = item identifier
@@ -873,11 +873,5 @@ namespace Barotrauma
}
partial void RemoveProjSpecific();
/// <summary>
/// Automatically filters out buffs.
/// </summary>
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions) =>
afflictions.Where(a => !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
}
}
@@ -62,7 +62,7 @@ namespace Barotrauma
skills = new Dictionary<string, Skill>();
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("skill", System.StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "skill") { continue; }
string skillIdentifier = subElement.GetAttributeString("identifier", "");
if (string.IsNullOrEmpty(skillIdentifier)) { continue; }
skills.Add(
@@ -262,7 +262,7 @@ namespace Barotrauma
}
foreach (XElement element in mainElement.Elements())
{
if (element.Name.ToString().Equals("nojob", StringComparison.OrdinalIgnoreCase)) { continue; }
if (element.Name.ToString().ToLowerInvariant() == "nojob") { continue; }
if (element.IsOverride())
{
var job = new JobPrefab(element.FirstElement(), file.Path)
@@ -125,7 +125,7 @@ namespace Barotrauma
public static string GetFolder(XDocument doc, string filePath)
{
var folder = doc.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
folder = Path.Combine(Path.GetDirectoryName(filePath), "Animations");
}
@@ -198,7 +198,7 @@ namespace Barotrauma
}
else
{
selectedFile = filteredFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
selectedFile = filteredFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
if (selectedFile == null)
{
DebugConsole.ThrowError($"[AnimationParams] Could not find an animation file that matches the name {fileName} and the animation type {animType}. Using the default animations.");
@@ -40,9 +40,6 @@ namespace Barotrauma
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 1000f)]
public float Noise { get; set; }
[Serialize(100f, true, description: "How visible the character is?"), Editable(minValue: 0f, maxValue: 1000f)]
public float Visibility { get; set; }
[Serialize("blood", true), Editable]
public string BloodDecal { get; private set; }
@@ -94,7 +94,8 @@ namespace Barotrauma
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
{
CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier.Equals(speciesName, StringComparison.OrdinalIgnoreCase) && (contentPackage == null || p.ContentPackage == contentPackage));
CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier.ToLowerInvariant()==speciesName.ToLowerInvariant() &&
(contentPackage==null || p.ContentPackage == contentPackage));
if (prefab?.XDocument == null)
{
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}' (content package {contentPackage?.Name ?? "null"})");
@@ -106,7 +107,7 @@ namespace Barotrauma
public static string GetFolder(XDocument doc, string filePath)
{
var folder = doc.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
folder = Path.Combine(Path.GetDirectoryName(filePath), "Ragdolls") + Path.DirectorySeparatorChar;
}
@@ -149,7 +150,7 @@ namespace Barotrauma
}
else
{
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
if (selectedFile == null)
{
DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.");
@@ -65,37 +65,6 @@ namespace Barotrauma
set { skillIncreasePerFabricatorRequiredSkill = value; }
}
private float skillIncreasePerHostileDamage;
[Serialize(0.01f, true)]
public float SkillIncreasePerHostileDamage
{
get { return skillIncreasePerHostileDamage * GetCurrentSkillGainMultiplier(); }
set { skillIncreasePerHostileDamage = value; }
}
private float skillIncreasePerSecondWhenOperatingTurret;
[Serialize(0.001f, true)]
public float SkillIncreasePerSecondWhenOperatingTurret
{
get { return skillIncreasePerSecondWhenOperatingTurret * GetCurrentSkillGainMultiplier(); }
set { skillIncreasePerSecondWhenOperatingTurret = value; }
}
private float skillIncreasePerFriendlyHealed;
[Serialize(0.001f, true)]
public float SkillIncreasePerFriendlyHealed
{
get { return skillIncreasePerFriendlyHealed * GetCurrentSkillGainMultiplier(); }
set { skillIncreasePerFriendlyHealed = value; }
}
[Serialize(1.1f, true)]
public float AssistantSkillIncreaseMultiplier
{
get;
set;
}
private SkillSettings(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
@@ -460,12 +460,12 @@ namespace Barotrauma
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
var rootElement = doc.Root;
var element = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;
var ragdollFolder = RagdollParams.GetFolder(doc, file.Path).CleanUpPathCrossPlatform(true);
var ragdollFolder = RagdollParams.GetFolder(doc, file.Path);
if (Directory.Exists(ragdollFolder))
{
Directory.GetFiles(ragdollFolder, "*.xml").ForEach(f => filePaths.Add(f));
}
var animationFolder = AnimationParams.GetFolder(doc, file.Path).CleanUpPathCrossPlatform(true);
var animationFolder = AnimationParams.GetFolder(doc, file.Path);
if (Directory.Exists(animationFolder))
{
Directory.GetFiles(animationFolder, "*.xml").ForEach(f => filePaths.Add(f));
@@ -186,7 +186,6 @@ namespace Barotrauma
#endif
if (handle.Thread == null)
{
if (handle.AbortRequested) { return true; }
if (handle.Coroutine.Current != null)
{
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
@@ -531,17 +531,18 @@ namespace Barotrauma
commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) =>
{
if (args.Length == 0) { return; }
if (args.Length == 0) return;
args[0] = args[0].ToLowerInvariant();
foreach (MapEntity mapEntity in MapEntity.mapEntityList)
{
if (mapEntity.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase))
if (mapEntity.Name.ToLowerInvariant() == args[0])
{
ThrowError(mapEntity.ID + ": " + mapEntity.Name.ToString());
}
}
foreach (Character character in Character.CharacterList)
{
if (character.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) || character.SpeciesName.Equals(args[0], StringComparison.OrdinalIgnoreCase))
if (character.Name.ToLowerInvariant() == args[0] || character.SpeciesName.ToLowerInvariant() == args[0])
{
ThrowError(character.ID + ": " + character.Name.ToString());
}
@@ -553,8 +554,8 @@ namespace Barotrauma
if (args.Length < 2) return;
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a =>
a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
a.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase));
a.Name.ToLowerInvariant() == args[0].ToLowerInvariant() ||
a.Identifier.ToLowerInvariant() == args[0].ToLowerInvariant());
if (afflictionPrefab == null)
{
ThrowError("Affliction \"" + args[0] + "\" not found.");
@@ -705,7 +706,7 @@ namespace Barotrauma
{
if (Submarine.MainSub == null || Level.Loaded == null) return;
if (args.Length > 0 && args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
if (args.Length > 0 && args[0].ToLowerInvariant() == "start")
{
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
@@ -1219,17 +1220,15 @@ namespace Barotrauma
return;
}
string firstCommand = splitCommand[0].ToLowerInvariant();
if (!firstCommand.Equals("admin", StringComparison.OrdinalIgnoreCase))
if (!splitCommand[0].ToLowerInvariant().Equals("admin"))
{
NewMessage(command, Color.White, true);
}
#if CLIENT
if (GameMain.Client != null)
{
Command matchingCommand = commands.Find(c => c.names.Contains(firstCommand));
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant()));
if (matchingCommand == null)
{
//if the command is not defined client-side, we'll relay it anyway because it may be a custom command at the server's side
@@ -1237,7 +1236,7 @@ namespace Barotrauma
NewMessage("Server command: " + command, Color.Cyan);
return;
}
else if (GameMain.Client.HasConsoleCommandPermission(firstCommand))
else if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant()))
{
if (matchingCommand.RelayToServer)
{
@@ -1256,7 +1255,7 @@ namespace Barotrauma
bool commandFound = false;
foreach (Command c in commands)
{
if (!c.names.Contains(firstCommand)) { continue; }
if (!c.names.Contains(splitCommand[0].ToLowerInvariant())) continue;
c.Execute(splitCommand.Skip(1).ToArray());
commandFound = true;
break;
@@ -1284,7 +1283,7 @@ namespace Barotrauma
}
var matchingCharacters = Character.CharacterList.FindAll(c =>
c.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase) &&
c.Name.ToLowerInvariant() == characterName &&
(!c.IsRemotePlayer || !ignoreRemotePlayers || allowedRemotePlayer?.Character == c));
if (!matchingCharacters.Any())
@@ -1330,7 +1329,7 @@ namespace Barotrauma
JobPrefab job = null;
if (!JobPrefab.Prefabs.ContainsKey(characterLowerCase))
{
job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(characterLowerCase, StringComparison.OrdinalIgnoreCase));
job = JobPrefab.Prefabs.Find(jp => jp.Name?.ToLowerInvariant() == characterLowerCase);
}
else
{
@@ -1588,7 +1587,12 @@ namespace Barotrauma
return true;
}
public static Command FindCommand(string commandName) => commands.Find(c => c.names.Any(n => n.Equals(commandName, StringComparison.OrdinalIgnoreCase)));
public static Command FindCommand(string commandName)
{
commandName = commandName.ToLowerInvariant();
return commands.Find(c => c.names.Any(n => n.ToLowerInvariant() == commandName));
}
public static void Log(string message)
{
@@ -1606,24 +1610,8 @@ namespace Barotrauma
}
}
System.Diagnostics.Debug.WriteLine(error);
NewMessage(error, Color.Red);
#if CLIENT
var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
style: "InnerFrame", color: Color.White)
{
CanBeFocused = false
};
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
error, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true)
{
CanBeFocused = false,
TextColor = Color.Red
};
textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
textBlock.SetTextPos();
listBox.UpdateScrollBarSize();
listBox.BarScroll = 1.0f;
if (createMessageBox)
{
CoroutineManager.StartCoroutine(CreateMessageBox(error));
@@ -1632,8 +1620,6 @@ namespace Barotrauma
{
isOpen = true;
}
#else
NewMessage(error, Color.Red);
#endif
}
@@ -219,40 +219,13 @@ namespace Barotrauma
private void CreateEvents(ScriptedEventSet eventSet)
{
int applyCount = 1;
if (eventSet.PerRuin)
if (eventSet.ChooseRandom)
{
applyCount = Level.Loaded.Ruins.Count();
}
for (int i = 0; i < applyCount; i++)
{
if (eventSet.ChooseRandom)
if (eventSet.EventPrefabs.Count > 0)
{
if (eventSet.EventPrefabs.Count > 0)
{
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand);
if (eventPrefab != null)
{
var newEvent = eventPrefab.CreateInstance();
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
{
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
}
selectedEvents[eventSet].Add(newEvent);
}
}
if (eventSet.ChildSets.Count > 0)
{
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
if (newEventSet != null) { CreateEvents(newEventSet); }
}
}
else
{
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand);
if (eventPrefab != null)
{
var newEvent = eventPrefab.CreateInstance();
newEvent.Init(true);
@@ -263,11 +236,30 @@ namespace Barotrauma
}
selectedEvents[eventSet].Add(newEvent);
}
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
}
if (eventSet.ChildSets.Count > 0)
{
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
if (newEventSet != null) { CreateEvents(newEventSet); }
}
}
else
{
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
{
var newEvent = eventPrefab.CreateInstance();
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
{
CreateEvents(childEventSet);
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
}
selectedEvents[eventSet].Add(newEvent);
}
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
{
CreateEvents(childEventSet);
}
}
}
@@ -304,14 +296,11 @@ namespace Barotrauma
0.0f, 1.0f);
//don't create new events if within 50 meters of the start/end of the level
if (!eventSet.AllowAtStart)
if (distanceTraveled <= 0.0f ||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
{
if (distanceTraveled <= 0.0f ||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
{
return false;
}
return false;
}
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
@@ -44,11 +44,6 @@ namespace Barotrauma
}
}
public override int TeamCount
{
get { return 2; }
}
public CombatMission(MissionPrefab prefab, Location[] locations)
: base(prefab, locations)
{
@@ -118,7 +113,7 @@ namespace Barotrauma
{
for (int i = 0; i < 2; i++)
{
if (wifiComponent.Item.Submarine == subs[i] || subs[i].ConnectedDockingPorts.ContainsKey(wifiComponent.Item.Submarine))
if (wifiComponent.Item.Submarine == subs[i] || subs[i].DockedTo.Contains(wifiComponent.Item.Submarine))
{
wifiComponent.TeamID = subs[i].TeamID;
}
@@ -74,11 +74,6 @@ namespace Barotrauma
get { return true; }
}
public virtual int TeamCount
{
get { return 1; }
}
public virtual IEnumerable<Vector2> SonarPositions
{
get { return Enumerable.Empty<Vector2>(); }
@@ -141,7 +136,7 @@ namespace Barotrauma
}
else
{
allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.Type)) != 0));
allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.type)) != 0));
}
allowedMissions.RemoveAll(m => isSinglePlayer ? m.MultiplayerOnly : m.SingleplayerOnly);
@@ -173,9 +168,10 @@ namespace Barotrauma
public virtual void Update(float deltaTime) { }
public virtual void AssignTeamIDs(List<Networking.Client> clients)
public virtual bool AssignTeamIDs(List<Networking.Client> clients)
{
clients.ForEach(c => c.TeamID = Character.TeamType.Team1);
return false;
}
protected void ShowMessage(int missionState)
@@ -31,7 +31,7 @@ namespace Barotrauma
private readonly ConstructorInfo constructor;
public readonly MissionType Type;
public readonly MissionType type;
public readonly bool MultiplayerOnly, SingleplayerOnly;
@@ -154,18 +154,18 @@ namespace Barotrauma
}
string missionTypeName = element.GetAttributeString("type", "");
if (!Enum.TryParse(missionTypeName, out Type))
if (!Enum.TryParse(missionTypeName, out type))
{
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
return;
}
if (Type == MissionType.None)
if (type == MissionType.None)
{
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - mission type cannot be none.");
return;
}
constructor = missionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
constructor = missionClasses[type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
InitProjSpecific(element);
}
@@ -176,11 +176,11 @@ namespace Barotrauma
{
foreach (Pair<string, string> allowedLocationType in AllowedLocationTypes)
{
if (allowedLocationType.First.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedLocationType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
if (allowedLocationType.First.ToLowerInvariant() == "any" ||
allowedLocationType.First.ToLowerInvariant() == from.Type.Identifier.ToLowerInvariant())
{
if (allowedLocationType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedLocationType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
if (allowedLocationType.Second.ToLowerInvariant() == "any" ||
allowedLocationType.Second.ToLowerInvariant() == to.Type.Identifier.ToLowerInvariant())
{
return true;
}
@@ -189,7 +189,7 @@ namespace Barotrauma
return false;
}
public Mission Instantiate(Location[] locations)
{
return constructor?.Invoke(new object[] { this, locations }) as Mission;
@@ -9,38 +9,42 @@ namespace Barotrauma
{
class MonsterEvent : ScriptedEvent
{
private readonly string speciesName;
private readonly int minAmount, maxAmount;
private string speciesName;
private int minAmount, maxAmount;
private List<Character> monsters;
private readonly bool spawnDeep;
private bool spawnDeep;
private Vector2? spawnPos;
private readonly bool disallowed;
private readonly Level.PositionType spawnPosType;
private bool disallowed;
private Level.PositionType spawnPosType;
private bool spawnPending;
private string characterFileName;
public override Vector2 DebugDrawPos
{
get { return spawnPos ?? Vector2.Zero; }
get { return spawnPos.HasValue ? spawnPos.Value : Vector2.Zero; }
}
public override string ToString()
{
if (maxAmount <= 1)
{
return "MonsterEvent (" + speciesName + ")";
return "MonsterEvent (" + characterFileName + ")";
}
else if (minAmount < maxAmount)
{
return "MonsterEvent (" + speciesName + " x" + minAmount + "-" + maxAmount + ")";
return "MonsterEvent (" + characterFileName + " x" + minAmount + "-" + maxAmount + ")";
}
else
{
return "MonsterEvent (" + speciesName + " x" + maxAmount + ")";
return "MonsterEvent (" + characterFileName + " x" + maxAmount + ")";
}
}
@@ -72,6 +76,7 @@ namespace Barotrauma
}
spawnDeep = prefab.ConfigElement.GetAttributeBool("spawndeep", false);
characterFileName = Path.GetFileName(Path.GetDirectoryName(speciesName)).ToLower();
if (GameMain.NetworkMember != null)
{
@@ -240,37 +245,7 @@ namespace Barotrauma
{
if (submarine.IsOutpost) { continue; }
float minDist = GetMinDistanceToSub(submarine);
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
}
//if spawning in a ruin/cave, wait for someone to be close to it to spawning
//unnecessary monsters in places the players might never visit during the round
if (spawnPosType == Level.PositionType.Ruin ||
spawnPosType == Level.PositionType.Cave)
{
bool someoneNearby = false;
float minDist = Items.Components.Sonar.DefaultSonarRange * 0.8f;
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine.IsOutpost) { continue; }
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist)
{
someoneNearby = true;
break;
}
}
foreach (Character c in Character.CharacterList)
{
if (c == Character.Controlled || c.IsRemotePlayer)
{
if (Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value) < minDist * minDist)
{
someoneNearby = true;
break;
}
}
}
if (!someoneNearby) { return; }
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) return;
}
spawnPending = false;
@@ -305,7 +280,7 @@ namespace Barotrauma
Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
#if CLIENT
if (Character.Controlled != null) { targetEntity = Character.Controlled; }
if (Character.Controlled != null) targetEntity = (Entity)Character.Controlled;
#endif
bool monstersDead = true;
@@ -322,7 +297,7 @@ namespace Barotrauma
}
}
if (monstersDead) { Finished(); }
if (monstersDead) Finished();
}
}
}
@@ -25,10 +25,6 @@ namespace Barotrauma
//the events in this set are delayed if the current EventManager intensity is not between these values
public readonly float MinIntensity, MaxIntensity;
public readonly bool AllowAtStart;
public readonly bool PerRuin;
public readonly Dictionary<string, float> Commonness;
public readonly List<ScriptedEventPrefab> EventPrefabs;
@@ -58,9 +54,6 @@ namespace Barotrauma
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
AllowAtStart = element.GetAttributeBool("allowatstart", false);
PerRuin = element.GetAttributeBool("perruin", false);
Commonness[""] = 1.0f;
foreach (XElement subElement in element.Elements())
{
@@ -70,7 +63,7 @@ namespace Barotrauma
Commonness[""] = subElement.GetAttributeFloat("commonness", 0.0f);
foreach (XElement overrideElement in subElement.Elements())
{
if (overrideElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
if (overrideElement.Name.ToString().ToLowerInvariant() == "override")
{
string levelType = overrideElement.GetAttributeString("leveltype", "");
if (!Commonness.ContainsKey(levelType))
@@ -123,7 +116,7 @@ namespace Barotrauma
int i = 0;
foreach (XElement element in doc.Root.Elements())
{
if (!element.Name.ToString().Equals("eventset", StringComparison.OrdinalIgnoreCase)) { continue; }
if (element.Name.ToString().ToLowerInvariant() != "eventset") { continue; }
List.Add(new ScriptedEventSet(element, i.ToString()));
i++;
}
@@ -7,23 +7,24 @@ namespace Barotrauma.Extensions
public static class IEnumerableExtensions
{
/// <summary>
/// Randomizes the collection (using OrderBy) and returns it.
/// Randomizes the collection and returns it.
/// </summary>
public static IOrderedEnumerable<T> Randomize<T>(this IEnumerable<T> source, Rand.RandSync randSync = Rand.RandSync.Unsynced)
public static IOrderedEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
return source.OrderBy(i => Rand.Value(randSync));
return source.OrderBy(i => Rand.Value());
}
/// <summary>
/// Randomizes the list in place without creating a new collection, using a Fisher-Yates-based algorithm.
/// Randomizes the list in place.
/// </summary>
public static void Shuffle<T>(this IList<T> list, Rand.RandSync randSync = Rand.RandSync.Unsynced)
public static void RandomizeList<T>(this List<T> list)
{
//Fisher-Yates shuffle
int n = list.Count;
while (n > 1)
{
n--;
int k = Rand.Int(n + 1, randSync);
int k = Rand.Int(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
@@ -89,11 +90,6 @@ namespace Barotrauma.Extensions
return source.Count(predicate) > 1;
}
}
public static IEnumerable<T> ToEnumerable<T>(this T item)
{
yield return item;
}
// source: https://stackoverflow.com/questions/19237868/get-all-children-to-one-list-recursive-c-sharp
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
@@ -66,7 +66,7 @@ namespace Barotrauma
spawnedItems.Clear();
var validContainers = new Dictionary<ItemContainer, PreferredContainer>();
prefabsWithContainer.Shuffle();
prefabsWithContainer.RandomizeList();
// Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc)
for (int i = 0; i < prefabsWithContainer.Count; i++)
{
@@ -82,7 +82,7 @@ namespace Barotrauma
// Another pass for items with containers because also they can spawn inside other items (like smg magazine)
prefabsWithContainer.ForEach(i => SpawnItems(i));
// Spawn items that don't have containers last
prefabsWithoutContainer.Shuffle();
prefabsWithoutContainer.RandomizeList();
prefabsWithoutContainer.ForEach(i => SpawnItems(i));
if (OutputDebugInfo)
@@ -150,14 +150,14 @@ namespace Barotrauma
SaveUtil.LoadGame(SavePath);
}
public void StartRound(string levelSeed, float? difficulty = null)
public void StartRound(string levelSeed, float? difficulty = null, bool loadSecondSub = false)
{
Level randomLevel = Level.CreateRandom(levelSeed, difficulty);
StartRound(randomLevel, true);
StartRound(randomLevel, true, loadSecondSub);
}
public void StartRound(Level level, bool reloadSub = true, bool mirrorLevel = false)
public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false)
{
//make sure no status effects have been carried on from the next round
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
@@ -177,7 +177,7 @@ namespace Barotrauma
if (reloadSub || Submarine.MainSub != Submarine) { Submarine.Load(true); }
Submarine.MainSub = Submarine;
if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1)
if (loadSecondSub)
{
if (Submarine.MainSubs[1] == null)
{
@@ -231,7 +231,7 @@ namespace Barotrauma
if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; }
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition);
if (myPort == null || dist < closestDistance || (port.MainDockingPort && !myPort.MainDockingPort))
if (myPort == null || dist < closestDistance)
{
myPort = port;
closestDistance = dist;
@@ -198,7 +198,7 @@ namespace Barotrauma
{
voiceChatVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume, 0);
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume * 30.0f, 0);
#endif
}
}
@@ -236,7 +236,6 @@ namespace Barotrauma
#if DEBUG
public bool AutomaticQuickStartEnabled { get; set; }
public bool TextManagerDebugModeEnabled { get; set; }
#endif
private FileSystemWatcher modsFolderWatcher;
@@ -1203,7 +1202,6 @@ namespace Barotrauma
new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning)
#if DEBUG
, new XAttribute("automaticquickstartenabled", AutomaticQuickStartEnabled)
, new XAttribute("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled)
#endif
);
@@ -1389,7 +1387,6 @@ namespace Barotrauma
ShowTutorialSkipWarning = doc.Root.GetAttributeBool("tutorialskipwarning", true);
#if DEBUG
AutomaticQuickStartEnabled = doc.Root.GetAttributeBool("automaticquickstartenabled", AutomaticQuickStartEnabled);
TextManagerDebugModeEnabled = doc.Root.GetAttributeBool("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled);
#endif
XElement gameplayElement = doc.Root.Element("gameplay");
jobPreferences = new List<Pair<string, int>>();
@@ -73,7 +73,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "item") continue;
string itemIdentifier = subElement.GetAttributeString("identifier", "");
ItemPrefab itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
@@ -58,13 +58,6 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false, description: "If set to true, this docking port is used when spawning the submarine docked to an outpost (if possible).")]
public bool MainDockingPort
{
get;
set;
}
public DockingPort DockingTarget { get; private set; }
public bool Docked
@@ -180,8 +173,8 @@ namespace Barotrauma.Items.Components
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine);
DockingTarget = target;
DockingTarget.DockingTarget = this;
@@ -710,8 +703,8 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnSecondaryUse, 1.0f);
DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine);
item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine);
DockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
item.Submarine.DockedTo.Remove(DockingTarget.item.Submarine);
if (door != null && DockingTarget.door != null)
{
@@ -287,21 +287,24 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
if (isBroken) { return true; }
bool hasRequiredItems = HasRequiredItems(character, false);
if (HasAccess(character))
if (!isBroken)
{
float originalPickingTime = PickingTime;
PickingTime = 0;
ToggleState(ActionType.OnUse, character);
PickingTime = originalPickingTime;
}
bool hasRequiredItems = HasRequiredItems(character, false);
if (HasAccess(character))
{
float originalPickingTime = PickingTime;
PickingTime = 0;
ToggleState(ActionType.OnUse, character);
PickingTime = originalPickingTime;
}
#if CLIENT
else if (hasRequiredItems && character != null && character == Character.Controlled)
{
GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
}
else if (hasRequiredItems && character != null && character == Character.Controlled)
{
GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
}
#endif
}
return false;
}
@@ -54,7 +54,7 @@ namespace Barotrauma.Items.Components
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "attack") { continue; }
attack = new Attack(subElement, item.Name + ", MeleeWeapon");
}
item.IsShootable = true;
@@ -29,13 +29,6 @@ namespace Barotrauma.Items.Components
set { reload = Math.Max(value, 0.0f); }
}
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
set;
}
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles when used by a character with sufficient skills to use the weapon (in degrees).")]
public float Spread
{
@@ -117,61 +110,55 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
}
for (int i = 0; i < ProjectileCount; i++)
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
if (projectile == null) { return true; }
float spread = GetSpread(character);
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
rotation += spread * Rand.Range(-0.5f, 0.5f);
projectile.User = character;
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
projectile.IgnoredBodies = new List<Body>(limbBodies);
Vector2 projectilePos = item.SimPosition;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
{
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
if (projectile == null) { return true; }
float spread = GetSpread(character);
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
rotation += spread * Rand.Range(-0.5f, 0.5f);
projectile.User = character;
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
projectile.IgnoredBodies = new List<Body>(limbBodies);
Vector2 projectilePos = item.SimPosition;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
{
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = barrelPos;
}
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
{
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
}
projectile.Item.body.ResetDynamics();
projectile.Item.SetTransform(projectilePos, rotation);
projectile.Use(deltaTime);
if (projectile.Item.Removed) { continue; }
projectile.User = character;
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.Item.SetTransform(projectilePos,
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
item.RemoveContained(projectile.Item);
if (i == 0)
{
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = barrelPos;
}
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
{
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
}
projectile.Item.body.ResetDynamics();
projectile.Item.SetTransform(projectilePos, rotation);
projectile.Use(deltaTime);
if (projectile.Item.Removed) { return true; }
projectile.User = character;
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.Item.SetTransform(projectilePos,
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
LaunchProjSpecific();
item.RemoveContained(projectile.Item);
return true;
}
@@ -578,7 +578,13 @@ namespace Barotrauma.Items.Components
{
character.SetInput(InputType.Aim, false, true);
}
sinTime += deltaTime * 5;
bool isAiming = false;
var holdable = item.GetComponent<Holdable>();
if (holdable != null)
{
isAiming = holdable.ControlPose;
}
sinTime = isAiming ? sinTime + deltaTime * 5 : 0;
}
// Press the trigger only when the tool is approximately facing the target.
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
@@ -741,7 +741,7 @@ namespace Barotrauma.Items.Components
{
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(this, attribute.Value);
}
ParseMsg();
@@ -870,7 +870,6 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
case "requireditems":
RelatedItem newRequiredItem = RelatedItem.Load(subElement, returnEmptyRequirements, item.Name);
if (newRequiredItem == null) continue;
@@ -39,7 +39,7 @@ namespace Barotrauma.Items.Components
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("fabricableitem", StringComparison.OrdinalIgnoreCase))
if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem")
{
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
break;
@@ -144,7 +144,7 @@ namespace Barotrauma.Items.Components
if (GameMain.Client != null) { return false; }
#endif
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
if (objective.Option.ToLowerInvariant() == "stoppumping")
{
#if SERVER
if (FlowPercentage > 0.0f)
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using System.Globalization;
namespace Barotrauma.Items.Components
{
@@ -652,20 +651,6 @@ namespace Barotrauma.Items.Components
unsentChanges = true;
}
break;
case "set_fissionrate":
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
FissionRate = newFissionRate;
unsentChanges = true;
}
break;
case "set_turbineoutput":
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
TurbineOutput = newTurbineOutput;
unsentChanges = true;
}
break;
}
}
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
}
if (HasBeenTuned) { return true; }
if (string.IsNullOrEmpty(objective.Option) || objective.Option.Equals("charge", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
{
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
{
@@ -53,9 +53,7 @@ namespace Barotrauma.Items.Components
private PrismaticJoint stickJoint;
private Body stickTarget;
private readonly Attack attack;
private Vector2 launchPos;
private Attack attack;
public List<Body> IgnoredBodies;
@@ -159,7 +157,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
attack = new Attack(subElement, item.Name + ", Projectile");
}
}
@@ -222,8 +220,6 @@ namespace Barotrauma.Items.Components
item.Drop(null);
launchPos = item.SimPosition;
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
@@ -419,9 +415,7 @@ namespace Barotrauma.Items.Components
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
collisionCategory: Physics.CollisionWall);
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure &&
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure)
{
target = wallBody.FixtureList.First();
}
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
@@ -112,7 +111,7 @@ namespace Barotrauma.Items.Components
element.GetAttributeString("name", "");
//backwards compatibility
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase));
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().ToLowerInvariant() == "showrepairuithreshold");
if (showRepairUIAttribute != null)
{
float repairThreshold;
@@ -131,26 +130,7 @@ namespace Barotrauma.Items.Components
}
partial void InitProjSpecific(XElement element);
/// <summary>
/// Check if the character manages to succesfully repair the item
/// </summary>
public bool CheckCharacterSuccess(Character character)
{
if (character == null) { return false; }
// Only check for success when repairing electrical devices
if (requiredSkills.None(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase))) { return true; }
//unpowered items can be repaired without a risk of electrical shock
if (item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
if (Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(character)) { return true; }
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
return false;
}
public bool StartRepairing(Character character, FixActions action)
{
if (character == null || character.IsDead || action == FixActions.None)
@@ -163,15 +143,8 @@ namespace Barotrauma.Items.Components
#if SERVER
if (CurrentFixer != character || currentFixerAction != action)
{
if (!CheckCharacterSuccess(character))
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
return false;
}
item.CreateServerEvent(this);
}
#else
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character)) { return false; }
#endif
CurrentFixer = character;
CurrentFixerAction = action;
@@ -88,7 +88,7 @@ namespace Barotrauma.Items.Components
{
foreach (XElement subElement in item.Prefab.ConfigElement.Elements())
{
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "connectionpanel") { continue; }
foreach (XElement connectionElement in subElement.Elements())
{
@@ -32,7 +32,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
if (subElement.Name.ToString().ToLowerInvariant() == "statuseffect")
{
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
}
@@ -272,12 +272,13 @@ namespace Barotrauma.Items.Components
private void UpdateAITarget(AITarget target)
{
target.Enabled = IsActive;
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
target.SightRange = target.MaxSightRange * lightBrightness;
}
partial void SetLightSourceState(bool enabled, float brightness);
@@ -39,14 +39,6 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(false, false, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.")]
public bool AllowCrossTeamCommunication
{
get;
set;
}
[Editable, Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
"as chat messages in the chatbox of the player holding the item.")]
public bool LinkToChat
@@ -92,7 +84,7 @@ namespace Barotrauma.Items.Components
{
if (sender == null || sender.channel != channel) { return false; }
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
if (sender.TeamID != TeamID)
{
return false;
}
@@ -72,13 +72,6 @@ namespace Barotrauma.Items.Components
set { reloadTime = value; }
}
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
set;
}
[Editable, Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate. TODO")]
public Vector2 RotationLimits
{
@@ -220,13 +213,13 @@ namespace Barotrauma.Items.Components
{
this.cam = cam;
if (reload > 0.0f) { reload -= deltaTime; }
if (reload > 0.0f) reload -= deltaTime;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
UpdateProjSpecific(deltaTime);
if (minRotation == maxRotation) { return; }
if (minRotation == maxRotation) return;
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
@@ -237,19 +230,12 @@ namespace Barotrauma.Items.Components
targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
}
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
if (degreeOfSuccess < 0.5f) { degreeOfSuccess *= degreeOfSuccess; } //the ease of aiming drops quickly with insufficient skill levels
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
if (degreeOfSuccess < 0.5f) degreeOfSuccess *= degreeOfSuccess; //the ease of aiming drops quickly with insufficient skill levels
float springStiffness = MathHelper.Lerp(SpringStiffnessLowSkill, SpringStiffnessHighSkill, degreeOfSuccess);
float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess);
float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess);
if (user?.Info != null)
{
user.Info.IncreaseSkillLevel("weapons",
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f),
user.WorldPosition + Vector2.UnitY * 150.0f);
}
angularVelocity +=
(MathHelper.WrapAngle(targetRotation - rotation) * springStiffness - angularVelocity * springDamping) * deltaTime;
angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
@@ -279,16 +265,18 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (!characterUsable && character != null) { return false; }
if (!characterUsable && character != null) return false;
return TryLaunch(deltaTime, character);
}
private bool TryLaunch(float deltaTime, Character character = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
#if CLIENT
if (GameMain.Client != null) return false;
#endif
if (reload > 0.0f) return false;
if (reload > 0.0f) { return false; }
if (GetAvailableBatteryPower() < powerConsumption)
{
#if CLIENT
@@ -300,77 +288,72 @@ namespace Barotrauma.Items.Components
#endif
return false;
}
Projectile launchedProjectile = null;
for (int i = 0; i < ProjectileCount; i++)
foreach (MapEntity e in item.linkedTo)
{
foreach (MapEntity e in item.linkedTo)
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) continue;
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
{
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) { continue; }
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
linkedItem.Use(deltaTime, null);
var repairable = linkedItem.GetComponent<Repairable>();
if (repairable != null)
{
linkedItem.Use(deltaTime, null);
var repairable = linkedItem.GetComponent<Repairable>();
if (repairable != null)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
}
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
}
}
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0)
{
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
failedLaunchAttempts++;
#if CLIENT
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
{
flashNoAmmo = true;
failedLaunchAttempts = 0;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
failedLaunchAttempts = 0;
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
float takePower = neededPower / batteries.Count;
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
foreach (PowerContainer battery in batteries)
{
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
#endif
}
}
launchedProjectile = projectiles[0];
Launch(projectiles[0].Item, character);
}
#if SERVER
if (character != null && launchedProjectile != null)
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0)
{
string msg = character.LogName + " launched " + item.Name + " (projectile: " + launchedProjectile.Item.Name;
var containedItems = launchedProjectile.Item.ContainedItems;
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
failedLaunchAttempts++;
#if CLIENT
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
{
flashNoAmmo = true;
failedLaunchAttempts = 0;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
failedLaunchAttempts = 0;
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
float takePower = neededPower / batteries.Count;
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
foreach (PowerContainer battery in batteries)
{
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
#endif
}
}
Launch(projectiles[0].Item, character);
#if SERVER
if (character != null)
{
string msg = character.LogName + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name;
var containedItems = projectiles[0].Item.ContainedItems;
if (containedItems == null || !containedItems.Any())
{
msg += ")";
@@ -564,7 +547,7 @@ namespace Barotrauma.Items.Components
return false;
}
if (objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase))
if (objective.Option.ToLowerInvariant() == "fireatwill")
{
character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f);
character.SetInput(InputType.Shoot, true, true);
@@ -211,7 +211,6 @@ namespace Barotrauma.Items.Components
}
public bool AutoEquipWhenFull { get; private set; }
public bool DisplayContainedStatus { get; private set; }
public readonly int Variants;
@@ -265,7 +264,6 @@ namespace Barotrauma.Items.Components
limbType = new LimbType[spriteCount];
limb = new Limb[spriteCount];
AutoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
DisplayContainedStatus = element.GetAttributeBool("displaycontainedstatus", false);
int i = 0;
foreach (XElement subElement in element.Elements())
{
@@ -286,7 +284,7 @@ namespace Barotrauma.Items.Components
foreach (XElement lightElement in subElement.Elements())
{
if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
if (lightElement.Name.ToString().ToLowerInvariant() != "lightcomponent") continue;
wearableSprites[i].LightComponent = new LightComponent(item, lightElement)
{
Parent = this
@@ -1839,30 +1839,6 @@ namespace Barotrauma
return true;
}
public float GetContainedItemConditionPercentage()
{
var containedItems = ContainedItems;
if (containedItems != null)
{
float condition = 0f;
float maxCondition = 0f;
foreach (Item item in containedItems)
{
condition += item.condition;
maxCondition += item.MaxCondition;
}
if (maxCondition > 0.0f)
{
return condition / maxCondition;
}
}
return -1;
}
public void Use(float deltaTime, Character character = null, Limb targetLimb = null)
{
if (RequireAimToUse && (character == null || !character.IsKeyDown(InputType.Aim)))
@@ -810,7 +810,7 @@ namespace Barotrauma
public PriceInfo GetPrice(Location location)
{
if (prices == null || !prices.ContainsKey(location.Type.Identifier.ToLowerInvariant())) { return null; }
if (prices == null || !prices.ContainsKey(location.Type.Identifier.ToLowerInvariant())) return null;
return prices[location.Type.Identifier.ToLowerInvariant()];
}
@@ -152,7 +152,7 @@ namespace Barotrauma
public void Save(XElement element)
{
element.Add(
new XAttribute("items", JoinedIdentifiers),
new XAttribute("identifiers", JoinedIdentifiers),
new XAttribute("type", type.ToString()),
new XAttribute("optional", IsOptional),
new XAttribute("ignoreineditor", IgnoreInEditor));
@@ -219,10 +219,7 @@ namespace Barotrauma
string typeStr = element.GetAttributeString("type", "");
if (string.IsNullOrEmpty(typeStr))
{
if (element.Name.ToString().Equals("containable", StringComparison.OrdinalIgnoreCase))
{
typeStr = "Contained";
}
if (element.Name.ToString().ToLowerInvariant() == "containable") typeStr = "Contained";
}
if (!Enum.TryParse(typeStr, true, out ri.type))
{
@@ -249,7 +246,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "statuseffect") continue;
ri.statusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
}
@@ -14,7 +14,7 @@ namespace Barotrauma
partial class FireSource : ISpatialEntity
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 20.0f;
const float GrowSpeed = 5.0f;
protected Hull hull;
@@ -378,7 +378,7 @@ namespace Barotrauma
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, 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)
@@ -399,7 +399,7 @@ namespace Barotrauma
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 5.0f * sizeModifier, 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)
@@ -414,14 +414,14 @@ namespace Barotrauma
{
float avg = (hull1.Surface + hull2.Surface) / 2.0f;
if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
if (hull1.WaterVolume < hull1.Volume - Hull.MaxCompress &&
hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1] < rect.Y)
{
hull1.WaveVel[hull1.WaveY.Length - 1] = (avg - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 0.1f;
hull1.WaveVel[hull1.WaveY.Length - 2] = hull1.WaveVel[hull1.WaveY.Length - 1];
}
if (hull2.WaterVolume < hull2.Volume / Hull.MaxCompress &&
if (hull2.WaterVolume < hull2.Volume - Hull.MaxCompress &&
hull2.Surface + hull2.WaveY[0] < rect.Y)
{
hull2.WaveVel[0] = (avg - (hull2.Surface + hull2.WaveY[0])) * 0.1f;
@@ -436,12 +436,12 @@ namespace Barotrauma
//lower room is full of water
if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
{
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + (hull2.Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier);
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + Hull.MaxCompress, deltaTime * 8000.0f * sizeModifier);
//make sure not to place more water to the target room than it can hold
if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress)
if (hull1.WaterVolume + delta > hull1.Volume + Hull.MaxCompress)
{
delta -= (hull1.WaterVolume + delta) - (hull1.Volume * Hull.MaxCompress);
delta -= (hull1.WaterVolume + delta) - (hull1.Volume + Hull.MaxCompress);
}
delta = Math.Max(delta, 0.0f);
@@ -469,9 +469,9 @@ namespace Barotrauma
float delta = Math.Min(hull1.WaterVolume, deltaTime * 25000f * sizeModifier);
//make sure not to place more water to the target room than it can hold
if (hull2.WaterVolume + delta > hull2.Volume * Hull.MaxCompress)
if (hull2.WaterVolume + delta > hull2.Volume + Hull.MaxCompress)
{
delta -= (hull2.WaterVolume + delta) - (hull2.Volume * Hull.MaxCompress);
delta -= (hull2.WaterVolume + delta) - (hull2.Volume + Hull.MaxCompress);
}
hull1.WaterVolume -= delta;
hull2.WaterVolume += delta;
@@ -489,7 +489,7 @@ namespace Barotrauma
if (open > 0.0f)
{
if (hull1.WaterVolume > hull1.Volume / Hull.MaxCompress && hull2.WaterVolume > hull2.Volume / Hull.MaxCompress)
if (hull1.WaterVolume > hull1.Volume - Hull.MaxCompress && hull2.WaterVolume > hull2.Volume - Hull.MaxCompress)
{
float avgLethality = (hull1.LethalPressure + hull2.LethalPressure) / 2.0f;
hull1.LethalPressure = avgLethality;
@@ -515,10 +515,10 @@ namespace Barotrauma
//the larger the gap is, the faster the water flows
float sizeModifier = size * open * open;
float delta = hull1.Volume * Hull.MaxCompress * sizeModifier * deltaTime;
float delta = Hull.MaxCompress * sizeModifier * deltaTime;
//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;
if (hull1.WaterVolume > hull1.Volume) hull1.Pressure += 0.5f;
@@ -541,7 +541,7 @@ namespace Barotrauma
higherSurface = hull1.Surface;
lowerSurface = rect.Y;
if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
if (hull1.WaterVolume < hull1.Volume - Hull.MaxCompress &&
hull1.Surface < rect.Y)
{
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
@@ -576,7 +576,7 @@ namespace Barotrauma
{
flowForce = new Vector2(0.0f, delta);
}
if (hull1.WaterVolume >= hull1.Volume / Hull.MaxCompress)
if (hull1.WaterVolume >= hull1.Volume - Hull.MaxCompress)
{
hull1.LethalPressure += (Submarine != null && Submarine.AtDamageDepth) ? 100.0f * deltaTime : 10.0f * deltaTime;
}
@@ -26,9 +26,8 @@ namespace Barotrauma
public static float WaveSpread = 0.05f;
public static float WaveDampening = 0.05f;
//how much excess water the room can contain, relative to the volume of the room.
//needed to make it possible for pressure to "push" water up through U-shaped hull configurations
public const float MaxCompress = 1.05f;
//how much excess water the room can contain (= more than the volume of the room)
public const float MaxCompress = 10000f;
public readonly Dictionary<string, SerializableProperty> properties;
public Dictionary<string, SerializableProperty> SerializableProperties
@@ -155,7 +154,7 @@ namespace Barotrauma
set
{
if (!MathUtils.IsValid(value)) return;
waterVolume = MathHelper.Clamp(value, 0.0f, Volume * MaxCompress);
waterVolume = MathHelper.Clamp(value, 0.0f, Volume + MaxCompress);
if (waterVolume < Volume) Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
if (waterVolume > 0.0f) update = true;
}
@@ -322,7 +321,6 @@ namespace Barotrauma
CeilingHeight = ConvertUnits.ToDisplayUnits(upperPickedPos.Y - lowerPickedPos.Y);
}
}
Pressure = rect.Y - rect.Height + waterVolume / rect.Width;
}
public void AddToGrid(Submarine submarine)
@@ -875,7 +873,7 @@ namespace Barotrauma
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine)
{
WaterVolume = element.GetAttributeFloat("pressure", 0.0f),
waterVolume = element.GetAttributeFloat("pressure", 0.0f),
ID = (ushort)int.Parse(element.Attribute("ID").Value)
};
@@ -310,7 +310,6 @@ namespace Barotrauma
if (Submarine.MainSub != null)
{
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
minWidth = Math.Min(minWidth, maxWidth);
}
@@ -1623,7 +1622,7 @@ namespace Barotrauma
EndOutpost = outpost;
if (GameMain.GameSession?.EndLocation != null) { outpost.Name = GameMain.GameSession.EndLocation.Name; }
}
}
}
}
private bool IsModeStartOutpostCompatible()
@@ -415,10 +415,10 @@ namespace Barotrauma
string biomeName = biomeNames[i].Trim().ToLowerInvariant();
if (biomeName == "none") { continue; }
Biome matchingBiome = biomes.Find(b => b.Identifier.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
Biome matchingBiome = biomes.Find(b => b.Identifier.ToLowerInvariant() == biomeName);
if (matchingBiome == null)
{
matchingBiome = biomes.Find(b => b.DisplayName.Equals(biomeName, StringComparison.OrdinalIgnoreCase));
matchingBiome = biomes.Find(b => b.DisplayName.ToLowerInvariant() == biomeName);
if (matchingBiome == null)
{
DebugConsole.ThrowError("Error in level generation parameters: biome \"" + biomeName + "\" not found.");
@@ -487,7 +487,7 @@ namespace Barotrauma
mainElement = doc.Root.FirstElement();
biomeElements.Clear();
levelParamElements.Clear();
DebugConsole.NewMessage($"Overriding the level generation parameters and biomes with '{file.Path}'", Color.Yellow);
DebugConsole.NewMessage($"Overriding the level generation parameters with '{file.Path}'", Color.Yellow);
}
else if (biomeElements.Any() || levelParamElements.Any())
{
@@ -497,22 +497,7 @@ namespace Barotrauma
foreach (XElement element in mainElement.Elements())
{
if (element.IsOverride())
{
if (element.FirstElement().Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
{
biomeElements.Clear();
biomeElements.AddRange(element.FirstElement().Elements());
DebugConsole.NewMessage($"Overriding biomes with '{file.Path}'", Color.Yellow);
}
else
{
levelParamElements.Clear();
DebugConsole.NewMessage($"Overriding the level generation parameters with '{file.Path}'", Color.Yellow);
levelParamElements.AddRange(element.Elements());
}
}
else if (element.Name.ToString().Equals("biomes", StringComparison.OrdinalIgnoreCase))
if (element.Name.ToString().ToLowerInvariant() == "biomes")
{
biomeElements.AddRange(element.Elements());
}
@@ -277,7 +277,7 @@ namespace Barotrauma.RuinGeneration
{
foreach (XElement subElement in element2.Elements())
{
if (subElement.Name.ToString().Equals("chooseone", StringComparison.OrdinalIgnoreCase))
if (subElement.Name.ToString().ToLowerInvariant() == "chooseone")
{
groupIndex++;
LoadEntities(subElement, ref groupIndex);
@@ -390,7 +390,7 @@ namespace Barotrauma.RuinGeneration
SourceEntityIdentifier = element.GetAttributeString("sourceentity", "");
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("wire", StringComparison.OrdinalIgnoreCase))
if (subElement.Name.ToString().ToLowerInvariant() == "wire")
{
WireConnection = new Pair<string, string>(
subElement.GetAttributeString("from", ""),
@@ -721,10 +721,8 @@ namespace Barotrauma.RuinGeneration
//doors create their own gaps, don't create an additional one if there's a door at this
bool doorFound = false;
foreach (Item item in Item.ItemList)
foreach (Door door in doors)
{
var door = item.GetComponent<Door>();
if (door == null) { continue; }
if (Math.Abs(door.Item.WorldPosition.X - gapRect.Value.Center.X) < 5 &&
Math.Abs(door.Item.WorldPosition.Y - gapRect.Value.Center.Y) < 5)
{
@@ -284,7 +284,7 @@ namespace Barotrauma
foreach (LocationConnection connection in connections)
{
float centerDist = Vector2.Distance(connection.CenterPos, mapCenter);
connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 0, 100);
connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100);
}
AssignBiomes();
@@ -463,7 +463,7 @@ namespace Barotrauma
bool disallowedFound = false;
foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(disallowedLocationName, StringComparison.OrdinalIgnoreCase)))
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.ToLowerInvariant() == disallowedLocationName.ToLowerInvariant()))
{
disallowedFound = true;
break;
@@ -475,7 +475,7 @@ namespace Barotrauma
bool requiredFound = false;
foreach (string requiredLocationName in typeChange.RequiredAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(requiredLocationName, StringComparison.OrdinalIgnoreCase)))
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.ToLowerInvariant() == requiredLocationName.ToLowerInvariant()))
{
requiredFound = true;
break;
@@ -499,7 +499,7 @@ namespace Barotrauma
if (selectedTypeChange != null)
{
string prevName = location.Name;
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(selectedTypeChange.ChangeToType, StringComparison.OrdinalIgnoreCase)));
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.ToLowerInvariant() == selectedTypeChange.ChangeToType.ToLowerInvariant()));
ChangeLocationType(location, prevName, selectedTypeChange);
location.TypeChangeTimer = -1;
break;
@@ -553,12 +553,13 @@ namespace Barotrauma
string prevLocationName = location.Name;
LocationType prevLocationType = location.Type;
location.Discovered = true;
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)));
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.ToLowerInvariant() == locationType.ToLowerInvariant()));
location.TypeChangeTimer = typeChangeTimer;
location.MissionsCompleted = missionsCompleted;
if (showNotifications && prevLocationType != location.Type)
{
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase));
var change = prevLocationType.CanChangeTo.Find(c =>
c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant());
if (change != null)
{
ChangeLocationType(location, prevLocationName, change);
@@ -43,6 +43,7 @@ namespace Barotrauma
protected string originalName;
protected string identifier;
protected ContentPackage contentPackage;
public Sprite sprite;
@@ -242,10 +243,7 @@ namespace Barotrauma
/// <param name="identifier">The identifier of the item (if null, the identifier is ignored and the search is done only based on the name)</param>
public static MapEntityPrefab Find(string name, string identifier = null, bool showErrorMessages = true)
{
if (name != null)
{
name = name.ToLowerInvariant();
}
if (name != null) name = name.ToLowerInvariant();
foreach (MapEntityPrefab prefab in List)
{
if (identifier != null)
@@ -256,17 +254,12 @@ namespace Barotrauma
}
else
{
if (string.IsNullOrEmpty(name)) { return prefab; }
if (string.IsNullOrEmpty(name)) return prefab;
}
}
if (!string.IsNullOrEmpty(name))
{
if (prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) ||
prefab.originalName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
(prefab.Aliases != null && prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))))
{
return prefab;
}
if (prefab.Name.ToLowerInvariant() == name || prefab.originalName.ToLowerInvariant() == name || (prefab.Aliases != null && prefab.Aliases.Any(a => a.ToLowerInvariant() == name))) return prefab;
}
}
@@ -289,9 +282,27 @@ namespace Barotrauma
/// <summary>
/// Check if the name or any of the aliases of this prefab match the given name.
/// </summary>
public bool NameMatches(string name, StringComparison comparisonType) => originalName.Equals(name, comparisonType) || (Aliases != null && Aliases.Any(a => a.Equals(name, comparisonType)));
public bool NameMatches(string name, bool caseSensitive = false)
{
if (caseSensitive)
{
return this.originalName == name || (Aliases != null && Aliases.Any(a => a == name));
}
else
{
name = name.ToLowerInvariant();
return this.originalName.ToLowerInvariant() == name || (Aliases != null && Aliases.Any(a => a.ToLowerInvariant() == name));
}
}
public bool NameMatches(IEnumerable<string> allowedNames, StringComparison comparisonType) => allowedNames.Any(n => NameMatches(n, comparisonType));
public bool NameMatches(IEnumerable<string> allowedNames, bool caseSensitive = false)
{
foreach (string name in allowedNames)
{
if (NameMatches(name, caseSensitive)) return true;
}
return false;
}
public bool IsLinkAllowed(MapEntityPrefab target)
{
@@ -260,15 +260,15 @@ namespace Barotrauma
}
#if CLIENT
if (subElement.GetAttributeBool("fliphorizontal", false))
if (subElement.GetAttributeBool("fliphorizontal", false))
sp.sprite.effects = SpriteEffects.FlipHorizontally;
if (subElement.GetAttributeBool("flipvertical", false))
if (subElement.GetAttributeBool("flipvertical", false))
sp.sprite.effects = SpriteEffects.FlipVertically;
#endif
sp.canSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
sp.canSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
if (subElement.Attribute("name") == null && !string.IsNullOrWhiteSpace(sp.Name))
{
sp.sprite.Name = sp.Name;
@@ -286,10 +286,12 @@ namespace Barotrauma
sp.BackgroundSprite.RelativeOrigin = subElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
}
#if CLIENT
if (subElement.GetAttributeBool("fliphorizontal", false)) { sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally; }
if (subElement.GetAttributeBool("flipvertical", false)) { sp.BackgroundSprite.effects = SpriteEffects.FlipVertically; }
sp.BackgroundSpriteColor = subElement.GetAttributeColor("color", Color.White);
if (subElement.GetAttributeBool("fliphorizontal", false))
sp.BackgroundSprite.effects = SpriteEffects.FlipHorizontally;
if (subElement.GetAttributeBool("flipvertical", false))
sp.BackgroundSprite.effects = SpriteEffects.FlipVertically;
#endif
break;
}
}
@@ -77,18 +77,7 @@ namespace Barotrauma
private SubmarineBody subBody;
public readonly Dictionary<Submarine, DockingPort> ConnectedDockingPorts;
public IEnumerable<Submarine> DockedTo
{
get
{
if (ConnectedDockingPorts == null) { yield break; }
foreach (Submarine sub in ConnectedDockingPorts.Keys)
{
yield return sub;
}
}
}
public readonly List<Submarine> DockedTo;
private static Vector2 lastPickedPosition;
private static float lastPickedFraction;
@@ -453,7 +442,7 @@ namespace Barotrauma
}
}
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
DockedTo = new List<Submarine>();
FreeID();
}
@@ -511,7 +500,7 @@ namespace Barotrauma
LeftBehindSubDockingPortOccupied = false;
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("linkedsubmarine")) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "linkedsubmarine") { continue; }
if (subElement.Attribute("location") == null) { continue; }
subsLeftBehind = true;
@@ -572,32 +561,27 @@ namespace Barotrauma
/// <summary>
/// Returns a rect that contains the borders of this sub and all subs docked to it
/// </summary>
public Rectangle GetDockedBorders(List<Submarine> checkd=null)
public Rectangle GetDockedBorders()
{
if (checkd == null) { checkd = new List<Submarine>(); }
checkd.Add(this);
Rectangle dockedBorders = Borders;
dockedBorders.Y -= dockedBorders.Height;
var connectedSubs = DockedTo.Where(s => !checkd.Contains(s) && !s.IsOutpost).ToList();
var connectedSubs = GetConnectedSubs();
foreach (Submarine dockedSub in connectedSubs)
{
//use docking ports instead of world position to determine
//borders, as world position will not necessarily match where
//the subs are supposed to go
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
if (expectedLocation == null) { continue; }
if (dockedSub == this) continue;
Rectangle dockedSubBorders = dockedSub.GetDockedBorders(checkd);
dockedSubBorders.Location += MathUtils.ToPoint(expectedLocation.Value);
Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition;
Rectangle dockedSubBorders = dockedSub.Borders;
dockedSubBorders.Y -= dockedSubBorders.Height;
dockedSubBorders.Location += MathUtils.ToPoint(diff);
dockedBorders.Y = -dockedBorders.Y;
dockedSubBorders.Y = -dockedSubBorders.Y;
dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders);
dockedBorders.Y = -dockedBorders.Y;
}
dockedBorders.Y += dockedBorders.Height;
return dockedBorders;
}
@@ -1147,37 +1131,25 @@ namespace Barotrauma
prevPosition = position;
}
public void SetPosition(Vector2 position, List<Submarine> checkd=null)
public void SetPosition(Vector2 position)
{
if (!MathUtils.IsValid(position)) return;
if (checkd == null) { checkd = new List<Submarine>(); }
if (checkd.Contains(this)) { return; }
checkd.Add(this);
subBody.SetPosition(position);
foreach (Submarine dockedSub in DockedTo)
foreach (Submarine sub in loaded)
{
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
if (expectedLocation == null) { continue; }
if (sub != this && sub.Submarine == this)
{
sub.SetPosition(position + sub.WorldPosition);
sub.Submarine = null;
}
dockedSub.SetPosition(position + expectedLocation.Value, checkd);
}
//Level.Loaded.SetPosition(-position);
//prevPosition = position;
}
public static Vector2? CalculateDockOffset(Submarine sub, Submarine dockedSub)
{
Item myPort = sub.ConnectedDockingPorts.ContainsKey(dockedSub) ? sub.ConnectedDockingPorts[dockedSub].Item : null;
if (myPort == null) { return null; }
Item theirPort = dockedSub.ConnectedDockingPorts.ContainsKey(sub) ? dockedSub.ConnectedDockingPorts[sub].Item : null;
if (theirPort == null) { return null; }
return (myPort.Position - sub.HiddenSubPosition) - (theirPort.Position - dockedSub.HiddenSubPosition);
}
public void Translate(Vector2 amount)
{
if (amount == Vector2.Zero || !MathUtils.IsValid(amount)) return;
@@ -1264,17 +1236,9 @@ namespace Barotrauma
{
if (Path.GetFullPath(savedSubmarines[i].filePath) == fullPath)
{
if (savedSubmarines[i] == MainSub)
{
savedSubmarines.Remove(savedSubmarines[i]);
}
else
{
savedSubmarines[i].Dispose();
}
savedSubmarines[i].Dispose();
}
}
if (File.Exists(filePath))
{
var sub = new Submarine(filePath);
@@ -1592,7 +1556,7 @@ namespace Barotrauma
foreach (Hull hull in matchingHulls)
{
if (string.IsNullOrEmpty(hull.RoomName) || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(hull.RoomName) || !hull.RoomName.ToLowerInvariant().Contains("roomname."))
{
hull.RoomName = hull.CreateRoomName();
}
@@ -1769,7 +1733,7 @@ namespace Barotrauma
if (MainSub == this) MainSub = null;
if (MainSubs[1] == this) MainSubs[1] = null;
ConnectedDockingPorts?.Clear();
DockedTo?.Clear();
}
public void Dispose()
@@ -121,15 +121,9 @@ namespace Barotrauma
idCardTags = new string[0];
#if CLIENT
if (iconSprites == null)
if (iconTexture == null)
{
iconSprites = new Dictionary<SpawnType, Sprite>()
{
{ SpawnType.Path, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,0,128,128)) },
{ SpawnType.Human, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,0,128,128)) },
{ SpawnType.Enemy, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256,0,128,128)) },
{ SpawnType.Cargo, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384,0,128,128)) }
};
iconTexture = Sprite.LoadTexture("Content/Map/waypointIcons.png");
}
#endif
@@ -154,12 +148,21 @@ namespace Barotrauma
return clone;
}
public static bool GenerateSubWaypoints(Submarine submarine)
public override bool IsMouseOn(Vector2 position)
{
#if CLIENT
if (IsHidden()) return false;
#endif
return base.IsMouseOn(position);
}
public static void GenerateSubWaypoints(Submarine submarine)
{
if (!Hull.hullList.Any())
{
DebugConsole.ThrowError("Couldn't generate waypoints: no hulls found.");
return false;
return;
}
List<WayPoint> existingWaypoints = WayPointList.FindAll(wp => wp.spawnType == SpawnType.Path);
@@ -462,8 +465,6 @@ namespace Barotrauma
{
door.Body.Enabled = false;
}
return true;
}
private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null)
@@ -653,7 +654,7 @@ namespace Barotrauma
{
w.assignedJob =
JobPrefab.Get(jobIdentifier) ??
JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase));
JobPrefab.Prefabs.Find(jp => jp.Name.ToLowerInvariant() == jobIdentifier);
}
w.ladderId = (ushort)element.GetAttributeInt("ladders", 0);
@@ -51,7 +51,7 @@ namespace Barotrauma.Networking
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("command", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "command") continue;
string commandName = subElement.GetAttributeString("name", "");
DebugConsole.Command command = DebugConsole.FindCommand(commandName);
@@ -22,8 +22,6 @@ namespace Barotrauma.Networking
RESPONSE_STARTGAME, //tell the server whether you're ready to start
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
ERROR //tell the server that an error occurred
}
enum ClientNetObject
@@ -62,7 +60,6 @@ namespace Barotrauma.Networking
QUERY_STARTGAME, //ask the clients whether they're ready to start
STARTGAME, //start a new round
STARTGAMEFINALIZE, //finalize round initialization
ENDGAME,
TRAITOR_MESSAGE,
@@ -138,7 +138,7 @@ namespace Barotrauma.Networking
{
return (a == null) == (b == null);
}
return a.ToString().Equals(b.ToString(), StringComparison.OrdinalIgnoreCase);
return a.ToString().Equals(b.ToString(), StringComparison.InvariantCulture);
}
}
@@ -759,7 +759,7 @@ namespace Barotrauma.Networking
private set;
}
[Serialize(600.0f, true)]
[Serialize(120.0f, true)]
public float KickAFKTime
{
get;
@@ -70,15 +70,14 @@ namespace Barotrauma.Steam
return Steamworks.SteamClient.Name;
}
public static bool OverlayCustomURL(string url)
public static void OverlayCustomURL(string url)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return false;
return;
}
Steamworks.SteamFriends.OpenWebOverlay(url);
return true;
}
public static bool UnlockAchievement(string achievementName)
@@ -679,7 +679,7 @@ namespace Barotrauma
{
foreach (XElement subElement in configElement.Elements())
{
if (!subElement.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Name.ToString().ToLowerInvariant() != "upgrade") { continue; }
var upgradeVersion = new Version(subElement.GetAttributeString("gameversion", "0.0.0.0"));
if (savedVersion >= upgradeVersion) { continue; }
@@ -570,7 +570,7 @@ namespace Barotrauma
{
bool hexFailed = true;
stringColor = stringColor.Trim();
if (stringColor.Length > 0 && stringColor[0] == '#')
if (stringColor[0]=='#')
{
stringColor = stringColor.Substring(1);
@@ -329,10 +329,10 @@ namespace Barotrauma
{
foreach (XElement subElement in SourceElement.Elements())
{
if (subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
if (subElement.Name.ToString().ToLowerInvariant() == "override")
{
string language = subElement.GetAttributeString("language", "");
if (TextManager.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase))
if (TextManager.Language.ToLower() == language.ToLower())
{
return subElement;
}
@@ -1,27 +1,17 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class DelayedListElement
{
public readonly DelayedEffect Parent;
public readonly Entity Entity;
public readonly Vector2? WorldPosition;
public readonly List<ISerializableEntity> Targets;
public DelayedEffect Parent;
public Entity Entity;
public Vector2? WorldPosition;
public List<ISerializableEntity> Targets;
public float StartTimer;
public DelayedListElement(DelayedEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float delay, Vector2? worldPosition)
{
Parent = parentEffect;
Entity = parentEntity;
Targets = new List<ISerializableEntity>(targets);
StartTimer = delay;
WorldPosition = worldPosition;
}
}
class DelayedEffect : StatusEffect
{
@@ -37,18 +27,28 @@ namespace Barotrauma
public override void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
{
if (this.type != type || !HasRequiredItems(entity)) { return; }
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.FirstOrDefault() == target)) { return; }
if (targetIdentifiers != null && !IsValidTarget(target)) { return; }
if (!HasRequiredConditions(target.ToEnumerable())) { return; }
if (this.type != type || !HasRequiredItems(entity)) return;
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.FirstOrDefault() == target)) return;
if (targetIdentifiers != null && !IsValidTarget(target)) return;
if (!HasRequiredConditions(new List<ISerializableEntity>() { target })) return;
DelayList.Add(new DelayedListElement(this, entity, target.ToEnumerable(), delay, worldPosition));
DelayedListElement element = new DelayedListElement
{
Parent = this,
StartTimer = delay,
Entity = entity,
WorldPosition = worldPosition,
Targets = new List<ISerializableEntity>() { target }
};
DelayList.Add(element);
}
public override void Apply(ActionType type, float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Vector2? worldPosition = null)
{
if (this.type != type || !HasRequiredItems(entity)) { return; }
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) { return; }
if (this.type != type || !HasRequiredItems(entity)) return;
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) return;
currentTargets.Clear();
foreach (ISerializableEntity target in targets)
@@ -61,9 +61,18 @@ namespace Barotrauma
currentTargets.Add(target);
}
if (!HasRequiredConditions(currentTargets)) { return; }
if (!HasRequiredConditions(currentTargets)) return;
DelayList.Add(new DelayedListElement(this, entity, currentTargets, delay, worldPosition));
DelayedListElement element = new DelayedListElement
{
Parent = this,
StartTimer = delay,
Entity = entity,
WorldPosition = worldPosition,
Targets = currentTargets
};
DelayList.Add(element);
}
public static void Update(float deltaTime)
@@ -79,7 +88,7 @@ namespace Barotrauma
element.StartTimer -= deltaTime;
if (element.StartTimer > 0.0f) { continue; }
if (element.StartTimer > 0.0f) continue;
element.Parent.Apply(1.0f, element.Entity, element.Targets, element.WorldPosition);
DelayList.Remove(element);
@@ -51,8 +51,7 @@ namespace Barotrauma
// Only used by attacks
public readonly bool TargetSelf;
// Only used by conditionals targeting an item (makes the conditional check the item/character whose inventory this item is inside)
public readonly bool TargetContainer;
private readonly string[] afflictionNames = new string[] { "internaldamage", "bleeding", "burn", "oxygenlow", "bloodloss", "pressure", "stun", "husk", "afflictionhusk", "huskinfection" };
private readonly int cancelStatusEffect;
@@ -63,7 +62,6 @@ namespace Barotrauma
{
case "targetitemcomponent":
case "targetself":
case "targetcontainer":
return false;
default:
return true;
@@ -134,7 +132,6 @@ namespace Barotrauma
}
TargetItemComponentName = attribute.Parent.GetAttributeString("targetitemcomponent", "");
TargetContainer = attribute.Parent.GetAttributeBool("targetcontainer", false);
TargetSelf = attribute.Parent.GetAttributeBool("targetself", false);
foreach (XElement subElement in attribute.Parent.Elements())
@@ -153,9 +150,13 @@ namespace Barotrauma
if (!Enum.TryParse(AttributeName, true, out Type))
{
if (AfflictionPrefab.Prefabs.Any(p => p.Identifier.Equals(AttributeName, StringComparison.OrdinalIgnoreCase)))
if (afflictionNames.Any(n => n == AttributeName))
{
Type = ConditionType.Affliction;
if (AttributeName == "husk" || AttributeName == "huskaffliction")
{
AttributeName = "huskinfection";
}
}
else
{
@@ -172,7 +173,8 @@ namespace Barotrauma
public bool Matches(ISerializableEntity target)
{
string valStr = AttributeValue.ToString();
string valStr = AttributeValue.ToString();
switch (Type)
{
case ConditionType.PropertyValue:
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -10,27 +9,11 @@ namespace Barotrauma
{
class DurationListElement
{
public readonly StatusEffect Parent;
public readonly Entity Entity;
public readonly List<ISerializableEntity> Targets;
public Character User { get; private set; }
public StatusEffect Parent;
public Entity Entity;
public List<ISerializableEntity> Targets;
public float Timer;
public DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float duration, Character user)
{
Parent = parentEffect;
Entity = parentEntity;
Targets = new List<ISerializableEntity>(targets);
Timer = duration;
User = user;
}
public void Reset(float duration, Character newUser)
{
Timer = duration;
User = newUser;
}
public Character User;
}
partial class StatusEffect
@@ -71,7 +54,7 @@ namespace Barotrauma
//backwards compatibility
DebugConsole.ThrowError("Error in StatusEffect config (" + element.ToString() + ") - use item identifier instead of the name.");
string itemPrefabName = element.GetAttributeString("name", "");
ItemPrefab = ItemPrefab.Prefabs.Find(m => m.NameMatches(itemPrefabName, StringComparison.OrdinalIgnoreCase) || m.Tags.Contains(itemPrefabName));
ItemPrefab = ItemPrefab.Prefabs.Find(m => m.NameMatches(itemPrefabName) || m.Tags.Contains(itemPrefabName));
if (ItemPrefab == null)
{
DebugConsole.ThrowError("Error in StatusEffect \""+ parentDebugName + "\" - item prefab \"" + itemPrefabName + "\" not found.");
@@ -374,7 +357,7 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers instead of names.");
string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, StringComparison.OrdinalIgnoreCase));
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.ToLowerInvariant() == afflictionName);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
@@ -384,7 +367,7 @@ namespace Barotrauma
else
{
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase));
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.");
@@ -493,68 +476,41 @@ namespace Barotrauma
}
}
public bool HasRequiredConditions(IEnumerable<ISerializableEntity> targets)
{
return HasRequiredConditions(targets, targetingContainer: false);
}
private bool HasRequiredConditions(IEnumerable<ISerializableEntity> targets, bool targetingContainer)
public virtual bool HasRequiredConditions(List<ISerializableEntity> targets)
{
if (!propertyConditionals.Any()) { return true; }
if (requiredItems.Any() && requiredItems.All(ri => ri.MatchOnEmpty) && !targets.Any()) { return true; }
if (requiredItems.Any() && requiredItems.All(ri => ri.MatchOnEmpty) && targets.Count == 0) { return true; }
switch (conditionalComparison)
{
case PropertyConditional.Comparison.Or:
foreach (PropertyConditional pc in propertyConditionals)
foreach (ISerializableEntity target in targets)
{
if (pc.TargetContainer && !targetingContainer)
foreach (PropertyConditional pc in propertyConditionals)
{
var target = targets.FirstOrDefault(t => t is Item || t is ItemComponent);
var targetItem = target as Item ?? (target as ItemComponent)?.Item;
if (targetItem?.ParentInventory == null) { continue; }
if (targetItem.ParentInventory.Owner is Item container && HasRequiredConditions(container.AllPropertyObjects, targetingContainer: true)) { return true; }
if (targetItem.ParentInventory.Owner is Character character && HasRequiredConditions(character.ToEnumerable(), targetingContainer: true)) { return true; }
}
else
{
foreach (ISerializableEntity target in targets)
if (!string.IsNullOrEmpty(pc.TargetItemComponentName))
{
if (!string.IsNullOrEmpty(pc.TargetItemComponentName))
if (!(target is ItemComponent ic) || ic.Name != pc.TargetItemComponentName)
{
if (!(target is ItemComponent ic) || ic.Name != pc.TargetItemComponentName)
{
continue;
}
continue;
}
if (pc.Matches(target)) { return true; }
}
if (pc.Matches(target)) { return true; }
}
}
return false;
case PropertyConditional.Comparison.And:
foreach (PropertyConditional pc in propertyConditionals)
foreach (ISerializableEntity target in targets)
{
if (pc.TargetContainer && !targetingContainer)
foreach (PropertyConditional pc in propertyConditionals)
{
var target = targets.FirstOrDefault(t => t is Item || t is ItemComponent);
var targetItem = target as Item ?? (target as ItemComponent)?.Item;
if (targetItem?.ParentInventory == null) { return false; }
if (targetItem.ParentInventory.Owner is Item container && !HasRequiredConditions(container.AllPropertyObjects, targetingContainer: true)) { return false; }
if (targetItem.ParentInventory.Owner is Character character && !HasRequiredConditions(character.ToEnumerable(), targetingContainer: true)) { return false; }
}
else
{
foreach (ISerializableEntity target in targets)
if (!string.IsNullOrEmpty(pc.TargetItemComponentName))
{
if (!string.IsNullOrEmpty(pc.TargetItemComponentName))
if (!(target is ItemComponent ic) || ic.Name != pc.TargetItemComponentName)
{
if (!(target is ItemComponent ic) || ic.Name != pc.TargetItemComponentName)
{
continue;
}
continue;
}
if (!pc.Matches(target)) { return false; }
}
if (!pc.Matches(target)) { return false; }
}
}
return true;
@@ -604,26 +560,33 @@ namespace Barotrauma
public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
{
if (this.type != type || !HasRequiredItems(entity)) { return; }
if (this.type != type || !HasRequiredItems(entity)) return;
if (targetIdentifiers != null && !IsValidTarget(target)) { return; }
if (targetIdentifiers != null && !IsValidTarget(target)) return;
if (duration > 0.0f && !Stackable)
{
//ignore if not stackable and there's already an identical statuseffect
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.FirstOrDefault() == target);
existingEffect?.Reset(Math.Max(existingEffect.Timer, duration), user);
return;
if (existingEffect != null)
{
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
existingEffect.User = user;
return;
}
}
if (!HasRequiredConditions(target.ToEnumerable())) { return; }
Apply(deltaTime, entity, target.ToEnumerable(), worldPosition);
List<ISerializableEntity> targets = new List<ISerializableEntity> { target };
if (!HasRequiredConditions(targets)) return;
Apply(deltaTime, entity, targets, worldPosition);
}
protected readonly List<ISerializableEntity> currentTargets = new List<ISerializableEntity>();
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Vector2? worldPosition = null)
{
if (this.type != type) { return; }
if (this.type != type) return;
currentTargets.Clear();
foreach (ISerializableEntity target in targets)
@@ -638,20 +601,24 @@ namespace Barotrauma
if (targetIdentifiers != null && currentTargets.Count == 0) { return; }
if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) { return; }
if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) return;
if (duration > 0.0f && !Stackable)
{
//ignore if not stackable and there's already an identical statuseffect
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(currentTargets));
existingEffect?.Reset(Math.Max(existingEffect.Timer, duration), user);
return;
if (existingEffect != null)
{
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
existingEffect.User = user;
return;
}
}
Apply(deltaTime, entity, currentTargets, worldPosition);
}
protected void Apply(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Vector2? worldPosition = null)
protected void Apply(float deltaTime, Entity entity, List<ISerializableEntity> targets, Vector2? worldPosition = null)
{
if (lifeTime > 0)
{
@@ -718,7 +685,16 @@ namespace Barotrauma
if (duration > 0.0f)
{
DurationList.Add(new DurationListElement(this, entity, targets, duration, user));
DurationListElement element = new DurationListElement
{
Parent = this,
Timer = duration,
Entity = entity,
Targets = targets,
User = user
};
DurationList.Add(element);
}
else
{
@@ -756,7 +732,7 @@ namespace Barotrauma
character.LastDamageSource = entity;
foreach (Limb limb in character.AnimController.Limbs)
{
limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
limb.character.DamageLimb(position, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability);
//only apply non-limb-specific afflictions to the first limb
if (!affliction.Prefab.LimbSpecific) { break; }
@@ -765,7 +741,7 @@ namespace Barotrauma
else if (target is Limb limb)
{
if (limb.character.Removed || limb.Removed) { continue; }
limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
limb.character.DamageLimb(position, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability);
}
}
@@ -788,7 +764,6 @@ namespace Barotrauma
{
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAmount);
targetCharacter.TryAdjustAttackerSkill(user, targetCharacter.Vitality - prevVitality);
#if SERVER
GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, user, prevVitality - targetCharacter.Vitality);
#endif
@@ -878,7 +853,7 @@ namespace Barotrauma
ApplyProjSpecific(deltaTime, entity, targets, hull, position);
}
partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition);
partial void ApplyProjSpecific(float deltaTime, Entity entity, List<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition);
private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime)
{
@@ -959,12 +934,12 @@ namespace Barotrauma
if (target is Character character)
{
if (character.Removed) { continue; }
character.AddDamage(character.WorldPosition, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User);
character.AddDamage(character.WorldPosition, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attacker: element.User);
}
else if (target is Limb limb)
{
if (limb.character.Removed || limb.Removed) { continue; }
limb.character.DamageLimb(limb.WorldPosition, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
limb.character.DamageLimb(limb.WorldPosition, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
}
}
@@ -985,7 +960,6 @@ namespace Barotrauma
{
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAffliction.Second * deltaTime);
targetCharacter.TryAdjustAttackerSkill(element.User, targetCharacter.Vitality - prevVitality);
#if SERVER
GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.User, prevVitality - targetCharacter.Vitality);
#endif
@@ -216,13 +216,6 @@ namespace Barotrauma
}
}
#if DEBUG
if (GameMain.Config != null && GameMain.Config.TextManagerDebugModeEnabled)
{
return textTag;
}
#endif
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(textTag);
@@ -30,7 +30,7 @@ namespace Barotrauma
{
doc = XMLExtensions.TryLoadXml(filePath);
if (doc != null) { break; }
if (filePath.Equals("content/texts/englishvanilla.xml", StringComparison.OrdinalIgnoreCase))
if (filePath.ToLowerInvariant() == "content/texts/englishvanilla.xml")
{
//try fixing legacy EnglishVanilla path
string newPath = "Content/Texts/English/EnglishVanilla.xml";
@@ -118,10 +118,6 @@ namespace Barotrauma
for (int i = 0; i < subDirs.Length; i++)
{
if (i == subDirs.Length - 1 && string.IsNullOrEmpty(subDirs[i]))
{
break;
}
string enumPath = string.IsNullOrEmpty(filename) ? "./" : filename;
List<string> filePaths = Directory.GetFileSystemEntries(enumPath).Select(s => Path.GetFileName(s)).ToList();
if (filePaths.Any(s => s.Equals(subDirs[i], StringComparison.Ordinal)))
@@ -151,7 +147,7 @@ namespace Barotrauma
public static string RemoveInvalidFileNameChars(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars().Concat(new char[] {':', ';'});
var invalidChars = Path.GetInvalidFileNameChars();
foreach (char invalidChar in invalidChars)
{
fileName = fileName.Replace(invalidChar.ToString(), "");