f8b0295...0671290

This commit is contained in:
Joonas Rikkonen
2019-03-18 22:59:45 +02:00
parent 23687fbf2f
commit 63eb4d64e5
103 changed files with 1378 additions and 4692 deletions
@@ -359,7 +359,7 @@ namespace Barotrauma
if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f))
{
//steer straight up if very deep
steeringManager.SteeringManual(deltaTime, Vector2.UnitY * speed);
steeringManager.SteeringManual(deltaTime, Vector2.UnitY);
return;
}
@@ -652,7 +652,7 @@ namespace Barotrauma
{
//wander around randomly and decrease the priority faster if no path is found
if (selectedTargetMemory != null) selectedTargetMemory.Priority -= deltaTime * 10.0f;
steeringManager.SteeringWander(speed);
steeringManager.SteeringWander();
}
else if (indoorsSteering.CurrentPath.Finished)
{
@@ -131,23 +131,20 @@ namespace Barotrauma
ignorePlatforms = true;
}
}
if (Character.IsClimbing && PathSteering.InLadders && PathSteering.IsNextLadderSameAsCurrent)
{
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
}
}
Character.AnimController.IgnorePlatforms = ignorePlatforms;
// Suspect that this causes issues when trying to exit from the ladders -> could try to check if the next node is ladder?
//if (Character.IsClimbing)
//{
// Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
//}
Vector2 targetMovement = AnimController.TargetMovement;
if (!Character.AnimController.InWater)
{
targetMovement = new Vector2(
Character.AnimController.TargetMovement.X,
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
}
float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed);
@@ -227,10 +224,6 @@ namespace Barotrauma
PropagateHullSafety(Character, Character.CurrentHull);
}
}
private void ReportProblems()
{
if (GameMain.Client != null) return;
protected void ReportProblems()
{
@@ -252,7 +245,7 @@ namespace Barotrauma
foreach (Character c in Character.CharacterList)
{
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
(c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC)))
{
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
@@ -297,10 +290,13 @@ namespace Barotrauma
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
float damage = attackResult.Damage;
if (damage < 0) { return; }
if (damage <= 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
if (objectiveManager.CurrentOrder == null)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
return;
}
if (IsFriendly(attacker))
@@ -314,7 +310,10 @@ namespace Barotrauma
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
{
// Don't react to damage done by friendly ai, because we know that it's accidental
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
if (objectiveManager.CurrentOrder == null)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
return;
}
float currentVitality = Character.CharacterHealth.Vitality;
@@ -322,8 +321,10 @@ namespace Barotrauma
if (dmgPercentage < currentVitality / 10)
{
// Don't react to a minor amount of (accidental) dmg done by friendly characters
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
return;
if (objectiveManager.CurrentOrder == null)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
}
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
{
@@ -465,7 +466,9 @@ namespace Barotrauma
// Even the smallest fire reduces the safety by 50%
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || e.TeamID != character.TeamID));
int enemyCount = Character.CharacterList.Count(e =>
e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious &&
(e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC)));
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
@@ -44,8 +44,8 @@ namespace Barotrauma
}
public bool InLadders => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Ladders != null;
private bool IsNextNodeLadder => currentPath.NextNode != null && currentPath.CurrentNode.Ladders != null;
private bool IsNextLadderSameAsCurrent => IsNextNodeLadder && currentPath.CurrentNode.Ladders == currentPath.NextNode.Ladders;
public bool IsNextNodeLadder => currentPath.NextNode != null && currentPath.CurrentNode.Ladders != null;
public bool IsNextLadderSameAsCurrent => IsNextNodeLadder && currentPath.CurrentNode.Ladders == currentPath.NextNode.Ladders;
public bool InStairs => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Stairs != null;
@@ -213,7 +213,7 @@ namespace Barotrauma
}
else if (character.AnimController.InWater)
{
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < collider.radius * collider.radius)
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
{
currentPath.SkipToNextNode();
}
@@ -221,11 +221,14 @@ namespace Barotrauma
else
{
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize();
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
float characterHeight = colliderSize.Y + character.AnimController.ColliderHeightFromFloor;
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
bool isAboveVerticalPosition = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + 1.5f;
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
if (horizontalDistance < collider.radius * 2 && isAboveVerticalPosition && isNotTooHigh)
if (horizontalDistance < collider.radius * 3 && isAboveFeet && isNotTooHigh)
{
currentPath.SkipToNextNode();
}
@@ -31,6 +31,7 @@ namespace Barotrauma
List<Tuple<string, string, string>> contentPackageFiles = new List<Tuple<string, string, string>>();
foreach (string filePath in filePaths)
{
if (Path.GetExtension(filePath) == ".csv") continue; // .csv files are not supported
XDocument doc = XMLExtensions.TryLoadXml(filePath);
if (doc == null || doc.Root == null) continue;
string language = doc.Root.GetAttributeString("Language", "English");
@@ -41,6 +42,7 @@ namespace Barotrauma
List<Tuple<string, string, string>> translationFiles = new List<Tuple<string, string, string>>();
foreach (string filePath in Directory.GetFiles(Path.Combine("Content", "NPCConversations")))
{
if (Path.GetExtension(filePath) == ".csv") continue; // .csv files are not supported
XDocument doc = XMLExtensions.TryLoadXml(filePath);
if (doc == null || doc.Root == null) continue;
string language = doc.Root.GetAttributeString("Language", "English");
@@ -139,6 +139,7 @@ namespace Barotrauma
}
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false;
public virtual void OnSelected() { }
protected abstract void Act(float deltaTime);
@@ -117,6 +117,17 @@ namespace Barotrauma
return weapon;
}
private void Unequip()
{
if (character.SelectedItems.Contains(Weapon))
{
if (!Weapon.AllowedSlots.Contains(InvSlotType.Any) || !character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Any }))
{
Weapon.Drop(character);
}
}
}
private bool Equip(float deltaTime)
{
if (!character.SelectedItems.Contains(Weapon))
@@ -241,7 +252,19 @@ namespace Barotrauma
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
public override bool IsCompleted() => Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0.0f;
public override bool IsCompleted()
{
bool completed = Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0;
if (completed)
{
if (Weapon != null)
{
Unequip();
}
}
return completed;
}
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
public override float GetPriority(AIObjectiveManager objectiveManager) => Enemy == null || Enemy.Removed || Enemy.IsDead ? 0 : 100;
@@ -18,7 +18,7 @@ namespace Barotrauma
private bool isCompleted;
public bool IgnoreAlreadyContainedItems;
public string[] ignoredContainerIdentifiers;
public Func<Item, float> GetItemPriority;
@@ -95,7 +95,7 @@ namespace Barotrauma
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
{
GetItemPriority = GetItemPriority,
IgnoreContainedItems = IgnoreAlreadyContainedItems
ignoredContainerIdentifiers = ignoredContainerIdentifiers
};
AddSubObjective(getItemObjective);
return;
@@ -78,16 +78,18 @@ namespace Barotrauma
foreach (FireSource fs in targetHull.FireSources)
{
// TODO: check if in the same room?
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
if (!character.IsClimbing && (inRange || useExtinquisherTimer > 0.0f))
if (targetHull == character.CurrentHull && (inRange || useExtinquisherTimer > 0.0f))
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
character.CursorPosition = fs.Position;
character.SetInput(InputType.Aim, false, true);
character.AIController.SteeringManager.Reset();
if (!character.IsClimbing)
{
character.AIController.SteeringManager.Reset();
}
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
@@ -1,5 +1,5 @@
using Barotrauma.Items.Components;
using System;
using Microsoft.Xna.Framework;
using System.Linq;
namespace Barotrauma
@@ -10,31 +10,26 @@ namespace Barotrauma
public override bool ForceRun => true;
private AIObjective subObjective;
private string gearTag;
public override bool IsCompleted()
{
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || character.Inventory.Items[i] == null) continue;
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || character.Inventory.Items[i] == null) { continue; }
if (character.Inventory.Items[i].HasTag(gearTag))
{
var containedItems = character.Inventory.Items[i].ContainedItems;
if (containedItems == null) continue;
if (containedItems == null) { continue; }
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
if (oxygenTank != null) return true;
if (oxygenTank != null) { return true; }
}
}
return false;
}
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit)
: base(character, "")
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "")
{
gearTag = needDivingSuit ? "divingsuit" : "diving";
}
@@ -54,12 +49,11 @@ namespace Barotrauma
else
{
var containedItems = item.ContainedItems;
if (containedItems == null) return;
if (containedItems == null) { return; }
//check if there's an oxygen tank in the mask/suit
foreach (Item containedItem in containedItems)
{
if (containedItem == null) continue;
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(character);
@@ -69,36 +63,21 @@ namespace Barotrauma
//we've got an oxygen source inside the mask/suit, all good
return;
}
}
}
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
{
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
}
}
if (subObjective != null)
{
subObjective.TryComplete(deltaTime);
}
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (character.AnimController.CurrentHull == null) return 100.0f;
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 100.0f - character.Oxygen;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveFindDivingGear;
}
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
public override float GetPriority(AIObjectiveManager objectiveManager) => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
}
}
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma
{
@@ -25,7 +24,7 @@ namespace Barotrauma
private float searchHullTimer;
private AIObjectiveGoTo goToObjective;
private AIObjective divingGearObjective;
private AIObjectiveFindDivingGear divingGearObjective;
public AIObjectiveFindSafety(Character character) : base(character, "") { }
@@ -35,13 +34,12 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
var currentHull = character.AnimController.CurrentHull;
if (HumanAIController.NeedsDivingGear(currentHull))
if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
{
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
if ((divingGearObjective == null || !divingGearObjective.CanBeCompleted) && !hasEquipment)
if (!hasEquipment)
{
// If the previous objective cannot be completed, create a new and try again.
divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit);
}
}
@@ -55,9 +53,15 @@ namespace Barotrauma
}
else if (divingGearObjective.CanBeCompleted)
{
// If diving gear objective is active, wait for it to complete.
// If diving gear objective is active and can be completed, wait for it to complete.
return;
}
else
{
divingGearObjective = null;
// Reset the timer so that we get a safe hull target.
searchHullTimer = 0;
}
}
if (unreachableClearTimer > 0)
@@ -83,17 +87,18 @@ namespace Barotrauma
{
if (goToObjective.Target != bestHull)
{
goToObjective = new AIObjectiveGoTo(bestHull, character)
// If we need diving gear, we should already have it, if possible.
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
{
AllowGoingOutside = true
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
};
}
}
else
{
goToObjective = new AIObjectiveGoTo(bestHull, character)
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
{
AllowGoingOutside = true
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
};
}
}
@@ -110,6 +115,7 @@ namespace Barotrauma
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective = null;
SteeringManager.SteeringWander();
}
}
else if (currentHull != null)
@@ -126,6 +132,11 @@ namespace Barotrauma
foreach (Character enemy in Character.CharacterList)
{
//don't run from friendly NPCs
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
@@ -170,13 +181,15 @@ namespace Barotrauma
if (unreachable.Contains(hull)) { continue; }
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
hullSafety = HumanAIController.GetHullSafety(hull, character);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
hullSafety *= distanceFactor;
// Each unsafe node reduces the hull safety value.
// Ignore current hull, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Unreachable) { continue; }
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
@@ -31,7 +31,7 @@ namespace Barotrauma
public override bool IsCompleted()
{
return leak.Open <= 0.0f || leak.Removed || pathUnreachable;
return leak.Open <= 0.0f || leak.Removed;
}
public override bool CanBeCompleted => !abandon && base.CanBeCompleted;
@@ -94,16 +94,15 @@ namespace Barotrauma
if (repairTool == null) { return; }
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
var humanoidController = character.AnimController as HumanoidAnimController;
// TODO: use the collider size/reach?
if (!character.AnimController.InWater && humanoidController != null && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{
((HumanoidAnimController)character.AnimController).Crouching = true;
HumanAIController.AnimController.Crouching = true;
}
float armLength = humanoidController != null ? ConvertUnits.ToDisplayUnits(humanoidController.ArmLength) : 100;
bool cannotReach = gapDiff.Length() > armLength + repairTool.Range;
float reach = HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range);
bool cannotReach = ConvertUnits.ToSimUnits(gapDiff.Length()) > reach;
if (cannotReach)
{
if (gotoObjective != null)
@@ -116,7 +115,10 @@ namespace Barotrauma
}
else
{
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character);
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)
{
CloseEnough = reach * 0.75f
};
if (!subObjectives.Contains(gotoObjective))
{
AddSubObjective(gotoObjective);
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -14,19 +15,15 @@ namespace Barotrauma
//can be either tags or identifiers
private string[] itemIdentifiers;
private Item targetItem, moveToTarget;
private int currSearchIndex;
public bool IgnoreContainedItems;
public string[] ignoredContainerIdentifiers;
private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private bool equip;
private HashSet<Item> ignoredItems = new HashSet<Item>();
private bool canBeCompleted = true;
public override bool CanBeCompleted => canBeCompleted;
@@ -36,25 +33,19 @@ namespace Barotrauma
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false)
: base(character, "")
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "")
{
currSearchIndex = -1;
this.equip = equip;
this.targetItem = targetItem;
}
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false)
: this(character, new string[] { itemIdentifier }, equip)
{
}
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { }
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false)
: base(character, "")
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "")
{
currSearchIndex = -1;
this.equip = equip;
@@ -108,12 +99,12 @@ namespace Barotrauma
FindTargetItem();
if (targetItem == null || moveToTarget == null)
{
// TODO: cannot be completed?
character?.AIController?.SteeringManager?.Reset();
SteeringManager.SteeringWander();
return;
}
if (Vector2.Distance(character.Position, moveToTarget.Position) < targetItem.InteractDistance * 2.0f)
if (moveToTarget.CurrentHull == character.CurrentHull &&
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
{
int targetSlot = -1;
if (equip)
@@ -169,9 +160,13 @@ namespace Barotrauma
}
goToObjective.TryComplete(deltaTime);
if (!goToObjective.CanBeCompleted) targetItem = null;
if (!goToObjective.CanBeCompleted)
{
targetItem = null;
moveToTarget = null;
ignoredItems.Add(targetItem);
}
}
}
/// <summary>
@@ -196,22 +191,30 @@ namespace Barotrauma
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
if (ignoredItems.Contains(item)) { continue; }
if (item.Submarine == null) { continue; }
else if (item.Submarine.TeamID != character.TeamID) { continue; }
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (item.CurrentHull == null || item.Condition <= 0.0f) continue;
if (IgnoreContainedItems && item.Container != null) continue;
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) continue;
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
//if the item is inside a character's inventory, don't steal it unless the character is dead
if (item.ParentInventory is CharacterInventory)
{
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) continue;
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
}
//if the item is inside an item, which is inside a character's inventory, don't steal it
Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) continue;
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
}
float itemPriority = 0.0f;
@@ -219,13 +222,13 @@ namespace Barotrauma
{
//ignore if the item has zero priority
itemPriority = GetItemPriority(item);
if (itemPriority <= 0.0f) continue;
if (itemPriority <= 0.0f) { continue; }
}
itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
//ignore if the item has a lower priority than the currently selected one
if (moveToTarget != null && itemPriority < currItemPriority) continue;
if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
currItemPriority = itemPriority;
@@ -186,11 +186,11 @@ namespace Barotrauma
bool completed = false;
float allowedDistance = 0.5f;
float allowedDistance = CloseEnough;
if (Target is Item item)
{
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.InteractDistance), allowedDistance);
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.InteractDistance), CloseEnough);
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
}
else if (Target is Character targetCharacter)
@@ -200,6 +200,7 @@ namespace Barotrauma
// Check that there is no unsafe or forbidden hulls on the way to the target
// Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Unreachable) { continue; }
if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
}
@@ -30,9 +30,7 @@ namespace Barotrauma
base.Update(objectiveManager, deltaTime);
if (ignoreListTimer > ignoreListClearInterval)
{
ignoreList.Clear();
ignoreListTimer = 0;
UpdateTargets();
Reset();
}
else
{
@@ -67,16 +65,24 @@ namespace Barotrauma
}
}
private void Reset()
{
ignoreList.Clear();
ignoreListTimer = 0;
UpdateTargets();
}
public override void OnSelected()
{
Reset();
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (character.Submarine == null) { return 0; }
if (targets.None()) { return 0; }
float avg = targets.Average(t => Average(t));
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority - MathHelper.Max(0, AIObjectiveManager.OrderPriority - avg);
}
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, avg / 100);
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100);
}
protected void UpdateTargets()
@@ -67,6 +67,7 @@ namespace Barotrauma
private AIObjective GetCurrentObjective()
{
var previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault();
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this))
{
@@ -76,6 +77,10 @@ namespace Barotrauma
{
CurrentObjective = firstObjective;
}
if (previousObjective != CurrentObjective)
{
CurrentObjective?.OnSelected();
}
return CurrentObjective;
}
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
namespace Barotrauma
{
@@ -84,7 +85,14 @@ namespace Barotrauma
}
if (character.CanInteractWith(Item))
{
OperateRepairTool(deltaTime);
if (repairTool == null)
{
FindRepairTool();
}
if (repairTool != null)
{
OperateRepairTool(deltaTime);
}
foreach (Repairable repairable in Item.Repairables)
{
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
@@ -121,13 +129,17 @@ namespace Barotrauma
subObjectives.Remove(goToObjective);
}
goToObjective = new AIObjectiveGoTo(Item, character);
if (repairTool != null)
{
goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f;
}
AddSubObjective(goToObjective);
}
}
private void OperateRepairTool(float deltaTime)
private RepairTool repairTool;
private void FindRepairTool()
{
// Operate repair tool, if required.
foreach (Repairable repairable in Item.Repairables)
{
foreach (var kvp in repairable.requiredItems)
@@ -138,19 +150,29 @@ namespace Barotrauma
{
if (requiredItem.MatchesItem(item))
{
var repairTool = item.GetComponent<RepairTool>();
if (repairTool != null)
{
character.CursorPosition = Item.Position;
character.SetInput(InputType.Aim, false, true);
repairTool.Use(deltaTime, character);
return;
}
repairTool = item.GetComponent<RepairTool>();
}
}
}
}
}
}
private void OperateRepairTool(float deltaTime)
{
character.CursorPosition = Item.Position;
character.SetInput(InputType.Aim, false, true);
Vector2 fromToolToTarget = Item.Position - repairTool.Item.Position;
if (fromToolToTarget.LengthSquared() < MathUtils.Pow(repairTool.Range / 2, 2))
{
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - Item.SimPosition) / 2);
}
if (character.IsClimbing ||
VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
{
repairTool.Use(deltaTime, character);
}
}
}
}
@@ -51,8 +51,14 @@ namespace Barotrauma
{
foreach (Repairable repairable in item.Repairables)
{
if (item.Condition > repairable.ShowRepairUIThreshold) { ignore = true; }
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character)) { ignore = true; }
if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold)
{
ignore = true;
}
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character))
{
ignore = true;
}
if (ignore) { break; }
}
}
@@ -203,7 +203,7 @@ namespace Barotrauma
{
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed);
return new SteeringPath();
return new SteeringPath(true);
}
closestDist = 0.0f;
@@ -225,8 +225,12 @@ namespace Barotrauma
//if searching for a path inside the sub, make sure the waypoint is visible
if (insideSubmarine)
{
// TODO: for some reason fails to find the path when the sub is flooding. Disabling this check helps fixes it, but we can't disable it
var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition);
if (body != null && body.UserData is Structure) continue;
if (body != null && body.UserData is Structure)
{
continue;
}
}
closestDist = dist;
@@ -237,10 +241,9 @@ namespace Barotrauma
if (endNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed);
return new SteeringPath();
return new SteeringPath(true);
}
var path = FindPath(startNode, endNode);
return path;
@@ -266,7 +269,7 @@ namespace Barotrauma
if (startNode == null || endNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed);
return new SteeringPath();
return new SteeringPath(true);
}
return FindPath(startNode, endNode);
@@ -290,13 +293,12 @@ namespace Barotrauma
node.G = 0.0f;
node.H = 0.0f;
}
start.state = 1;
while (true)
{
PathNode currNode = null;
float dist = 10000.0f;
float dist = float.MaxValue;
foreach (PathNode node in nodes)
{
if (node.state != 1) continue;
@@ -364,7 +366,7 @@ namespace Barotrauma
if (end.state == 0 || end.Parent == null)
{
//path not found
//DebugConsole.NewMessage("Pathfinding error: path not found", Color.DarkRed);
return new SteeringPath(true);
}
@@ -683,12 +683,6 @@ namespace Barotrauma
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
}
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
@@ -1059,6 +1059,8 @@ namespace Barotrauma
}
}
private float prevFootPos;
void UpdateClimbing()
{
if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent<Ladder>() == null)
@@ -1137,15 +1139,34 @@ namespace Barotrauma
handPos.X - Dir * 0.05f,
bottomPos + ColliderHeightFromFloor - stepHeight * 2.7f - ladderSimPos.Y);
MoveLimb(leftFoot,
new Vector2(footPos.X,
(slide ? footPos.Y : MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight) + ladderSimPos.Y),
15.5f, true);
if (slide)
{
MoveLimb(leftFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true);
MoveLimb(rightFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true);
}
else
{
float leftFootPos = MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight;
float prevLeftFootPos = MathUtils.Round(prevFootPos + stepHeight, stepHeight * 2.0f) - stepHeight;
MoveLimb(leftFoot, new Vector2(footPos.X, leftFootPos + ladderSimPos.Y), 15.5f, true);
MoveLimb(rightFoot,
new Vector2(footPos.X,
(slide ? footPos.Y : MathUtils.Round(footPos.Y, stepHeight * 2.0f)) + ladderSimPos.Y),
15.5f, true);
float rightFootPos = MathUtils.Round(footPos.Y, stepHeight * 2.0f);
float prevRightFootPos = MathUtils.Round(prevFootPos, stepHeight * 2.0f);
MoveLimb(rightFoot, new Vector2(footPos.X, rightFootPos + ladderSimPos.Y), 15.5f, true);
#if CLIENT
if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: leftFoot.WorldPosition);
leftFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: rightFoot.WorldPosition);
rightFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
#endif
prevFootPos = footPos.Y;
}
//apply torque to the legs to make the knees bend
Limb leftLeg = GetLimb(LimbType.LeftLeg);
@@ -1781,14 +1802,24 @@ namespace Barotrauma
float c = Vector2.Distance(pos, shoulderPos);
c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f);
float ang2 = MathUtils.VectorToAngle(pos - shoulderPos) + MathHelper.PiOver2;
float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + MathHelper.PiOver2;
float armAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c);
float handAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c);
float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir;
float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir;
//make sure the arm angle "has the same number of revolutions" as the arm
while (arm.Rotation - armAngle > MathHelper.Pi)
{
armAngle += MathHelper.TwoPi;
}
while (arm.Rotation - armAngle < -MathHelper.Pi)
{
armAngle -= MathHelper.TwoPi;
}
arm?.body.SmoothRotate((ang2 - armAngle * Dir), 20.0f * force * arm.Mass);
forearm?.body.SmoothRotate((ang2 + handAngle * Dir), 20.0f * force * forearm.Mass);
hand?.body.SmoothRotate((ang2 + handAngle * Dir), 100.0f * force * hand.Mass);
arm?.body.SmoothRotate((armAngle - upperArmAngle), 20.0f * force * arm.Mass, wrapAngle: false);
forearm?.body.SmoothRotate((armAngle + lowerArmAngle), 20.0f * force * forearm.Mass, wrapAngle: false);
hand?.body.SmoothRotate((armAngle + lowerArmAngle), 100.0f * force * hand.Mass, wrapAngle: false);
}
private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle)
@@ -86,7 +86,10 @@ namespace Barotrauma
public bool onGround;
private bool ignorePlatforms;
protected float ColliderHeightFromFloor => ConvertUnits.ToSimUnits(RagdollParams.ColliderHeightFromFloor) * RagdollParams.JointScale;
/// <summary>
/// In sim units. Joint scale applied.
/// </summary>
public float ColliderHeightFromFloor => ConvertUnits.ToSimUnits(RagdollParams.ColliderHeightFromFloor) * RagdollParams.JointScale;
public Structure Stairs;
@@ -1324,6 +1327,19 @@ namespace Barotrauma
}
if (errorMsg != null)
{
if (character.IsRemotePlayer)
{
errorMsg += " Ragdoll controlled remotely.";
}
if (SimplePhysicsEnabled)
{
errorMsg += " Simple physics enabled.";
}
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
@@ -1343,7 +1359,6 @@ namespace Barotrauma
SetInitialLimbPositions();
return;
}
UpdateProjSpecific(deltaTime);
}
partial void UpdateProjSpecific(float deltaTime);
@@ -120,8 +120,6 @@ namespace Barotrauma
private List<StatusEffect> statusEffects = new List<StatusEffect>();
private List<float> speedMultipliers = new List<float>();
private List<StatusEffect> statusEffects = new List<StatusEffect>();
public Entity ViewTarget
{
get;
@@ -330,7 +328,8 @@ namespace Barotrauma
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
private float ragdollingLockTimer;
public bool IsRagdolled;
public bool IsForceRagdolled;
public bool dontFollowCursor;
@@ -1877,8 +1876,6 @@ namespace Barotrauma
}
speechImpedimentSet = false;
if (needsAir)
{
bool protectedFromPressure = PressureProtection > 0.0f;
@@ -1935,9 +1932,23 @@ namespace Barotrauma
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true;
if (IsForceRagdolled)
{
IsRagdolled = IsForceRagdolled;
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
}
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f))
{
if (ragdollingLockTimer > 0.0f)
{
ragdollingLockTimer -= deltaTime;
}
else
{
bool wasRagdolled = IsRagdolled;
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; }
}
}
UpdateSightRange();
UpdateSoundRange();
@@ -19,6 +19,10 @@ namespace Barotrauma
public readonly List<string> AllowedDialogTags;
private float commonness;
public float Commonness
{
get { return commonness; }
}
public NPCPersonalityTrait(XElement element)
{
@@ -457,20 +457,6 @@ namespace Barotrauma
}
}));
#if CLIENT && WINDOWS
commands.Add(new Command("copyitemnames", "", (string[] args) =>
{
StringBuilder sb = new StringBuilder();
foreach (MapEntityPrefab mp in MapEntityPrefab.List)
{
if (!(mp is ItemPrefab)) continue;
sb.AppendLine(mp.Name);
}
System.Windows.Clipboard.SetText(sb.ToString());
}));
#endif
commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) =>
{
if (args.Length == 0) return;
@@ -1435,58 +1421,25 @@ namespace Barotrauma
#if SERVER
if (GameMain.Server != null)
{
case "cursor":
spawnPos = cursorPos;
break;
case "inventory":
spawnInventory = controlledCharacter?.Inventory;
break;
case "cargo":
var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
break;
default:
//Check if last arg matches the name of an in-game player
if (GameMain.Server != null)
{
var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower());
if (client == null)
{
NewMessage("No player found with the name \"" + args.Last() + "\". Spawning item at random location. If the player you want to give the item to has a space in their name, try surrounding their name with quotes (\").", Color.Red);
break;
}
else if (client.Character == null)
{
errorMsg = "The player \"" + args.Last() + "\" is connected, but hasn't spawned yet.";
return;
}
else
{
//If the last arg matches the name of an in-game player, set the destination to their inventory.
spawnInventory = client.Character.Inventory;
break;
}
}
else
{
var matchingCharacter = FindMatchingCharacter(args.Skip(1).ToArray());
if (matchingCharacter?.Inventory != null) spawnInventory = matchingCharacter.Inventory;
}
break;
var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower());
if (client != null)
{
extraParams += 1;
itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant();
if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory;
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
}
}
#endif
}
string itemName = args[0];
var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
//Check again if the item can be found again after having checked for a character
if (itemPrefab == null)
{
errorMsg = "Item \"" + itemName + "\" not found!";
return;
}
if (spawnPos == null && spawnInventory == null)
if ((spawnPos == null || spawnPos == Vector2.Zero) && spawnInventory == null)
{
var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
@@ -1495,6 +1448,7 @@ namespace Barotrauma
if (spawnPos != null)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos);
}
else if (spawnInventory != null)
{
@@ -12,7 +12,7 @@ namespace Barotrauma
public bool CheatsEnabled;
const int InitialMoney = 4500;
const int InitialMoney = 4700;
protected bool watchmenSpawned;
protected Character startWatchman, endWatchman;
@@ -45,8 +45,6 @@ namespace Barotrauma
private static byte currentCampaignID;
private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
public byte CampaignID
{
get; private set;
@@ -217,7 +215,7 @@ namespace Barotrauma
break;
}
}
#if SERVER
characterData.Clear();
string characterDataPath = GetCharacterDataSavePath();
var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
@@ -226,33 +224,8 @@ namespace Barotrauma
{
characterData.Add(new CharacterCampaignData(subElement));
}
#endif
}
public override void Save(XElement element)
{
XElement modeElement = new XElement("MultiPlayerCampaign",
new XAttribute("money", Money),
new XAttribute("cheatsenabled", CheatsEnabled));
Map.Save(modeElement);
element.Add(modeElement);
//save character data to a separate file
string characterDataPath = GetCharacterDataSavePath();
XDocument characterDataDoc = new XDocument(new XElement("CharacterData"));
foreach (CharacterCampaignData cd in characterData)
{
characterDataDoc.Root.Add(cd.Save());
}
try
{
characterDataDoc.Save(characterDataPath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving multiplayer campaign characters to \"" + characterDataPath + "\" failed!", e);
}
lastSaveID++;
}
}
}
@@ -36,34 +36,6 @@ namespace Barotrauma
public bool VSyncEnabled { get; set; }
public bool EnableSplashScreen { get; set; }
public int ParticleLimit { get; set; }
public int ParticleLimit { get; set; }
public float LightMapScale { get; set; }
public bool SpecularityEnabled { get; set; }
public bool ChromaticAberrationEnabled { get; set; }
public int ParticleLimit { get; set; }
public float LightMapScale { get; set; }
public bool SpecularityEnabled { get; set; }
public bool ChromaticAberrationEnabled { get; set; }
public bool MuteOnFocusLost { get; set; }
public enum VoiceMode
{
Disabled,
PushToTalk,
Activity
};
public VoiceMode VoiceSetting { get; set; }
public string VoiceCaptureDevice { get; set; }
public int ParticleLimit { get; set; }
public int ParticleLimit { get; set; }
@@ -196,7 +168,7 @@ namespace Barotrauma
}
}
private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f;
private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f, microphoneVolume = 1.0f;
public float SoundVolume
{
@@ -239,6 +211,14 @@ namespace Barotrauma
}
}
public float MicrophoneVolume
{
get { return microphoneVolume; }
set
{
microphoneVolume = MathHelper.Clamp(value, 0.1f, 5.0f);
}
}
public string Language
{
get { return TextManager.Language; }
@@ -397,10 +377,6 @@ namespace Barotrauma
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
keyMapping[(int)InputType.Up] = new KeyOrMouse(Keys.W);
keyMapping[(int)InputType.Down] = new KeyOrMouse(Keys.S);
@@ -509,70 +485,6 @@ namespace Barotrauma
SelectedContentPackages.Add(matchingContentPackage);
}
break;
}
}
TextManager.LoadTextPacks(SelectedContentPackages);
//display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths)
{
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
}
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
{
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
.Replace("[packagename]", incompatiblePackage.Name)
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
.Replace("[gameversion]", GameMain.Version.ToString()));
}
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
TextManager.LoadTextPacks(SelectedContentPackages);
//display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths)
{
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
}
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
{
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
.Replace("[packagename]", incompatiblePackage.Name)
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
.Replace("[gameversion]", GameMain.Version.ToString()));
}
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
if (!SelectedContentPackages.Any())
{
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
if (availablePackage != null)
{
SelectedContentPackages.Add(availablePackage);
}
}
@@ -1021,369 +933,7 @@ namespace Barotrauma
gMode = new XElement("graphicsmode");
doc.Root.Add(gMode);
}
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
}
else
{
gMode.ReplaceAttributes(
new XAttribute("width", GraphicsWidth),
new XAttribute("height", GraphicsHeight),
new XAttribute("vsync", VSyncEnabled),
new XAttribute("displaymode", windowMode));
}
XElement gSettings = doc.Root.Element("graphicssettings");
if (gSettings == null)
{
gSettings = new XElement("graphicssettings");
doc.Root.Add(gSettings);
}
gSettings.ReplaceAttributes(
new XAttribute("particlelimit", ParticleLimit),
new XAttribute("lightmapscale", LightMapScale),
new XAttribute("specularity", SpecularityEnabled),
new XAttribute("chromaticaberration", ChromaticAberrationEnabled),
new XAttribute("losmode", LosMode),
new XAttribute("hudscale", HUDScale),
new XAttribute("inventoryscale", InventoryScale));
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
if (contentPackage.Path.Contains(vanillaContentPackagePath))
{
doc.Root.Add(new XElement("contentpackage", new XAttribute("path", contentPackage.Path)));
break;
}
}
var keyMappingElement = new XElement("keymapping");
doc.Root.Add(keyMappingElement);
for (int i = 0; i < keyMapping.Length; i++)
{
if (keyMapping[i].MouseButton == null)
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].Key));
}
else
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].MouseButton));
}
}
var gameplay = new XElement("gameplay");
var jobPreferences = new XElement("jobpreferences");
foreach (string jobName in JobPreferences)
{
jobPreferences.Add(new XElement("job", new XAttribute("identifier", jobName)));
}
gameplay.Add(jobPreferences);
doc.Root.Add(gameplay);
var playerElement = new XElement("player",
new XAttribute("name", defaultPlayerName ?? ""),
new XAttribute("headindex", CharacterHeadIndex),
new XAttribute("gender", CharacterGender),
new XAttribute("race", CharacterRace),
new XAttribute("hairindex", CharacterHairIndex),
new XAttribute("beardindex", CharacterBeardIndex),
new XAttribute("moustacheindex", CharacterMoustacheIndex),
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
doc.Root.Add(playerElement);
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true,
NewLineOnAttributes = true
};
try
{
using (var writer = XmlWriter.Create(savePath, settings))
{
doc.WriteTo(writer);
writer.Flush();
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving game settings failed.", e);
GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace);
}
}
#endregion
#region Load PlayerConfig
// TODO: DRY
public void LoadPlayerConfig()
{
XDocument doc = XMLExtensions.LoadXml(playerSavePath);
if (doc == null || doc.Root == null)
{
ShowUserStatisticsPrompt = true;
SaveNewPlayerConfig();
return;
}
Language = doc.Root.GetAttributeString("language", Language);
AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates);
sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", true);
XElement graphicsMode = doc.Root.Element("graphicsmode");
GraphicsWidth = graphicsMode.GetAttributeInt("width", GraphicsWidth);
GraphicsHeight = graphicsMode.GetAttributeInt("height", GraphicsHeight);
VSyncEnabled = graphicsMode.GetAttributeBool("vsync", VSyncEnabled);
XElement graphicsSettings = doc.Root.Element("graphicssettings");
ParticleLimit = graphicsSettings.GetAttributeInt("particlelimit", ParticleLimit);
LightMapScale = MathHelper.Clamp(graphicsSettings.GetAttributeFloat("lightmapscale", LightMapScale), 0.1f, 1.0f);
SpecularityEnabled = graphicsSettings.GetAttributeBool("specularity", SpecularityEnabled);
ChromaticAberrationEnabled = graphicsSettings.GetAttributeBool("chromaticaberration", ChromaticAberrationEnabled);
HUDScale = graphicsSettings.GetAttributeFloat("hudscale", HUDScale);
InventoryScale = graphicsSettings.GetAttributeFloat("inventoryscale", InventoryScale);
var losModeStr = graphicsSettings.GetAttributeString("losmode", "Transparent");
if (!Enum.TryParse(losModeStr, out losMode))
{
losMode = LosMode.Transparent;
}
#if CLIENT
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
GraphicsWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
GraphicsHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
}
#endif
var windowModeStr = graphicsMode.GetAttributeString("displaymode", "Fullscreen");
if (!Enum.TryParse(windowModeStr, out windowMode))
{
windowMode = WindowMode.Fullscreen;
}
XElement audioSettings = doc.Root.Element("audio");
if (audioSettings != null)
{
SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume);
MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume);
VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled");
VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", "");
NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45);
var voiceSetting = VoiceMode.Disabled;
if (Enum.TryParse(voiceSettingStr, out voiceSetting))
{
VoiceSetting = voiceSetting;
}
}
useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", useSteamMatchmaking);
requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", requireSteamAuthentication);
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount);
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "keymapping":
foreach (XAttribute attribute in subElement.Attributes())
{
if (Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType))
{
if (int.TryParse(attribute.Value.ToString(), out int mouseButton))
{
keyMapping[(int)inputType] = new KeyOrMouse(mouseButton);
}
else
{
if (Enum.TryParse(attribute.Value.ToString(), true, out Keys key))
{
keyMapping[(int)inputType] = new KeyOrMouse(key);
}
}
}
}
break;
case "gameplay":
jobPreferences = new List<string>();
foreach (XElement ele in subElement.Element("jobpreferences").Elements("job"))
{
string jobIdentifier = ele.GetAttributeString("identifier", "");
if (string.IsNullOrEmpty(jobIdentifier)) continue;
jobPreferences.Add(jobIdentifier);
}
break;
case "player":
defaultPlayerName = subElement.GetAttributeString("name", defaultPlayerName);
CharacterHeadIndex = subElement.GetAttributeInt("headindex", CharacterHeadIndex);
if (Enum.TryParse(subElement.GetAttributeString("gender", "none"), true, out Gender g))
{
CharacterGender = g;
}
if (Enum.TryParse(subElement.GetAttributeString("race", "white"), true, out Race r))
{
CharacterRace = r;
}
else
{
CharacterRace = Race.White;
}
CharacterHairIndex = subElement.GetAttributeInt("hairindex", CharacterHairIndex);
CharacterBeardIndex = subElement.GetAttributeInt("beardindex", CharacterBeardIndex);
CharacterMoustacheIndex = subElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex);
CharacterFaceAttachmentIndex = subElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex);
break;
case "tutorials":
foreach (XElement tutorialElement in subElement.Elements())
{
CompletedTutorialNames.Add(tutorialElement.GetAttributeString("name", ""));
}
break;
}
}
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
if (keyMapping[(int)inputType] == null)
{
DebugConsole.ThrowError("Key binding for the input type \"" + inputType + " not set!");
keyMapping[(int)inputType] = new KeyOrMouse(Keys.D1);
}
}
UnsavedSettings = false;
selectedContentPackagePaths = new HashSet<string>();
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "contentpackage":
string path = System.IO.Path.GetFullPath(subElement.GetAttributeString("path", ""));
selectedContentPackagePaths.Add(path);
break;
}
}
LoadContentPackages(selectedContentPackagePaths);
}
public void ReloadContentPackages()
{
LoadContentPackages(selectedContentPackagePaths);
}
private void LoadContentPackages(IEnumerable<string> contentPackagePaths)
{
var missingPackagePaths = new List<string>();
var incompatiblePackages = new List<ContentPackage>();
SelectedContentPackages.Clear();
foreach (string path in contentPackagePaths)
{
var matchingContentPackage = ContentPackage.List.Find(cp => System.IO.Path.GetFullPath(cp.Path) == path);
if (matchingContentPackage == null)
{
missingPackagePaths.Add(path);
}
else if (!matchingContentPackage.IsCompatible())
{
incompatiblePackages.Add(matchingContentPackage);
}
else
{
SelectedContentPackages.Add(matchingContentPackage);
}
}
TextManager.LoadTextPacks(SelectedContentPackages);
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
if (!SelectedContentPackages.Any())
{
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
if (availablePackage != null)
{
SelectedContentPackages.Add(availablePackage);
}
}
//save to get rid of the invalid selected packages in the config file
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
//display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths)
{
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
}
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
{
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
.Replace("[packagename]", incompatiblePackage.Name)
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
.Replace("[gameversion]", GameMain.Version.ToString()));
}
}
#endregion
#region Save PlayerConfig
public void SaveNewPlayerConfig()
{
XDocument doc = new XDocument();
UnsavedSettings = false;
if (doc.Root == null)
{
doc.Add(new XElement("config"));
}
doc.Root.Add(
new XAttribute("language", TextManager.Language),
new XAttribute("masterserverurl", MasterServerUrl),
new XAttribute("autocheckupdates", AutoCheckUpdates),
new XAttribute("musicvolume", musicVolume),
new XAttribute("soundvolume", soundVolume),
new XAttribute("verboselogging", VerboseLogging),
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
new XAttribute("enablesplashscreen", EnableSplashScreen),
new XAttribute("usesteammatchmaking", useSteamMatchmaking),
new XAttribute("quickstartsub", QuickStartSubmarineName),
new XAttribute("requiresteamauthentication", requireSteamAuthentication),
new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems),
new XAttribute("aimassistamount", aimAssistAmount));
if (!ShowUserStatisticsPrompt)
{
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
}
XElement gMode = doc.Root.Element("graphicsmode");
if (gMode == null)
{
gMode = new XElement("graphicsmode");
doc.Root.Add(gMode);
}
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
@@ -641,7 +641,7 @@ namespace Barotrauma.Items.Components
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
}
//make sure the left hull is linked to the gap first (gap logic assumes that the first hull is the one to the left)
if (doorGap.linkedTo[0].Rect.X > doorGap.linkedTo[1].Rect.X)
if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].Rect.X > doorGap.linkedTo[1].Rect.X)
{
var temp = doorGap.linkedTo[0];
doorGap.linkedTo[0] = doorGap.linkedTo[1];
@@ -659,7 +659,7 @@ namespace Barotrauma.Items.Components
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
}
//make sure the upper hull is linked to the gap first (gap logic assumes that the first hull is above the second one)
if (doorGap.linkedTo[0].Rect.Y < doorGap.linkedTo[1].Rect.Y)
if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].Rect.Y < doorGap.linkedTo[1].Rect.Y)
{
var temp = doorGap.linkedTo[0];
doorGap.linkedTo[0] = doorGap.linkedTo[1];
@@ -51,6 +51,11 @@ namespace Barotrauma.Items.Components
public PhysicsBody Body { get; private set; }
private float RepairThreshold
{
get { return item.GetComponent<Repairable>()?.ShowRepairUIThreshold ?? 0.0f; }
}
private float stuck;
[Serialize(0.0f, false)]
public float Stuck
@@ -207,7 +212,7 @@ namespace Barotrauma.Items.Components
public override bool HasRequiredItems(Character character, bool addMessage)
{
if (item.Condition <= 0.0f) return true; //For repairing
if (item.Condition <= RepairThreshold) return true; //For repairing
//this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing.
return requiredItems.Any() ? base.HasRequiredItems(character, addMessage) : canBePicked;
@@ -215,12 +220,12 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
return item.Condition <= 0.0f ? true : base.Pick(picker);
return item.Condition <= RepairThreshold ? true : base.Pick(picker);
}
public override bool OnPicked(Character picker)
{
if (item.Condition <= 0.0f) return true; //repairs
if (item.Condition <= RepairThreshold) return true; //repairs
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function
#if CLIENT
@@ -232,7 +237,7 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
//can only be selected if the item is broken
return item.Condition <= 0.0f;
return item.Condition <= RepairThreshold;
}
public override void Update(float deltaTime, Camera cam)
@@ -141,6 +141,7 @@ namespace Barotrauma.Items.Components
partial void UseProjSpecific(float deltaTime);
private List<FireSource> fireSourcesInRange = new List<FireSource>();
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
{
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
@@ -159,7 +160,7 @@ namespace Barotrauma.Items.Components
if (ExtinguishAmount > 0.0f && item.CurrentHull != null)
{
List<FireSource> fireSourcesInRange = new List<FireSource>();
fireSourcesInRange.Clear();
//step along the ray in 10% intervals, collecting all fire sources in the range
for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f)
{
@@ -200,7 +201,7 @@ namespace Barotrauma.Items.Components
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user);
//if the next section is small enough, apply the effect to it as well
//(to make it easier to fix a small "left-over" section)
for (int i = -1; i < 2; i += 2)
@@ -252,11 +253,11 @@ namespace Barotrauma.Items.Components
}
}
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition);
private float sinTime;
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
Gap leak = objective.OperateTarget as Gap;
@@ -280,18 +281,26 @@ namespace Barotrauma.Items.Components
{
Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f);
standPos = leak.WorldPosition + standPos * Range;
// TODO: check if too close to the stand pos -> move away so that the tool can hit the target and not through it?
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
}
else
{
// TODO: sometimes stuck here, if too close to the target
//close enough -> stop moving
character.AIController.SteeringManager.Reset();
if (dist < Range / 2)
{
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
}
else if (!character.IsClimbing)
{
// Close enough -> stop if not in ladders.
// In ladders, we most likely want to move back and forth, because we cannot aim -> if the leak is on the side, it should get fixed.
character.AIController.SteeringManager.Reset();
}
}
character.CursorPosition = leak.Position;
sinTime += deltaTime;
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
character.SetInput(InputType.Aim, false, true);
// Press the trigger only when the tool is approximately facing the target.
@@ -300,12 +309,17 @@ namespace Barotrauma.Items.Components
{
Use(deltaTime, character);
}
else
{
sinTime -= deltaTime * 2;
}
// TODO: fix until the wall is fixed?
bool leakFixed = leak.Open <= 0.0f || leak.Removed;
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
if (leakFixed && leak.FlowTargetHull != null)
{
sinTime = 0;
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
{
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
@@ -535,7 +535,6 @@ namespace Barotrauma.Items.Components
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return 0.0f;
}
float average = skillSuccessSum / requiredSkills.Count;
float skillSuccessSum = 0.0f;
for (int i = 0; i < requiredSkills.Count; i++)
@@ -155,29 +155,5 @@ namespace Barotrauma.Items.Components
}
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f));
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
if (item.CanClientAccess(c))
{
if (Math.Abs(newTargetForce - targetForce) > 0.01f)
{
GameServer.Log(c.Character.LogName + " set the force of " + item.Name + " to " + (int)(newTargetForce) + " %", ServerLog.MessageType.ItemInteraction);
}
targetForce = newTargetForce;
}
//notify all clients of the changed state
item.CreateServerEvent(this);
}
}
}
@@ -160,7 +160,6 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
if (AITarget != null) AITarget.Enabled = voltage > minVoltage || powerConsumption <= 0.0f;
#if CLIENT
light.ParentSub = item.Submarine;
@@ -177,12 +177,21 @@ namespace Barotrauma.Items.Components
Vector2 nodePos = refSub == null ?
newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (i == 0)
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
int newNodeIndex = 0;
if (nodes.Count > 1)
{
if (Vector2.DistanceSquared(nodes[nodes.Count-1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
{
newNodeIndex = nodes.Count;
}
}
if (newNodeIndex == 0)
{
nodes.Insert(0, nodePos);
}
@@ -433,9 +433,10 @@ namespace Barotrauma.Items.Components
if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill"))
{
ItemContainer container = null;
Item containerItem = null;
foreach (MapEntity e in item.linkedTo)
{
var containerItem = e as Item;
containerItem = e as Item;
if (containerItem == null) continue;
container = containerItem.GetComponent<ItemContainer>();
@@ -453,7 +454,7 @@ namespace Barotrauma.Items.Components
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container);
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
containShellObjective.MinContainedAmount = usableProjectileCount + 1;
containShellObjective.IgnoreAlreadyContainedItems = true;
containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
objective.AddSubObjective(containShellObjective);
return false;
}
@@ -1631,12 +1631,13 @@ namespace Barotrauma
SerializableProperty property = extraData[1] as SerializableProperty;
if (property != null)
{
var propertyOwner = allProperties.Find(p => p.Second == property);
if (allProperties.Count > 1)
{
msg.WriteRangedInteger(0, allProperties.Count - 1, allProperties.FindIndex(p => p.Second == property));
}
object value = property.GetValue(this);
object value = property.GetValue(propertyOwner.First);
if (value is string)
{
msg.Write((string)value);
@@ -89,7 +89,9 @@ namespace Barotrauma
}
CommonnessPerZone[zoneIndex] = zoneCommonness;
}
catch (Exception e)
hireableJobs = new List<Tuple<JobPrefab, float>>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -269,9 +269,6 @@ namespace Barotrauma
}
}
//remove orphans
Locations.RemoveAll(c => !connectedLocations.Contains(c));
for (int i = connections.Count - 1; i >= 0; i--)
{
i = Math.Min(i, connections.Count - 1);
@@ -428,7 +425,6 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
CurrentLocation.SelectedMissionIndex = missionIndex;
SelectedLocation = location;
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
@@ -134,7 +134,7 @@ namespace Barotrauma
public HashSet<string> Tags
{
get { return prefab; }
get { return prefab.Tags; }
}
protected Color spriteColor;
@@ -523,30 +523,6 @@ namespace Barotrauma
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
else
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
{
//no walls found at either side, just use the initial spawnpos and hope for the best
}
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f;
}
else if (maxX > Level.Loaded.Size.X)
{
//no wall found at right side, spawn to the right from the left-side wall
spawnPos.X = minX + minWidth + 100.0f;
}
else
{
//walls found at both sides, use their midpoint
spawnPos.X = (minX + maxX) / 2;
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
@@ -1016,26 +992,6 @@ namespace Barotrauma
return closest;
}
public List<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList);
public List<Gap> GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
public List<T> GetEntities<T>(bool includingConnectedSubs, List<T> list) where T : MapEntity
{
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs)
{
if (entity.Submarine == this) { return true; }
if (entity.Submarine == null) { return false; }
if (includingConnectedSubs)
{
return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID);
}
return false;
}
/// <summary>
/// Returns true if the sub is same as the other.
/// </summary>
@@ -700,6 +700,23 @@ namespace Barotrauma
Vector2 impulse = direction * impact * 0.5f;
impulse = impulse.ClampLength(5.0f);
if (!MathUtils.IsValid(impulse))
{
string errorMsg =
"Invalid impulse in SubmarineBody.ApplyImpact: " + impulse +
". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + ".";
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"SubmarineBody.ApplyImpact:InvalidImpulse",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
return;
}
#if CLIENT
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{
@@ -522,7 +522,12 @@ namespace Barotrauma
string errorMsg =
"Attempted to apply invalid " + valueName +
" to a physics body (userdata: " + userData +
"), value: " + value + "\n" + Environment.StackTrace;
"), value: " + value;
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
errorMsg += "\n" + Environment.StackTrace;
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
@@ -544,7 +549,12 @@ namespace Barotrauma
string errorMsg =
"Attempted to apply invalid " + valueName +
" to a physics body (userdata: " + userData +
"), value: " + value + "\n" + Environment.StackTrace;
"), value: " + value;
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
errorMsg += "\n" + Environment.StackTrace;
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
@@ -54,7 +54,7 @@ namespace Barotrauma
case 6:
return PlayerInput.MouseWheelDownClicked();
}
return false;
}
@@ -113,14 +113,7 @@ namespace Barotrauma
{
private bool hit, hitQueue;
private bool held, heldQueue;
#if CLIENT
private InputType inputType;
public Key(InputType inputType)
{
this.inputType = inputType;
}
private InputType inputType;
@@ -138,17 +131,6 @@ namespace Barotrauma
{
get { return binding; }
}
#endif
public void SetState()
{
hit = binding.IsHit();
if (hit) hitQueue = true;
held = binding.IsDown();
if (held) heldQueue = true;
}
#endif
public void SetState()
{
@@ -717,11 +717,6 @@ namespace Barotrauma
}
}
}
bool isNotClient = true;
#if CLIENT
isNotClient = GameMain.Client == null;
#endif
if (FireSize > 0.0f && entity != null)
{
@@ -153,49 +153,59 @@ namespace Barotrauma
string[] messages = serverMessage.Split('/');
for (int i = 0; i < messages.Length; i++)
try
{
if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate
for (int i = 0; i < messages.Length; i++)
{
if (messages[i].Contains(" ")) continue; // Spaces found, do not translate
string msg = Get(messages[i], true);
if (msg != null) // If a translation was found, otherwise use the original
if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate
{
messages[i] = msg;
}
}
else
{
string[] messageWithVariables = messages[i].Split('~');
string msg = Get(messageWithVariables[0], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
if (messages[i].Contains(" ")) continue; // Spaces found, do not translate
string msg = Get(messages[i], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
}
}
else
{
continue; // No translation found, probably caused by player input -> skip variable handling
}
string[] messageWithVariables = messages[i].Split('~');
string msg = Get(messageWithVariables[0], true);
// First index is always the message identifier -> start at 1
for (int j = 1; j < messageWithVariables.Length; j++)
{
string[] variableAndValue = messageWithVariables[j].Split('=');
messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
}
else
{
continue; // No translation found, probably caused by player input -> skip variable handling
}
// First index is always the message identifier -> start at 1
for (int j = 1; j < messageWithVariables.Length; j++)
{
string[] variableAndValue = messageWithVariables[j].Split('=');
messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]);
}
}
}
string translatedServerMessage = string.Empty;
for (int i = 0; i < messages.Length; i++)
{
translatedServerMessage += messages[i];
}
return translatedServerMessage;
}
string translatedServerMessage = string.Empty;
for (int i = 0; i < messages.Length; i++)
catch (IndexOutOfRangeException exception)
{
translatedServerMessage += messages[i];
string errorMsg = "Failed to translate server message \"" + serverMessage + "\".";
#if DEBUG
DebugConsole.ThrowError(errorMsg, exception);
#endif
GameAnalyticsManager.AddErrorEventOnce("TextManager.GetServerMessage:" + serverMessage, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return errorMsg;
}
return translatedServerMessage;
}
public static bool IsServerMessageWithVariables(string message)
@@ -33,7 +33,11 @@ namespace Barotrauma
texts.Add(infoName, infoList);
}
infoList.Add(subElement.ElementInnerText());
string text = subElement.ElementInnerText();
text = text.Replace("&amp;", "&");
text = text.Replace("&lt;", "<");
text = text.Replace("&gt;", ">");
infoList.Add(text);
}
}