aeafa16...4d3cf73
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+206
-132
@@ -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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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"))
|
||||
{
|
||||
|
||||
+44
-35
@@ -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);
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
+30
-1
@@ -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) &&
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user