409d4d9...aeafa16 (merge human-ai)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -19,8 +20,16 @@ namespace Barotrauma
|
||||
private float crouchRaycastTimer;
|
||||
const float CrouchRaycastInterval = 1.0f;
|
||||
|
||||
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;
|
||||
|
||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||
|
||||
public override AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return objectiveManager; }
|
||||
@@ -68,17 +77,18 @@ namespace Barotrauma
|
||||
steeringManager = outsideSteering;
|
||||
}
|
||||
|
||||
(Character.AnimController as HumanoidAnimController).Crouching = shouldCrouch;
|
||||
AnimController.Crouching = shouldCrouch;
|
||||
CheckCrouching(deltaTime);
|
||||
Character.ClearInputs();
|
||||
|
||||
|
||||
objectiveManager.UpdateObjectives(deltaTime);
|
||||
if (updateObjectiveTimer > 0.0f)
|
||||
{
|
||||
updateObjectiveTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.UpdateObjectives();
|
||||
objectiveManager.SortObjectives();
|
||||
updateObjectiveTimer = UpdateObjectiveInterval;
|
||||
}
|
||||
|
||||
@@ -89,69 +99,107 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
|
||||
float currObjectivePriority = objectiveManager.GetCurrentPriority();
|
||||
|
||||
bool run = currObjectivePriority > 30.0f;
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run));
|
||||
bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.OrderPriority;
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
|
||||
{
|
||||
if (Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3)
|
||||
{
|
||||
run = true;
|
||||
}
|
||||
}
|
||||
if (!run)
|
||||
{
|
||||
run = objectiveManager.CurrentObjective.ForceRun;
|
||||
}
|
||||
if (run)
|
||||
{
|
||||
run = !AnimController.Crouching && !AnimController.IsMovingBackwards;
|
||||
}
|
||||
float currentSpeed = Character.AnimController.GetCurrentSpeed(run);
|
||||
steeringManager.Update(currentSpeed);
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
|
||||
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
var currPath = (steeringManager as IndoorsSteeringManager)?.CurrentPath;
|
||||
if (currPath != null && currPath.CurrentNode != null)
|
||||
if (steeringManager == insideSteering)
|
||||
{
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
var currPath = PathSteering.CurrentPath;
|
||||
if (currPath != null && currPath.CurrentNode != null)
|
||||
{
|
||||
ignorePlatforms = true;
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
{
|
||||
ignorePlatforms = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||
|
||||
if (Character.IsClimbing)
|
||||
{
|
||||
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||
}
|
||||
|
||||
Vector2 targetMovement = AnimController.TargetMovement;
|
||||
|
||||
if (!Character.AnimController.InWater)
|
||||
{
|
||||
Vector2 targetMovement = new Vector2(
|
||||
targetMovement = new Vector2(
|
||||
Character.AnimController.TargetMovement.X,
|
||||
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
|
||||
|
||||
float maxSpeed = Character.GetCurrentMaxSpeed(run);
|
||||
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
|
||||
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
|
||||
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
|
||||
float speedMultiplier = Character.SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
|
||||
Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
|
||||
Character.AnimController.TargetMovement = targetMovement;
|
||||
}
|
||||
|
||||
if (Character.AnimController.Anim == AnimController.Animation.Climbing &&
|
||||
Character.SelectedConstruction != null &&
|
||||
Character.SelectedConstruction.GetComponent<Items.Components.Ladder>() != null)
|
||||
float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed);
|
||||
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
|
||||
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
|
||||
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
float speedMultiplier = Character.SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
Character.AnimController.TargetMovement = targetMovement;
|
||||
|
||||
if (!NeedsDivingGear(Character.CurrentHull))
|
||||
{
|
||||
if (currPath != null && currPath.CurrentNode != null && currPath.CurrentNode.Ladders != null)
|
||||
bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
||||
bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0;
|
||||
bool shouldKeepTheGearOn = objectiveManager.CurrentObjective.KeepDivingGearOn;
|
||||
|
||||
bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs);
|
||||
if (removeDivingSuit)
|
||||
{
|
||||
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||
var divingSuit = Character.Inventory.FindItemByIdentifier("divingsuit") ?? Character.Inventory.FindItemByTag("divingsuit");
|
||||
if (divingSuit != null)
|
||||
{
|
||||
// TODO: take the item where it was taken from?
|
||||
divingSuit.Drop(Character);
|
||||
}
|
||||
}
|
||||
bool takeMaskOff = oxygenLow || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 20);
|
||||
if (takeMaskOff)
|
||||
{
|
||||
var mask = Character.Inventory.FindItemByIdentifier("divingmask");
|
||||
if (mask != null)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//suit can be taken off if there character is inside a hull and there's air in the room
|
||||
bool canTakeOffSuit = Character.AnimController.CurrentHull != null &&
|
||||
Character.AnimController.CurrentHull.OxygenPercentage > 30.0f &&
|
||||
Character.AnimController.CurrentHull.WaterVolume < Character.AnimController.CurrentHull.Volume * 0.3f;
|
||||
|
||||
//the suit can be taken off and the character is running out of oxygen (couldn't find a tank for the suit?) or idling
|
||||
//-> take the suit off
|
||||
if (canTakeOffSuit && (Character.Oxygen < 50.0f || objectiveManager.CurrentObjective is AIObjectiveIdle))
|
||||
if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire))
|
||||
{
|
||||
var divingSuit = Character.Inventory.FindItemByIdentifier("divingsuit") ?? Character.Inventory.FindItemByTag("divingsuit");
|
||||
if (divingSuit != null) divingSuit.Drop(Character);
|
||||
var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher");
|
||||
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
|
||||
{
|
||||
// TODO: take the item where it was taken from?
|
||||
extinguisherItem.Drop();
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.IsKeyDown(InputType.Aim))
|
||||
@@ -173,14 +221,61 @@ namespace Barotrauma
|
||||
{
|
||||
Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
}
|
||||
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
PropagateHullSafety(Character, Character.CurrentHull);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ReportProblems()
|
||||
{
|
||||
Order newOrder = null;
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (Character.CurrentHull.FireSources.Count > 0)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
|
||||
if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
|
||||
(c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
|
||||
if (newOrder != null)
|
||||
{
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||
{
|
||||
Character.Speak(
|
||||
newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportProblems()
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
partial void ReportProblems();
|
||||
|
||||
private void UpdateSpeaking()
|
||||
{
|
||||
if (Character.Oxygen < 20.0f)
|
||||
@@ -204,11 +299,26 @@ namespace Barotrauma
|
||||
float totalDamage = attackResult.Damage;
|
||||
if (totalDamage <= 0.0f || attacker == null) return;
|
||||
|
||||
if (attacker.AnimController.Anim == AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
|
||||
if (attacker.SpeciesName == Character.SpeciesName)
|
||||
{
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
float currentVitality = Character.CharacterHealth.Vitality;
|
||||
float dmgPercentage = totalDamage / currentVitality * 100;
|
||||
if (dmgPercentage < currentVitality / 10)
|
||||
{
|
||||
// Don't react to a minor amount of (accidental) dmg done by friendly characters
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () =>
|
||||
@@ -251,5 +361,87 @@ namespace Barotrauma
|
||||
float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f;
|
||||
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null;
|
||||
}
|
||||
|
||||
public static bool NeedsDivingGear(Hull hull) => hull == null || hull.OxygenPercentage < 50 || hull.WaterPercentage > 50;
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving suit in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
public static bool HasDivingSuit(Character character) => HasItem(character, "divingsuit", "oxygensource");
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource");
|
||||
|
||||
public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0)
|
||||
{
|
||||
var item = character.Inventory.FindItemByTag(tag);
|
||||
return item != null &&
|
||||
item.ConditionPercentage > conditionPercentage &&
|
||||
character.HasEquippedItem(item) &&
|
||||
(containedTag == null ||
|
||||
(item.ContainedItems != null &&
|
||||
item.ContainedItems.Any(i => i.HasTag(containedTag) && i.ConditionPercentage > conditionPercentage)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the hull safety for all ai characters in the team.
|
||||
/// </summary>
|
||||
public static void PropagateHullSafety(Character character, Hull hull)
|
||||
{
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (c.TeamID == character.TeamID)
|
||||
{
|
||||
if (c.AIController is HumanAIController humanAi)
|
||||
{
|
||||
humanAi.RefreshHullSafety(hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshHullSafety(Hull hull)
|
||||
{
|
||||
if (GetHullSafety(hull) > HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
UnsafeHulls.Remove(hull);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnsafeHulls.Add(hull);
|
||||
}
|
||||
}
|
||||
|
||||
public float GetHullSafety(Hull hull)
|
||||
{
|
||||
if (hull == null) { return 0; }
|
||||
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);
|
||||
}
|
||||
|
||||
public static float GetHullSafety(Hull hull, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
||||
{
|
||||
if (hull == null) { return 0; }
|
||||
if (hull.LethalPressure > 0 && character.PressureProtection <= 0) { return 0; }
|
||||
float oxygenFactor = ignoreOxygen ? 1 : MathHelper.Lerp(0.25f, 1, hull.OxygenPercentage / 100);
|
||||
float waterFactor = ignoreWater ? 1 : MathHelper.Lerp(1, 0.25f, hull.WaterPercentage / 100);
|
||||
if (!character.NeedsAir)
|
||||
{
|
||||
oxygenFactor = 1;
|
||||
waterFactor = 1;
|
||||
}
|
||||
// Even the smallest fire reduces the safety by 50%
|
||||
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
|
||||
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
|
||||
int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || e.TeamID != character.TeamID));
|
||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||
float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
|
||||
return MathHelper.Clamp(safety * 100, 0, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user