This commit is contained in:
EvilFactory
2023-06-21 10:36:16 -03:00
30 changed files with 242 additions and 137 deletions
@@ -521,8 +521,5 @@ namespace Barotrauma
protected virtual void OnStateChanged(AIState from, AIState to) { }
protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { }
public virtual void ClientRead(IReadMessage msg) { }
public virtual void ServerWrite(IWriteMessage msg) { }
}
}
@@ -4055,18 +4055,6 @@ namespace Barotrauma
}
return null;
}
public override void ServerWrite(IWriteMessage msg)
{
msg.WriteByte((byte)State);
PetBehavior?.ServerWrite(msg);
}
public override void ClientRead(IReadMessage msg)
{
State = (AIState)msg.ReadByte();
PetBehavior?.ClientRead(msg);
}
}
//the "memory" of the Character
@@ -200,7 +200,8 @@ namespace Barotrauma
Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
endNodeFilter = IsSuitableEndNode,
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
// Only report about contextual targets.
SpeakCannotReachCondition = () => isPriority && !CheckObjectiveSpecific()
},
onAbandon: () =>
{
@@ -223,23 +223,30 @@ namespace Barotrauma
Hull targetHull = GetTargetHull();
if (!IsFollowOrder)
{
// Abandon if going through unsafe paths or targeting unsafe hulls.
bool isUnreachable = HumanAIController.UnreachableHulls.Contains(targetHull);
if (!objectiveManager.CurrentObjective.IgnoreUnsafeHulls)
{
if (HumanAIController.UnsafeHulls.Contains(targetHull))
// Wait orders check this so that the bot temporarily leaves the unsafe hull.
// Non-orders (that are not set to ignore the unsafe hulls) abandon. In practice this means e.g. repair and clean up item subobjectives (of the looping parent objective).
// Other orders are only abandoned if the hull is unreachable, because the path is invalid or not found at all.
if (IsWaitOrder || !objectiveManager.HasOrders())
{
isUnreachable = true;
HumanAIController.AskToRecalculateHullSafety(targetHull);
}
else if (PathSteering?.CurrentPath != null)
{
foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
if (HumanAIController.UnsafeHulls.Contains(targetHull))
{
if (wp.CurrentHull == null) { continue; }
if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
isUnreachable = true;
HumanAIController.AskToRecalculateHullSafety(targetHull);
}
else if (PathSteering?.CurrentPath != null)
{
foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
{
isUnreachable = true;
HumanAIController.AskToRecalculateHullSafety(wp.CurrentHull);
if (wp.CurrentHull == null) { continue; }
if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
{
isUnreachable = true;
HumanAIController.AskToRecalculateHullSafety(wp.CurrentHull);
}
}
}
}
@@ -222,7 +222,8 @@ namespace Barotrauma
{
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
{
TargetName = Item.Name
TargetName = Item.Name,
SpeakCannotReachCondition = () => isPriority
};
if (repairTool != null)
{
@@ -287,7 +287,7 @@ namespace Barotrauma
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
float bestSuitability = 0.0f;
Item bestItem = null;
foreach (KeyValuePair<Identifier, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
foreach (KeyValuePair<Identifier, float> treatmentSuitability in affliction.Prefab.TreatmentSuitabilities)
{
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) &&
currentTreatmentSuitabilities[treatmentSuitability.Key] > bestSuitability)
@@ -109,6 +109,7 @@ namespace Barotrauma
foreach (Affliction affliction in allAfflictions)
{
if (affliction.Prefab.IsBuff) { continue; }
if (!affliction.Prefab.HasTreatments) { continue; }
if (!ignoreTreatmentThreshold)
{
//other afflictions of the same type increase the "treatability"
@@ -116,7 +117,6 @@ namespace Barotrauma
float totalAfflictionStrength = character.CharacterHealth.GetTotalAdjustedAfflictionStrength(affliction);
if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) { continue; }
}
if (affliction.Prefab.TreatmentSuitability.None(kvp => kvp.Value > 0)) { continue; }
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
yield return affliction;
}
@@ -435,37 +435,34 @@ namespace Barotrauma
spawnPoint ??= WayPoint.WayPointList.Where(wp => wp.SpawnType == SpawnType.Human && wp.Submarine?.Info.Type == SubmarineType.Player).GetRandomUnsynced();
spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition;
}
var pet = Character.Create(speciesName, spawnPos, seed, spawnInitialItems: false);
var petBehavior = (pet?.AIController as EnemyAIController)?.PetBehavior;
if (petBehavior != null)
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName.ToIdentifier());
if (characterPrefab == null)
{
petBehavior.Owner = owner;
var petBehaviorElement = subElement.Element("petbehavior");
if (petBehaviorElement != null)
DebugConsole.ThrowError($"Failed to load the pet \"{speciesName}\". Character prefab not found.");
continue;
}
var pet = Character.Create(characterPrefab, spawnPos, seed, spawnInitialItems: false);
if (pet != null)
{
var petBehavior = (pet.AIController as EnemyAIController)?.PetBehavior;
if (petBehavior != null)
{
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat("hunger", 50.0f);
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat("happiness", 50.0f);
petBehavior.Owner = owner;
var petBehaviorElement = subElement.Element("petbehavior");
if (petBehaviorElement != null)
{
petBehavior.Hunger = petBehaviorElement.GetAttributeFloat("hunger", 50.0f);
petBehavior.Happiness = petBehaviorElement.GetAttributeFloat("happiness", 50.0f);
}
}
}
var inventoryElement = subElement.Element("inventory");
if (inventoryElement != null)
{
pet.SpawnInventoryItems(pet.Inventory, inventoryElement.FromPackage(null));
}
}
}
}
public void ServerWrite(IWriteMessage msg)
{
msg.WriteRangedSingle(Happiness, 0.0f, MaxHappiness, 8);
msg.WriteRangedSingle(Hunger, 0.0f, MaxHunger, 8);
}
public void ClientRead(IReadMessage msg)
{
Happiness = msg.ReadRangedSingle(0.0f, MaxHappiness, 8);
Hunger = msg.ReadRangedSingle(0.0f, MaxHunger, 8);
}
}
}
@@ -7,6 +7,7 @@ using System.Xml.Linq;
using Barotrauma.Extensions;
using System.Collections.Immutable;
using Barotrauma.Items.Components;
using System.Linq;
namespace Barotrauma
{
@@ -841,20 +842,16 @@ namespace Barotrauma
/// </summary>
public readonly Sprite AfflictionOverlay;
public IEnumerable<KeyValuePair<Identifier, float>> TreatmentSuitability
public ImmutableDictionary<Identifier, float> TreatmentSuitabilities
{
get
{
foreach (var itemPrefab in ItemPrefab.Prefabs)
{
float suitability = itemPrefab.GetTreatmentSuitability(Identifier) + itemPrefab.GetTreatmentSuitability(AfflictionType);
if (!MathUtils.NearlyEqual(suitability, 0.0f))
{
yield return new KeyValuePair<Identifier, float>(itemPrefab.Identifier, suitability);
}
}
}
}
get;
private set;
} = new Dictionary<Identifier, float>().ToImmutableDictionary();
/// <summary>
/// Can this affliction be treated with some item?
/// </summary>
public bool HasTreatments { get; private set; }
public AfflictionPrefab(ContentXElement element, AfflictionsFile file, Type type) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
@@ -974,6 +971,22 @@ namespace Barotrauma
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
}
private void RefreshTreatmentSuitabilities()
{
var newTreatmentSuitabilities = new Dictionary<Identifier, float>();
foreach (var itemPrefab in ItemPrefab.Prefabs)
{
float suitability = itemPrefab.GetTreatmentSuitability(Identifier) + itemPrefab.GetTreatmentSuitability(AfflictionType);
if (!MathUtils.NearlyEqual(suitability, 0.0f))
{
newTreatmentSuitabilities.TryAdd(itemPrefab.Identifier, suitability);
}
}
HasTreatments = newTreatmentSuitabilities.Any(kvp => kvp.Value > 0);
TreatmentSuitabilities = newTreatmentSuitabilities.ToImmutableDictionary();
}
public LocalizedString GetDescription(float strength, Description.TargetType targetType)
{
foreach (var description in Descriptions)
@@ -993,9 +1006,16 @@ namespace Barotrauma
return defaultDescription;
}
public static void LoadAllEffects()
/// <summary>
/// Should be called before each round: loads all StatusEffects and refreshes treatment suitabilities.
/// </summary>
public static void LoadAllEffectsAndTreatmentSuitabilities()
{
Prefabs.ForEach(p => p.LoadEffects());
foreach (var prefab in Prefabs)
{
prefab.RefreshTreatmentSuitabilities();
prefab.LoadEffects();
}
}
public static void ClearAllEffects()
@@ -1003,7 +1023,7 @@ namespace Barotrauma
Prefabs.ForEach(p => p.ClearEffects());
}
public void LoadEffects()
private void LoadEffects()
{
ClearEffects();
foreach (var subElement in configElement.Elements())
@@ -1032,7 +1052,7 @@ namespace Barotrauma
}
}
public void ClearEffects()
private void ClearEffects()
{
effects.Clear();
periodicEffects.Clear();
@@ -1152,7 +1152,7 @@ namespace Barotrauma
}
}
foreach (KeyValuePair<Identifier, float> treatment in affliction.Prefab.TreatmentSuitability)
foreach (KeyValuePair<Identifier, float> treatment in affliction.Prefab.TreatmentSuitabilities)
{
float suitability = treatment.Value * strength;
if (treatment.Value > strength)
@@ -392,7 +392,7 @@ namespace Barotrauma
DateTime startTime = DateTime.Now;
#endif
RoundDuration = 0.0f;
AfflictionPrefab.LoadAllEffects();
AfflictionPrefab.LoadAllEffectsAndTreatmentSuitabilities();
MirrorLevel = mirrorLevel;
if (SubmarineInfo == null)
@@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components
private void CreateTriggerBody()
{
System.Diagnostics.Debug.Assert(trigger == null, "LevelResource trigger already created!");
var body = item.body ?? holdable.Body;
var body = item.body ?? holdable?.Body;
if (body != null && Attached)
{
trigger = new PhysicsBody(body.Width, body.Height, body.Radius,
@@ -178,7 +178,7 @@ namespace Barotrauma
if (eventSet is null) { continue; }
int count = childElement.GetAttributeInt("count", 0);
if (count < 1) { continue; }
FinishedEvents.Add(eventSet, count);
FinishedEvents.TryAdd(eventSet, count);
}
static EventSet FindSetRecursive(EventSet parentSet, Identifier setIdentifier)
@@ -365,7 +365,7 @@ namespace Barotrauma
if (FinishedEvents.Any())
{
var finishedEventsElement = new XElement(nameof(FinishedEvents));
foreach (var (set, count) in FinishedEvents)
foreach (var (set, count) in FinishedEvents.DistinctBy(f => f.Key.Identifier))
{
var element = new XElement(nameof(FinishedEvents),
new XAttribute("set", set.Identifier),
@@ -1353,44 +1353,38 @@ namespace Barotrauma
if (startWaypoint.WorldPosition.X > endWaypoint.WorldPosition.X)
{
var temp = startWaypoint;
startWaypoint = endWaypoint;
endWaypoint = temp;
(endWaypoint, startWaypoint) = (startWaypoint, endWaypoint);
}
if (hallwayLength > 100 && isHorizontal)
{
//if the hallway is longer than 100 pixels, generate some waypoints inside it
//for vertical hallways this isn't necessarily, it's done as a part of the ladder generation in AlignLadders
WayPoint prevWayPoint = startWaypoint;
WayPoint firstWayPoint = null;
for (float x = leftHull.Rect.Right + 50; x < rightHull.Rect.X - 50; x += 100.0f)
{
var newWayPoint = new WayPoint(new Vector2(x, hullBounds.Y + 110.0f), SpawnType.Path, sub);
firstWayPoint ??= newWayPoint;
prevWayPoint.linkedTo.Add(newWayPoint);
newWayPoint.linkedTo.Add(prevWayPoint);
prevWayPoint = newWayPoint;
}
if (firstWayPoint != null)
{
firstWayPoint.linkedTo.Add(startWaypoint);
startWaypoint.linkedTo.Add(firstWayPoint);
}
if (prevWayPoint != null)
{
prevWayPoint.linkedTo.Add(endWaypoint);
endWaypoint.linkedTo.Add(prevWayPoint);
}
}
WayPoint closestWaypoint = null;
float closestDistSqr = 30.0f * 30.0f;
foreach (WayPoint waypoint in WayPoint.WayPointList)
else
{
if (waypoint == startWaypoint) { continue; }
float dist = Vector2.DistanceSquared(waypoint.WorldPosition, startWaypoint.WorldPosition);
if (dist < closestDistSqr)
{
closestWaypoint = waypoint;
closestDistSqr = dist;
}
}
if (closestWaypoint != null)
{
startWaypoint.linkedTo.Add(closestWaypoint);
closestWaypoint.linkedTo.Add(startWaypoint);
startWaypoint.linkedTo.Add(endWaypoint);
endWaypoint.linkedTo.Add(startWaypoint);
}
}
}
@@ -1444,7 +1438,7 @@ namespace Barotrauma
{
foreach (MapEntity me in entities[module])
{
if (!(me is Gap gap)) { continue; }
if (me is not Gap gap) { continue; }
var door = gap.ConnectedDoor;
if (door != null && !door.UseBetweenOutpostModules) { continue; }
if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap))
@@ -1524,15 +1518,38 @@ namespace Barotrauma
static void RemoveLinkedEntity(MapEntity linked)
{
if (linked is Item linkedItem && linkedItem.Connections != null)
if (linked is Item linkedItem)
{
foreach (Connection connection in linkedItem.Connections)
if (linkedItem.Connections != null)
{
foreach (Wire w in connection.Wires.ToArray())
foreach (Connection connection in linkedItem.Connections)
{
w?.Item.Remove();
foreach (Wire w in connection.Wires.ToArray())
{
w?.Item.Remove();
}
}
}
//if we end up removing a ladder, remove its waypoints too
if (linkedItem.GetComponent<Ladder>() is Ladder ladder)
{
var ladderWaypoints = WayPoint.WayPointList.FindAll(wp => wp.Ladders == ladder);
foreach (var ladderWaypoint in ladderWaypoints)
{
//got through all waypoints linked to the ladder waypoints, and link them together
//so we don't end up breaking up any paths by removing the ladder waypoints
for (int i = 0; i < ladderWaypoint.linkedTo.Count; i++)
{
if (ladderWaypoint.linkedTo[i] is not WayPoint waypoint1 || waypoint1.Ladders == ladder) { continue; }
for (int j = i + 1; j < ladderWaypoint.linkedTo.Count; j++)
{
if (ladderWaypoint.linkedTo[j] is not WayPoint waypoint2 || waypoint2.Ladders == ladder) { continue; }
waypoint1.ConnectTo(waypoint2);
}
}
}
ladderWaypoints.ForEach(wp => wp.Remove());
}
}
linked.Remove();
}