aeafa16...4d3cf73

This commit is contained in:
Joonas Rikkonen
2019-03-18 22:57:05 +02:00
parent 3301bed442
commit 23687fbf2f
77 changed files with 2275 additions and 717 deletions
@@ -22,7 +22,6 @@ namespace Barotrauma
public const float HULL_SAFETY_THRESHOLD = 50;
// TODO: update the list when someone gives a report
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
private SteeringManager outsideSteering, insideSteering;
@@ -136,10 +135,11 @@ namespace Barotrauma
Character.AnimController.IgnorePlatforms = ignorePlatforms;
if (Character.IsClimbing)
{
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
}
// 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;
@@ -187,7 +187,7 @@ namespace Barotrauma
// Try to put the mask in an Any slot, and drop it if that fails
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
mask.Drop();
mask.Drop(Character);
}
}
}
@@ -198,7 +198,7 @@ namespace Barotrauma
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
{
// TODO: take the item where it was taken from?
extinguisherItem.Drop();
extinguisherItem.Drop(Character);
}
}
@@ -227,6 +227,10 @@ namespace Barotrauma
PropagateHullSafety(Character, Character.CurrentHull);
}
}
private void ReportProblems()
{
if (GameMain.Client != null) return;
protected void ReportProblems()
{
@@ -271,10 +275,6 @@ namespace Barotrauma
}
}
}
private void ReportProblems()
{
if (GameMain.Client != null) return;
private void UpdateSpeaking()
{
@@ -293,41 +293,68 @@ namespace Barotrauma
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
}
}
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
float totalDamage = attackResult.Damage;
if (totalDamage <= 0.0f || attacker == null) return;
if (attacker.SpeciesName == Character.SpeciesName)
float damage = attackResult.Damage;
if (damage < 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
return;
}
if (IsFriendly(attacker))
{
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
return;
}
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
{
// Don't attack characters that damage you while doing cpr, because let's assume that they are helping you.
// 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.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;
return;
}
float currentVitality = Character.CharacterHealth.Vitality;
float dmgPercentage = totalDamage / currentVitality * 100;
float dmgPercentage = damage / currentVitality * 100;
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.CurrentObjective is AIObjectiveCombat combatObjective)
{
if (combatObjective.Enemy != attacker)
{
// Replace the old objective with the new.
ObjectiveManager.Objectives.Remove(combatObjective);
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
}
}
else
{
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
}
}
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () =>
else
{
//the objective in the manager is not necessarily the same as the one we just instantiated,
//because the objective isn't added if there's already an identical objective in the manager
var combatObjective = objectiveManager.GetObjective<AIObjectiveCombat>();
combatObjective.MaxEnemyDamage = Math.Max(totalDamage, combatObjective.MaxEnemyDamage);
});
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
{
if (combatObjective.Enemy != attacker)
{
// Replace the old objective with the new.
ObjectiveManager.Objectives.Remove(combatObjective);
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
}
}
else
{
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
}
}
}
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
@@ -420,7 +447,8 @@ namespace Barotrauma
bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires;
bool ignoreWater = HasDivingSuit(Character);
bool ignoreOxygen = ignoreWater || HasDivingGear(Character);
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies: false);
bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat;
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
}
public static float GetHullSafety(Hull hull, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
@@ -443,5 +471,8 @@ namespace Barotrauma
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
return MathHelper.Clamp(safety * 100, 0, 100);
}
// TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species.
public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName;
}
}
@@ -15,7 +15,7 @@ namespace Barotrauma
public virtual bool KeepDivingGearOn => false;
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
protected float priority;
public float Priority { get; set; }
protected readonly Character character;
protected string option;
protected bool abandon;
@@ -26,6 +26,7 @@ namespace Barotrauma
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
public string Option
{
@@ -103,20 +104,20 @@ namespace Barotrauma
CurrentSubObjective.SortSubObjectives(objectiveManager);
}
public virtual float GetPriority(AIObjectiveManager objectiveManager) => priority;
public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority;
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
{
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
if (objectiveManager.CurrentOrder == this)
{
priority = AIObjectiveManager.OrderPriority;
Priority = AIObjectiveManager.OrderPriority;
}
else if (objectiveManager.CurrentObjective == this || subObjective == this)
{
priority += Devotion * deltaTime;
Priority += Devotion * deltaTime;
}
priority = MathHelper.Clamp(priority, 0, 100);
Priority = MathHelper.Clamp(Priority, 0, 100);
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
}
@@ -1,194 +1,268 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
class AIObjectiveCombat : AIObjective
{
public override string DebugTag => "combat";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
const float CoolDown = 10.0f;
//the largest amount of damage the enemy has inflicted on this character
//(may be higher than enemyStrength if the enemy is e.g. a human using items)
public float MaxEnemyDamage;
private Character enemy;
private AIObjectiveFindSafety escapeObjective;
public Character Enemy { get; private set; }
private Item _weapon;
private Item Weapon
{
get { return _weapon; }
set
{
_weapon = value;
_weaponComponent = null;
reloadWeaponObjective = null;
}
}
private ItemComponent _weaponComponent;
private ItemComponent WeaponComponent
{
get
{
if (_weaponComponent == null)
{
_weaponComponent =
Weapon.GetComponent<RangedWeapon>() as ItemComponent ??
Weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
Weapon.GetComponent<RepairTool>() as ItemComponent;
}
return _weaponComponent;
}
}
private AIObjectiveContainItem reloadWeaponObjective;
private Hull retreatTarget;
private AIObjectiveGoTo retreatObjective;
private float coolDownTimer;
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
{
this.enemy = enemy;
Enemy = enemy;
coolDownTimer = CoolDown;
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 0;
}
protected override void Act(float deltaTime)
{
coolDownTimer -= deltaTime;
var weapon = character.Inventory.FindItemByTag("weapon");
if (weapon == null)
if (Weapon != null && character.Inventory.Items.Contains(_weapon))
{
Weapon = null;
}
if (Weapon == null)
{
Weapon = GetWeapon();
}
if (Weapon == null)
{
Escape(deltaTime);
}
else
else if (Equip(deltaTime))
{
if (!character.SelectedItems.Contains(weapon))
if (Reload(deltaTime))
{
if (character.Inventory.TryPutItem(weapon, 3, true, false, character))
{
weapon.Equip(character);
}
else
{
//couldn't equip the item, escape
Escape(deltaTime);
return;
}
Attack(deltaTime);
}
}
if (!abandon)
{
Move(deltaTime);
}
}
//make sure the weapon is loaded
var weaponComponent =
weapon.GetComponent<RangedWeapon>() as ItemComponent ??
weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
weapon.GetComponent<RepairTool>() as ItemComponent;
if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
private Item GetWeapon()
{
_weaponComponent = null;
var weapon = character.Inventory.FindItemByTag("weapon");
if (weapon == null)
{
foreach (var item in character.Inventory.Items)
{
var containedItems = weapon.ContainedItems;
foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
if (item == null) { continue; }
foreach (var component in item.Components)
{
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (containedItem == null)
if (component is MeleeWeapon || component is RangedWeapon)
{
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
if (!newReloadWeaponObjective.IsDuplicate(reloadWeaponObjective))
return item;
}
var effects = component.statusEffectLists;
if (effects != null)
{
foreach (var statusEffects in effects.Values)
{
reloadWeaponObjective = newReloadWeaponObjective;
foreach (var statusEffect in statusEffects)
{
if (statusEffect.Afflictions.Any())
{
return item;
}
}
}
}
}
}
}
return weapon;
}
if (reloadWeaponObjective != null)
private bool Equip(float deltaTime)
{
if (!character.SelectedItems.Contains(Weapon))
{
if (character.Inventory.TryPutItem(Weapon, 3, true, false, character))
{
if (reloadWeaponObjective.IsCompleted())
{
reloadWeaponObjective = null;
}
else if (!reloadWeaponObjective.CanBeCompleted)
{
Escape(deltaTime);
}
else
{
reloadWeaponObjective.TryComplete(deltaTime);
}
return;
Weapon.Equip(character);
}
character.CursorPosition = enemy.Position;
character.SetInput(InputType.Aim, false, true);
Vector2 enemyDiff = Vector2.Normalize(enemy.Position - character.Position);
if (!MathUtils.IsValid(enemyDiff)) enemyDiff = Rand.Vector(1.0f);
float weaponAngle = ((weapon.body.Dir == 1.0f) ? weapon.body.Rotation : weapon.body.Rotation - MathHelper.Pi);
Vector2 weaponDir = new Vector2((float)Math.Cos(weaponAngle), (float)Math.Sin(weaponAngle));
if (Vector2.Dot(enemyDiff, weaponDir) < 0.9f) return;
List<FarseerPhysics.Dynamics.Body> ignoredBodies = new List<FarseerPhysics.Dynamics.Body>();
foreach (Limb limb in character.AnimController.Limbs)
else
{
ignoredBodies.Add(limb.body.FarseerBody);
//couldn't equip the item, escape
Escape(deltaTime);
return false;
}
}
return true;
}
var pickedBody = Submarine.PickBody(character.SimPosition, enemy.SimPosition, ignoredBodies);
if (pickedBody != null && !(pickedBody.UserData is Limb)) return;
private void Move(float deltaTime)
{
// Retreat to safety
// TODO: aggressive behaviour, chasing?
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
{
retreatTarget = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull();
}
if (retreatTarget != null)
{
if (retreatObjective == null || retreatObjective.Target != retreatTarget)
{
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true);
}
retreatObjective.TryComplete(deltaTime);
}
}
weapon.Use(deltaTime, character);
private bool Reload(float deltaTime)
{
if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
var containedItems = Weapon.ContainedItems;
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (containedItem == null)
{
if (reloadWeaponObjective == null)
{
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>());
}
}
}
}
if (reloadWeaponObjective != null)
{
if (reloadWeaponObjective.IsCompleted())
{
reloadWeaponObjective = null;
}
else if (!reloadWeaponObjective.CanBeCompleted)
{
Escape(deltaTime);
}
else
{
reloadWeaponObjective.TryComplete(deltaTime);
}
return false;
}
return true;
}
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
private void Attack(float deltaTime)
{
character.CursorPosition = Enemy.Position;
character.SetInput(InputType.Aim, false, true);
if (WeaponComponent is MeleeWeapon meleeWeapon)
{
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
{
Weapon.Use(deltaTime, character);
}
}
else
{
if (WeaponComponent is RepairTool repairTool)
{
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
}
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
{
if (myBodies == null)
{
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
}
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
if (pickedBody != null)
{
Character target = null;
if (pickedBody.UserData is Character c)
{
target = c;
}
else if (pickedBody.UserData is Limb limb)
{
target = limb.character;
}
if (target != null && target == Enemy)
{
Weapon.Use(deltaTime, character);
}
}
}
}
}
private void Escape(float deltaTime)
{
// TODO: just let the find safety run?
if (escapeObjective == null)
{
escapeObjective = new AIObjectiveFindSafety(character);
}
if (enemy.AnimController.CurrentHull == character.AnimController.CurrentHull)
{
escapeObjective.OverrideCurrentHullSafety = 0.0f;
}
else
{
escapeObjective.OverrideCurrentHullSafety = null;
}
escapeObjective.TryComplete(deltaTime);
abandon = true;
SteeringManager.Reset();
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
public override bool IsCompleted()
{
return enemy == null || enemy.Removed || enemy.IsDead || coolDownTimer <= 0.0f;
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (enemy == null || enemy.Removed)
{
return 0.0f;
}
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
//clamp the strength to the health of this character
//(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway)
float enemyDanger = Math.Min(Math.Max(CalculateEnemyStrength(), MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
if (enemy.AIController is EnemyAIController enemyAI)
{
if (enemyAI.SelectedAiTarget == character.AiTarget) enemyDanger *= 2.0f;
}
return Math.Max(enemyDanger, AIObjectiveManager.OrderPriority);
}
public override bool IsCompleted() => Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0.0f;
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;
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveCombat objective = otherObjective as AIObjectiveCombat;
if (objective == null) return false;
return objective.enemy == enemy;
if (!(otherObjective is AIObjectiveCombat objective)) return false;
return objective.Enemy == Enemy;
}
private float CalculateEnemyStrength()
{
float enemyStrength = 0;
AttackContext currentContext = character.GetAttackContext();
foreach (Limb limb in enemy.AnimController.Limbs)
{
if (limb.attack == null) continue;
if (!limb.attack.IsValidContext(currentContext)) { continue; }
if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
enemyStrength += limb.attack.GetTotalDamage(false);
}
return enemyStrength;
}
//private float CalculateEnemyStrength()
//{
// float enemyStrength = 0;
// AttackContext currentContext = character.GetAttackContext();
// foreach (Limb limb in Enemy.AnimController.Limbs)
// {
// if (limb.attack == null) continue;
// if (!limb.attack.IsValidContext(currentContext)) { continue; }
// if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
// enemyStrength += limb.attack.GetTotalDamage(false);
// }
// return enemyStrength;
//}
}
}
@@ -32,7 +32,7 @@ namespace Barotrauma
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1));
return MathHelper.Clamp(priority * severityFactor * distanceFactor, 0, 100);
return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100);
}
public override bool IsCompleted()
@@ -62,7 +62,7 @@ namespace Barotrauma
if (containedItem == null) continue;
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop();
containedItem.Drop(character);
}
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
{
@@ -16,18 +16,17 @@ namespace Barotrauma
const float priorityIncrease = 25;
const float priorityDecrease = 10;
const float SearchHullInterval = 3.0f;
const float clearUnreachableInterval = 30;
private List<Hull> unreachable = new List<Hull>();
private float currenthullSafety;
private float unreachableClearTimer;
private float searchHullTimer;
private AIObjectiveGoTo goToObjective;
private AIObjective divingGearObjective;
public float? OverrideCurrentHullSafety;
public AIObjectiveFindSafety(Character character) : base(character, "") { }
public override bool IsCompleted() => false;
@@ -52,7 +51,7 @@ namespace Barotrauma
if (divingGearObjective.IsCompleted())
{
divingGearObjective = null;
priority = 0;
Priority = 0;
}
else if (divingGearObjective.CanBeCompleted)
{
@@ -61,6 +60,16 @@ namespace Barotrauma
}
}
if (unreachableClearTimer > 0)
{
unreachableClearTimer -= deltaTime;
}
else
{
unreachableClearTimer = clearUnreachableInterval;
unreachable.Clear();
}
if (searchHullTimer > 0.0f)
{
searchHullTimer -= deltaTime;
@@ -147,16 +156,35 @@ namespace Barotrauma
}
}
private Hull FindBestHull()
public Hull FindBestHull()
{
Hull bestHull = character.CurrentHull;
float bestValue = currenthullSafety;
foreach (Hull hull in Hull.hullList)
{
if (unreachable.Contains(hull)) { continue; }
if (hull.Submarine == null) { continue; }
float hullSafety = 0;
if (character.Submarine == null)
if (character.Submarine != null && SteeringManager == PathSteering)
{
// Inside or outside near the sub
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.
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
{
hullSafety /= 10;
}
}
else
{
// Outside
if (hull.RoomName?.ToLowerInvariant() == "airlock")
@@ -177,7 +205,8 @@ namespace Barotrauma
}
// Huge preference for closer targets
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 100000, Vector2.Distance(character.WorldPosition, hull.WorldPosition)));
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC)
@@ -185,26 +214,6 @@ namespace Barotrauma
hullSafety /= 10;
}
}
else
{
// Inside
// Not connected.
if (!character.Submarine.GetConnectedSubs().Contains(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.
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
{
hullSafety /= 10;
}
}
if (hullSafety > bestValue)
{
bestHull = hull;
@@ -224,24 +233,24 @@ namespace Barotrauma
if (character.CurrentHull == null)
{
currenthullSafety = 0;
priority = 5;
Priority = 5;
return;
}
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { priority = 100; }
currenthullSafety = OverrideCurrentHullSafety ?? HumanAIController.GetHullSafety(character.CurrentHull);
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
priority -= priorityDecrease * deltaTime;
Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
priority += dangerFactor * priorityIncrease * deltaTime;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
priority = MathHelper.Clamp(priority, 0, 100);
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
{
priority = Math.Max(priority, AIObjectiveManager.OrderPriority + 10);
Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
}
}
}
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string DebugTag => "fix leak";
public override bool KeepDivingGearOn => true;
public override bool ForceRun => true;
private readonly Gap leak;
@@ -10,6 +10,7 @@ namespace Barotrauma
{
public override string DebugTag => "fix leaks";
public override bool KeepDivingGearOn => true;
public override bool ForceRun => true;
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
@@ -144,7 +144,7 @@ namespace Barotrauma
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) continue;
//if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop();
character.Inventory.Items[i].Drop(character);
}
}
}
@@ -45,17 +45,22 @@ namespace Barotrauma
get
{
bool canComplete = !cannotReach && !abandon;
if (FollowControlledCharacter && Character.Controlled == null) { canComplete = false; }
else if (Target != null && Target.Removed) { canComplete = false; }
else if (repeat || waitUntilPathUnreachable > 0.0f) { canComplete = true; }
else if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering)
if (canComplete)
{
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable TODO: add a timer?
if (pathSteering.CurrentPath == null) { canComplete = true; }
else if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { canComplete = false; }
if (canComplete)
if (FollowControlledCharacter && Character.Controlled == null)
{
canComplete = !pathSteering.CurrentPath.Unreachable;
canComplete = false;
}
else if (Target != null && Target.Removed)
{
canComplete = false;
}
else if (!repeat && waitUntilPathUnreachable < 0)
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
{
canComplete = !PathSteering.CurrentPath.Unreachable;
}
}
}
if (!canComplete)
@@ -144,9 +149,9 @@ namespace Barotrauma
}
else
{
var indoorSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
bool targetIsOutside = (Target != null && Target.Submarine == null) || (indoorSteering != null && indoorSteering.CurrentPath != null && indoorSteering.CurrentPath.HasOutdoorsNodes);
if (targetIsOutside && !AllowGoingOutside)
bool targetIsOutside = (Target != null && Target.Submarine == null) ||
(SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.HasOutdoorsNodes);
if (targetIsOutside && character.CurrentHull != null && !AllowGoingOutside)
{
cannotReach = true;
}
@@ -11,6 +11,12 @@ namespace Barotrauma
public override string DebugTag => "idle";
const float WallAvoidDistance = 150.0f;
private readonly float newTargetIntervalMin = 5;
private readonly float newTargetIntervalMax = 15;
private readonly float standStillMin = 1;
private readonly float standStillMax = 10;
private readonly float walkDurationMin = 3;
private readonly float walkDurationMax = 10;
private Hull currentTarget;
private float newTargetTimer;
@@ -46,7 +52,10 @@ namespace Barotrauma
character.SelectedConstruction = null;
}
if (currentTarget == null && (IsForbidden(character.CurrentHull) || HumanAIController.UnsafeHulls.Contains(character.CurrentHull)))
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (currentTargetIsInvalid || (currentTarget == null && IsForbidden(character.CurrentHull)))
{
newTargetTimer = 0;
standStillTimer = 0;
@@ -70,7 +79,7 @@ namespace Barotrauma
PathSteering.SetPath(path);
}
newTargetTimer = currentTarget == null ? 5.0f : 15.0f;
newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax);
}
newTargetTimer -= deltaTime;
@@ -79,20 +88,20 @@ namespace Barotrauma
// - if reached the end of the path
// - if the target is unreachable
// - if the path requires going outside
if (PathSteering == null || (PathSteering.CurrentPath != null &&
if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
(PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
{
standStillTimer -= deltaTime;
if (standStillTimer > 0.0f)
{
walkDuration = Rand.Range(1.0f, 5.0f);
walkDuration = Rand.Range(walkDurationMin, walkDurationMax);
PathSteering.Reset();
return;
}
if (standStillTimer < -walkDuration)
{
standStillTimer = Rand.Range(1.0f, 10.0f);
standStillTimer = Rand.Range(standStillMin, standStillMax);
}
//steer away from edges of the hull
@@ -128,12 +137,11 @@ namespace Barotrauma
}
character.AIController.SteeringManager.SteeringWander();
if (!character.IsClimbing)
if (!character.IsClimbing && !character.AnimController.InWater)
{
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
return;
}
@@ -150,6 +158,7 @@ namespace Barotrauma
{
var idCard = character.Inventory.FindItemByIdentifier("idcard");
Hull targetHull = null;
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
//random chance of navigating back to the room where the character spawned
if (Rand.Int(5) == 1 && idCard != null)
{
@@ -186,9 +195,13 @@ namespace Barotrauma
continue;
}
}
// Check that there is no unsafe or forbidden hulls on the way to the target
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
if (isCurrentHullOK)
{
// 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.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
}
// If we want to do a steering check, we should do it here, before setting the path
//if (path.Cost > 1000.0f) { continue; }
@@ -56,23 +56,6 @@ namespace Barotrauma
DelayedObjectives.Add(objective, coroutine);
}
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
public void AddObjective(AIObjective objective, float delay, Action callback = null)
{
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
{
CoroutineManager.StopCoroutines(coroutine);
DelayedObjectives.Remove(objective);
}
coroutine = CoroutineManager.InvokeAfter(() =>
{
DelayedObjectives.Remove(objective);
AddObjective(objective);
callback?.Invoke();
}, delay);
DelayedObjectives.Add(objective, coroutine);
}
public T GetObjective<T>() where T : AIObjective
{
foreach (AIObjective objective in Objectives)
@@ -200,27 +183,6 @@ namespace Barotrauma
if (order.TargetItemComponent == null) return;
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
break;
case "chargebatteries":
currentOrder = new AIObjectiveChargeBatteries(character, option);
break;
case "rescue":
currentOrder = new AIObjectiveRescueAll(character);
break;
case "repairsystems":
currentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
break;
case "pumpwater":
currentOrder = new AIObjectivePumpWater(character, option);
break;
case "extinguishfires":
currentOrder = new AIObjectiveExtinguishFires(character);
break;
case "steer":
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
if (order.TargetItemComponent == null) return;
currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
break;
default:
if (order.TargetItemComponent == null) return;
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
@@ -135,7 +135,7 @@ namespace Barotrauma
if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) ||
!character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any }))
{
character.Inventory.Items[i].Drop();
character.Inventory.Items[i].Drop(character);
}
}
if (character.Inventory.TryPutItem(component.Item, i, true, false, character))
@@ -35,7 +35,7 @@ namespace Barotrauma
float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
float baseLevel = Math.Max(priority + isSelected, 1);
float baseLevel = Math.Max(Priority + isSelected, 1);
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100);
}
@@ -84,6 +84,7 @@ namespace Barotrauma
}
if (character.CanInteractWith(Item))
{
OperateRepairTool(deltaTime);
foreach (Repairable repairable in Item.Repairables)
{
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
@@ -123,5 +124,33 @@ namespace Barotrauma
AddSubObjective(goToObjective);
}
}
private void OperateRepairTool(float deltaTime)
{
// Operate repair tool, if required.
foreach (Repairable repairable in Item.Repairables)
{
foreach (var kvp in repairable.requiredItems)
{
foreach (RelatedItem requiredItem in kvp.Value)
{
foreach (var item in character.Inventory.Items)
{
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;
}
}
}
}
}
}
}
}
}
@@ -549,8 +549,9 @@ namespace Barotrauma
limbHealths[i].Afflictions.RemoveAt(j);
}
}
foreach (Affliction affliction in limbHealths[i].Afflictions)
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
{
var affliction = limbHealths[i].Afflictions[j];
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == i);
affliction.Update(this, targetLimb, deltaTime);
affliction.DamagePerSecondTimer += deltaTime;
@@ -120,18 +120,30 @@ namespace Barotrauma
private void CreateEvents()
{
//don't create new events if docked to the start oupost
if (Level.Loaded?.StartOutpost != null &&
Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))
{
return;
}
for (int i = selectedEventSets.Count - 1; i >= 0; i--)
{
ScriptedEventSet eventSet = selectedEventSets[i];
float distFromStart = Vector2.Distance(Submarine.MainSub.WorldPosition, level.StartPosition);
float distFromEnd = Vector2.Distance(Submarine.MainSub.WorldPosition, level.EndPosition);
float distanceTraveled = MathHelper.Clamp(
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
0.0f, 1.0f);
if (Level.Loaded?.StartOutpost != null &&
Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))
//don't create new events if within 50 meters of the start/end of the level
if (distanceTraveled <= 0.0f ||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
{
distanceTraveled = 0.0f;
continue;
}
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
+363 -100
View File
@@ -65,12 +65,6 @@ namespace Barotrauma
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 int ParticleLimit { get; set; }
public float LightMapScale { get; set; }
@@ -405,8 +399,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];
@@ -620,98 +612,6 @@ namespace Barotrauma
}
}
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);
}
}
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);
}
}
//save to get rid of the invalid selected packages in the config file
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
}
@@ -1084,6 +984,369 @@ namespace Barotrauma
}
#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));
}
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()
{
@@ -438,7 +438,7 @@ namespace Barotrauma.Items.Components
GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
}
#endif
item.Drop();
item.Drop(character);
}
AttachToWall();
@@ -294,9 +294,10 @@ namespace Barotrauma.Items.Components
character.CursorPosition = leak.Position;
character.SetInput(InputType.Aim, false, true);
if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
// Press the trigger only when the tool is approximately facing the target.
// If the character is climbing, ignore the check, because we cannot aim while climbing.
if (character.IsClimbing || VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
{
// Press the trigger only when the tool is approximately facing the target.
Use(deltaTime, character);
}
@@ -105,7 +105,7 @@ namespace Barotrauma.Items.Components
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
item.Drop();
item.Drop(picker);
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
@@ -279,7 +279,7 @@ namespace Barotrauma.Items.Components
foreach (Item item in Inventory.Items)
{
if (item == null) continue;
item.Drop();
item.Drop(null);
}
}
@@ -420,7 +420,7 @@ namespace Barotrauma.Items.Components
if (inputContainer.Inventory.Items.All(it => it != null))
{
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
unneededItem?.Drop();
unneededItem?.Drop(null);
}
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
}
@@ -437,7 +437,7 @@ namespace Barotrauma.Items.Components
{
if (item != null && item.Condition <= 0.0f)
{
item.Drop();
item.Drop(character);
}
}
@@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components
private void Launch(Vector2 impulse)
{
item.Drop();
item.Drop(null);
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse);
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
{
float rotation = item.body.Rotation;
Vector2 simPositon = item.SimPosition;
item.Drop();
item.Drop(null);
item.body.Enabled = true;
//set the velocity of the body because the OnProjectileCollision method
@@ -350,7 +350,7 @@ namespace Barotrauma.Items.Components
{
reload = reloadTime;
projectile.Drop();
projectile.Drop(null);
projectile.body.Dir = 1.0f;
projectile.body.ResetDynamics();
@@ -1563,7 +1563,7 @@ namespace Barotrauma
return isCombined;
}
public void Drop(Character dropper = null)
public void Drop(Character dropper)
{
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
@@ -430,13 +430,6 @@ namespace Barotrauma
}
CurrentLocation.SelectedMissionIndex = missionIndex;
//the destination must be the same as the destination of the mission
if (CurrentLocation.SelectedMission != null &&
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
{
SelectLocation(CurrentLocation.SelectedMission.Locations[1]);
}
SelectedLocation = location;
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
@@ -467,6 +467,7 @@ namespace Barotrauma
public List<Submarine> GetConnectedSubs()
{
connectedSubs.Clear();
connectedSubs.Add(this);
GetConnectedSubsRecursive(connectedSubs);
return connectedSubs;
@@ -522,6 +523,30 @@ 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)
@@ -655,7 +680,7 @@ namespace Barotrauma
}
}
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
{
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
{
@@ -1011,6 +1036,31 @@ namespace Barotrauma
return false;
}
/// <summary>
/// Returns true if the sub is same as the other.
/// </summary>
public bool IsConnectedTo(Submarine otherSub) => this == otherSub || GetConnectedSubs().Contains(otherSub);
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>
/// Finds the sub whose borders contain the position
/// </summary>
@@ -160,16 +160,6 @@ namespace Barotrauma
}
#endif
public void SetState()
{
hit = binding.IsHit();
if (hit) hitQueue = true;
held = binding.IsDown();
if (held) heldQueue = true;
}
#endif
public bool Hit
{
get