236 lines
10 KiB
C#
236 lines
10 KiB
C#
using Barotrauma.Items.Components;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class AIObjectiveRescue : AIObjective
|
|
{
|
|
public override string DebugTag => "rescue";
|
|
public override bool ForceRun => true;
|
|
|
|
const float TreatmentDelay = 0.5f;
|
|
|
|
private readonly Character targetCharacter;
|
|
|
|
private AIObjectiveGoTo goToObjective;
|
|
|
|
private float treatmentTimer;
|
|
|
|
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
|
: base(character, objectiveManager, priorityModifier)
|
|
{
|
|
this.targetCharacter = targetCharacter;
|
|
}
|
|
|
|
public override bool IsDuplicate(AIObjective otherObjective)
|
|
{
|
|
AIObjectiveRescue rescueObjective = otherObjective as AIObjectiveRescue;
|
|
return rescueObjective != null && rescueObjective.targetCharacter == targetCharacter;
|
|
}
|
|
|
|
protected override void Act(float deltaTime)
|
|
{
|
|
// Unconcious target is not in a safe place -> Move to a safe place first
|
|
if (targetCharacter.IsUnconscious && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
|
{
|
|
if (character.SelectedCharacter != targetCharacter)
|
|
{
|
|
character.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
|
|
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
|
|
null, 1.0f,
|
|
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
|
|
|
// Go to the target and select it
|
|
if (!character.CanInteractWith(targetCharacter))
|
|
{
|
|
if (goToObjective != null && goToObjective.Target != targetCharacter)
|
|
{
|
|
goToObjective = null;
|
|
}
|
|
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
|
}
|
|
else
|
|
{
|
|
character.SelectCharacter(targetCharacter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Drag the character into safety
|
|
if (goToObjective != null && goToObjective.Target == targetCharacter)
|
|
{
|
|
goToObjective = null;
|
|
}
|
|
var findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
|
|
if (findSafety == null)
|
|
{
|
|
// Ensure that we have the find safety objective (should always be the case)
|
|
objectiveManager.AddObjective(new AIObjectiveFindSafety(character, objectiveManager));
|
|
}
|
|
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(findSafety.FindBestHull(new Hull[] { character.CurrentHull }), character, objectiveManager));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (subObjectives.Any()) { return; }
|
|
|
|
if (!character.CanInteractWith(targetCharacter))
|
|
{
|
|
// Go to the target and select it
|
|
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
|
}
|
|
else
|
|
{
|
|
// We can start applying treatment
|
|
if (character.SelectedCharacter != targetCharacter)
|
|
{
|
|
character.Speak(TextManager.Get("DialogFoundWoundedTarget")
|
|
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
|
|
null, 1.0f,
|
|
"foundwoundedtarget" + targetCharacter.Name, 60.0f);
|
|
|
|
character.SelectCharacter(targetCharacter);
|
|
}
|
|
GiveTreatment(deltaTime);
|
|
}
|
|
}
|
|
|
|
// TODO: consider optimizing a bit
|
|
private void GiveTreatment(float deltaTime)
|
|
{
|
|
if (treatmentTimer > 0.0f)
|
|
{
|
|
treatmentTimer -= deltaTime;
|
|
}
|
|
treatmentTimer = TreatmentDelay;
|
|
|
|
var allAfflictions = targetCharacter.CharacterHealth.GetAllAfflictions()
|
|
.Where(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth) > 0)
|
|
.ToList();
|
|
|
|
allAfflictions.Sort((a1, a2) =>
|
|
{
|
|
return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth));
|
|
});
|
|
//check if we already have a suitable treatment for any of the afflictions
|
|
foreach (Affliction affliction in allAfflictions)
|
|
{
|
|
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
|
{
|
|
if (treatmentSuitability.Value > 0.0f)
|
|
{
|
|
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key);
|
|
if (matchingItem == null) { continue; }
|
|
ApplyTreatment(affliction, matchingItem);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//didn't have any suitable treatments available, try to find some medical items
|
|
HashSet<string> suitableItemIdentifiers = new HashSet<string>();
|
|
foreach (Affliction affliction in allAfflictions)
|
|
{
|
|
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
|
{
|
|
if (treatmentSuitability.Value > 0.0f)
|
|
{
|
|
suitableItemIdentifiers.Add(treatmentSuitability.Key);
|
|
}
|
|
}
|
|
}
|
|
if (suitableItemIdentifiers.Count > 0)
|
|
{
|
|
List<string> itemNameList = new List<string>();
|
|
foreach (string itemIdentifier in suitableItemIdentifiers)
|
|
{
|
|
if (MapEntityPrefab.Find(null, itemIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab)
|
|
{
|
|
itemNameList.Add(itemPrefab.Name);
|
|
}
|
|
//only list the first 4 items
|
|
if (itemNameList.Count >= 4) { break; }
|
|
}
|
|
if (itemNameList.Count > 0)
|
|
{
|
|
string itemListStr = "";
|
|
if (itemNameList.Count == 1)
|
|
{
|
|
itemListStr = itemNameList[0];
|
|
}
|
|
else
|
|
{
|
|
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
|
}
|
|
character.Speak(TextManager.Get("DialogListRequiredTreatments")
|
|
.Replace("[targetname]", targetCharacter.Name)
|
|
.Replace("[treatmentlist]", itemListStr),
|
|
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
|
|
}
|
|
character.DeselectCharacter();
|
|
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true));
|
|
}
|
|
character.AnimController.Anim = AnimController.Animation.CPR;
|
|
}
|
|
|
|
private void ApplyTreatment(Affliction affliction, Item item)
|
|
{
|
|
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
|
bool remove = false;
|
|
foreach (ItemComponent ic in item.Components)
|
|
{
|
|
if (!ic.HasRequiredContainedItems(addMessage: false)) { continue; }
|
|
#if CLIENT
|
|
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
|
|
#endif
|
|
ic.WasUsed = true;
|
|
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
|
if (ic.DeleteOnUse)
|
|
{
|
|
remove = true;
|
|
}
|
|
}
|
|
if (remove)
|
|
{
|
|
Entity.Spawner?.AddToRemoveQueue(item);
|
|
}
|
|
}
|
|
|
|
public override bool IsCompleted()
|
|
{
|
|
bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager);
|
|
if (isCompleted)
|
|
{
|
|
character.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
|
|
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
|
|
}
|
|
return isCompleted || targetCharacter.Removed || targetCharacter.IsDead;
|
|
}
|
|
|
|
public override float GetPriority()
|
|
{
|
|
if (targetCharacter == null) { return 0; }
|
|
if (targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
|
{
|
|
abandon = true;
|
|
return 0;
|
|
}
|
|
// Don't go into rooms that have enemies
|
|
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(c)))
|
|
{
|
|
abandon = true;
|
|
return 0;
|
|
}
|
|
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
|
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
|
|
float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist));
|
|
float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
|
|
float devotion = Math.Min(Priority, 10) / 100;
|
|
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
|
|
}
|
|
}
|
|
}
|