Merge remote-tracking branch 'upstream/master' into develop
This commit is contained in:
@@ -2628,7 +2628,7 @@ namespace Barotrauma
|
||||
float margin = MathHelper.PiOver4 * distanceFactor;
|
||||
if (angle < margin || dist < minDistance)
|
||||
{
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
var pickedBody = Submarine.PickBody(weapon.SimPosition, Character.GetRelativeSimPosition(target), myBodies, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody != null)
|
||||
{
|
||||
@@ -2643,7 +2643,6 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Character t = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
{
|
||||
@@ -2657,6 +2656,16 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pickedBody.UserData is Item item && item.Prefab.DamagedByProjectiles)
|
||||
{
|
||||
// Target behind an item -> allow shooting.
|
||||
return true;
|
||||
}
|
||||
if (pickedBody.UserData is Holdable holdable && holdable.Item.Prefab.DamagedByProjectiles)
|
||||
{
|
||||
// Target behind a blocking but destructible item -> allow shooting.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -2889,7 +2898,7 @@ namespace Barotrauma
|
||||
if (aiTarget.ShouldBeIgnored()) { continue; }
|
||||
if (ignoredTargets.Contains(aiTarget)) { continue; }
|
||||
if (aiTarget.Type == AITarget.TargetType.HumanOnly) { continue; }
|
||||
if (!TargetOutposts && GameMain.GameSession.GameMode is not TestGameMode)
|
||||
if (!TargetOutposts && GameMain.GameSession?.GameMode is not TestGameMode)
|
||||
{
|
||||
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.Info.IsOutpost) { continue; }
|
||||
}
|
||||
@@ -3918,7 +3927,13 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters originally defined in the AI params and modified temporarily.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Identifier, IEnumerable<CharacterParams.TargetParams>> modifiedParams = new Dictionary<Identifier, IEnumerable<CharacterParams.TargetParams>>();
|
||||
/// <summary>
|
||||
/// Parameters created temporarily. Not originally defined in the AI params at all.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Identifier, CharacterParams.TargetParams> tempParams = new Dictionary<Identifier, CharacterParams.TargetParams>();
|
||||
private readonly List<CharacterParams.TargetParams> tempParamsList = new List<CharacterParams.TargetParams>();
|
||||
|
||||
@@ -3952,11 +3967,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (AIParams.TryAddNewTarget(tag, state, priority ?? minPriority, out CharacterParams.TargetParams targetParams))
|
||||
{
|
||||
if (state == AIState.Attack)
|
||||
{
|
||||
// Only applies to new temp target params. Shouldn't affect any existing definitions (handled below).
|
||||
targetParams.IgnoreIfNotInSameSub = ignoreAttacksIfNotInSameSub;
|
||||
}
|
||||
tempParams.Add(tag, targetParams);
|
||||
}
|
||||
}
|
||||
@@ -3970,6 +3980,15 @@ namespace Barotrauma
|
||||
targetParams.Priority = Math.Max(targetParams.Priority, priority.Value);
|
||||
}
|
||||
targetParams.State = state;
|
||||
if (state == AIState.Attack)
|
||||
{
|
||||
targetParams.IgnoreIfNotInSameSub = ignoreAttacksIfNotInSameSub;
|
||||
targetParams.IgnoreInside = false;
|
||||
targetParams.IgnoreOutside = false;
|
||||
targetParams.IgnoreTargetInside = false;
|
||||
targetParams.IgnoreTargetOutside = false;
|
||||
targetParams.IgnoreIncapacitated = false;
|
||||
}
|
||||
}
|
||||
modifiedParams.TryAdd(tag, existingTargetParams);
|
||||
}
|
||||
|
||||
@@ -574,7 +574,7 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != Character.Submarine) { continue; }
|
||||
if (c.Removed || c.IsDead || c.IsIncapacitated) { continue; }
|
||||
if (c.Removed || c.IsDead || c.IsIncapacitated || c.InDetectable) { continue; }
|
||||
if (IsFriendly(c)) { continue; }
|
||||
Vector2 toTarget = c.WorldPosition - WorldPosition;
|
||||
float dist = toTarget.LengthSquared();
|
||||
@@ -1045,7 +1045,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Character target in Character.CharacterList)
|
||||
{
|
||||
if (target.CurrentHull != hull || !target.Enabled) { continue; }
|
||||
if (target.CurrentHull != hull || !target.Enabled || target.InDetectable) { continue; }
|
||||
if (AIObjectiveFightIntruders.IsValidTarget(target, Character, false))
|
||||
{
|
||||
if (!target.IsHandcuffed && AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
|
||||
@@ -1696,16 +1696,25 @@ namespace Barotrauma
|
||||
|
||||
public bool AllowCampaignInteraction()
|
||||
{
|
||||
if (Character == null || Character.Removed || Character.IsIncapacitated) { return false; }
|
||||
if (Character == null || Character.Removed) { return false; }
|
||||
|
||||
switch (ObjectiveManager.CurrentObjective)
|
||||
//some events might want to allow talking/examining characters that are incapacitated or in some "emergency" ai state,
|
||||
//so let's ignore those here
|
||||
var type = Character.CampaignInteractionType;
|
||||
if (type != CampaignMode.InteractionType.None &&
|
||||
type != CampaignMode.InteractionType.Talk &&
|
||||
type != CampaignMode.InteractionType.Examine)
|
||||
{
|
||||
case AIObjectiveCombat _:
|
||||
case AIObjectiveFindSafety _:
|
||||
case AIObjectiveExtinguishFires _:
|
||||
case AIObjectiveFightIntruders _:
|
||||
case AIObjectiveFixLeaks _:
|
||||
return false;
|
||||
if (Character.IsIncapacitated) { return false; }
|
||||
switch (ObjectiveManager.CurrentObjective)
|
||||
{
|
||||
case AIObjectiveCombat _:
|
||||
case AIObjectiveFindSafety _:
|
||||
case AIObjectiveExtinguishFires _:
|
||||
case AIObjectiveFightIntruders _:
|
||||
case AIObjectiveFixLeaks _:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2272,7 +2281,7 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
|
||||
{
|
||||
if (other.IsHusk)
|
||||
if (other.IsHusk && !onlySameTeam)
|
||||
{
|
||||
// Disguised as husk
|
||||
return me.IsDisguisedAsHusk;
|
||||
@@ -2305,16 +2314,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode)
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode &&
|
||||
//ignore hostile faction if offering services that don't get disabled by faction hostility
|
||||
(me.CampaignInteractionType == CampaignMode.InteractionType.None || CampaignMode.HostileFactionDisablesInteraction(me.CampaignInteractionType)))
|
||||
{
|
||||
if ((me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1) ||
|
||||
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
{
|
||||
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
|
||||
|
||||
//NPCs that allow some campaign interaction are not turned hostile by low reputation
|
||||
if (npc.CampaignInteractionType != CampaignMode.InteractionType.None) { return true; }
|
||||
|
||||
if (npc.AIController is HumanAIController npcAI)
|
||||
{
|
||||
return !npcAI.IsInHostileFaction();
|
||||
@@ -2347,7 +2355,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsActive(Character c) => c != null && c.Enabled && !c.IsUnconscious;
|
||||
public static bool IsActive(Character c) => c is { Enabled: true, IsUnconscious: false };
|
||||
|
||||
public static bool IsTrueForAllBotsInTheCrew(Character character, Func<HumanAIController, bool> predicate)
|
||||
{
|
||||
@@ -2359,7 +2367,7 @@ namespace Barotrauma
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2468,11 +2476,11 @@ namespace Barotrauma
|
||||
operatingCharacter = c;
|
||||
return true;
|
||||
}
|
||||
if (c.AIController is HumanAIController humanAI && humanAI.ObjectiveManager is AIObjectiveManager objectiveManager)
|
||||
if (c.AIController is HumanAIController { ObjectiveManager: AIObjectiveManager objectiveManager })
|
||||
{
|
||||
foreach (var objective in objectiveManager.Objectives)
|
||||
{
|
||||
if (!(objective is AIObjectiveOperateItem operateObjective)) { continue; }
|
||||
if (objective is not AIObjectiveOperateItem operateObjective) { continue; }
|
||||
if (operateObjective.Component?.Item != target.Item) { continue; }
|
||||
if (operateObjective.Priority < highestPriority) { continue; }
|
||||
if (operateObjective.PriorityModifier < highestPriorityModifier) { continue; }
|
||||
@@ -2485,136 +2493,6 @@ namespace Barotrauma
|
||||
return operatingCharacter != null;
|
||||
}
|
||||
|
||||
// There's some duplicate logic in the two methods below, but making them use the same code would require some changes in the target classes so that we could use exactly the same checks.
|
||||
// And even then there would be some differences that could end up being confusing (like the exception for steering).
|
||||
public bool IsItemOperatedByAnother(ItemComponent target, out Character other)
|
||||
{
|
||||
other = null;
|
||||
if (target?.Item == null) { return false; }
|
||||
bool isOrder = IsOrderedToOperateTarget(this);
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!IsActive(c)) { continue; }
|
||||
if (c == Character) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (c.SelectedItem == target.Item)
|
||||
{
|
||||
// If the other character is player, don't try to operate
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is HumanAIController otherAI)
|
||||
{
|
||||
if (otherAI.ObjectiveManager.Objectives.None(o => o is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item))
|
||||
{
|
||||
// Not targeting the same item.
|
||||
continue;
|
||||
}
|
||||
bool isTargetOrdered = IsOrderedToOperateTarget(otherAI);
|
||||
if (!isOrder && isTargetOrdered)
|
||||
{
|
||||
// If the other bot is ordered to operate the item, let him do it, unless we are ordered too
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOrder && !isTargetOrdered)
|
||||
{
|
||||
// We are ordered and the target is not -> allow to operate
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsOperatingTarget(otherAI))
|
||||
{
|
||||
// The other bot is doing something else -> stick to the target.
|
||||
continue;
|
||||
}
|
||||
if (target is Steering)
|
||||
{
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
if (Character.GetSkillLevel(Tags.HelmSkill) <= c.GetSkillLevel(Tags.HelmSkill))
|
||||
{
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c))
|
||||
{
|
||||
other = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return other != null;
|
||||
bool IsOrderedToOperateTarget(HumanAIController ai) => ai.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item;
|
||||
bool IsOperatingTarget(HumanAIController ai) => ai.ObjectiveManager.CurrentObjective is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item;
|
||||
}
|
||||
|
||||
public bool IsItemRepairedByAnother(Item target, out Character other)
|
||||
{
|
||||
other = null;
|
||||
if (Character == null) { return false; }
|
||||
if (target == null) { return false; }
|
||||
bool isOrder = IsOrderedToRepairThis(Character.AIController as HumanAIController);
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (!IsActive(c)) { continue; }
|
||||
if (c == Character) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
other = c;
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (target.Repairables.Any(r => r.CurrentFixer == c))
|
||||
{
|
||||
// If the other character is player, don't try to repair
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is HumanAIController operatingAI)
|
||||
{
|
||||
var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
|
||||
if (repairItemsObjective == null) { continue; }
|
||||
if (repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is not AIObjectiveRepairItem activeObjective || activeObjective.Item != target)
|
||||
{
|
||||
// Not targeting the same item.
|
||||
continue;
|
||||
}
|
||||
bool isTargetOrdered = IsOrderedToRepairThis(operatingAI);
|
||||
if (!isOrder && isTargetOrdered)
|
||||
{
|
||||
// If the other bot is ordered to repair the item, let him do it, unless we are ordered too
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOrder && !isTargetOrdered)
|
||||
{
|
||||
// We are ordered and the target is not -> allow to repair
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isTargetOrdered && operatingAI.ObjectiveManager.CurrentOrder != operatingAI.ObjectiveManager.CurrentObjective)
|
||||
{
|
||||
// The other bot is ordered to do something else
|
||||
continue;
|
||||
}
|
||||
return target.Repairables.Max(r => r.DegreeOfSuccess(Character)) <= target.Repairables.Max(r => r.DegreeOfSuccess(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
bool IsOrderedToRepairThis(HumanAIController ai) => ai.ObjectiveManager.CurrentOrder is AIObjectiveRepairItems repairOrder && repairOrder.PrioritizedItem == target;
|
||||
}
|
||||
|
||||
#region Wrappers
|
||||
public bool IsFriendly(Character other, bool onlySameTeam = false) => IsFriendly(Character, other, onlySameTeam);
|
||||
public bool IsTrueForAnyBotInTheCrew(Func<HumanAIController, bool> predicate) => IsTrueForAnyBotInTheCrew(Character, predicate);
|
||||
|
||||
@@ -562,6 +562,7 @@ namespace Barotrauma
|
||||
bool buttonsFound = false;
|
||||
// Check wired controllers (e.g. buttons)
|
||||
// Always run the buttonFilter delegate (inside CanAccessButton method), if defined, because it's used for find a valid controller component that can be used for closing the door, when needed.
|
||||
// TODO: connectionFilter is ignored in the recursive searches, so it does nothing here.
|
||||
foreach (Controller button in door.Item.GetConnectedComponents<Controller>(recursive: true, connectionFilter: c => c.Name is "toggle" or "set_state"))
|
||||
{
|
||||
buttonsFound = true;
|
||||
@@ -727,12 +728,15 @@ namespace Barotrauma
|
||||
float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition);
|
||||
//heavily prefer buttons linked to the door, so sub builders can help the bots figure out which button to use by linking them
|
||||
if (door.Item.linkedTo.Contains(button.Item)) { distance *= 0.1f; }
|
||||
if (closestButton == null || distance < closestDist && character.CanSeeTarget(button.Item))
|
||||
if (closestButton == null || distance < closestDist)
|
||||
{
|
||||
closestButton = button;
|
||||
closestDist = distance;
|
||||
if (distance < MathUtils.Pow2(button.Item.InteractDistance + GetColliderLength()) && character.CanSeeTarget(button.Item))
|
||||
{
|
||||
closestButton = button;
|
||||
closestDist = distance;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return closestButton != null;
|
||||
});
|
||||
if (canAccess)
|
||||
{
|
||||
@@ -755,41 +759,19 @@ namespace Barotrauma
|
||||
}
|
||||
else if (closestButton != null)
|
||||
{
|
||||
if (closestDist < MathUtils.Pow2(closestButton.Item.InteractDistance + GetColliderLength()))
|
||||
if (pressButton)
|
||||
{
|
||||
if (pressButton)
|
||||
if (closestButton.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
if (closestButton.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
lastDoor = (door, shouldBeOpen);
|
||||
buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonPressTimer = 0;
|
||||
}
|
||||
lastDoor = (door, shouldBeOpen);
|
||||
buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can't reach the button closest to the character.
|
||||
// It's possible that we could reach another buttons.
|
||||
// If this becomes an issue, we could go through them here and check if any of them are reachable
|
||||
// (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above)
|
||||
var body = Submarine.PickBody(character.SimPosition, character.GetRelativeSimPosition(closestButton.Item), collisionCategory: Physics.CollisionWall | Physics.CollisionLevel);
|
||||
if (body != null)
|
||||
else
|
||||
{
|
||||
if (body.UserData is Item item)
|
||||
{
|
||||
var d = item.GetComponent<Door>();
|
||||
if (d == null || d.IsOpen) { return; }
|
||||
}
|
||||
// The button is on the wrong side of the door or a wall
|
||||
currentPath.Unreachable = true;
|
||||
buttonPressTimer = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (shouldBeOpen)
|
||||
@@ -871,6 +853,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (!CanAccessDoor(door, button =>
|
||||
{
|
||||
if (Vector2.DistanceSquared(door.Item.WorldPosition, button.Item.WorldPosition) > MathUtils.Pow2(button.Item.InteractDistance + GetColliderLength()))
|
||||
{
|
||||
// Too far from the door.
|
||||
return false;
|
||||
}
|
||||
if (!ISpatialEntity.IsTargetVisible(button.Item, door.Item))
|
||||
{
|
||||
// Obstructed.
|
||||
return false;
|
||||
}
|
||||
// Ignore buttons that are on the wrong side of the door, unless there's a motion sensor connected to the door, which can be triggered by the character.
|
||||
if (door.IsHorizontal)
|
||||
{
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Character target, Character character, bool targetCharactersInOtherSubs)
|
||||
{
|
||||
if (target == null || target.Removed) { return false; }
|
||||
if (target.IsDead) { return false; }
|
||||
if (target.IsDead || target.InDetectable) { return false; }
|
||||
if (target.IsUnconscious && target.Params.Health.ConstantHealthRegeneration <= 0.0f) { return false; }
|
||||
if (target == character) { return false; }
|
||||
if (target.Submarine == null) { return false; }
|
||||
|
||||
+5
-5
@@ -387,14 +387,14 @@ namespace Barotrauma
|
||||
chairCheckTimer -= deltaTime;
|
||||
if (chairCheckTimer <= 0.0f && character.SelectedSecondaryItem == null)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
foreach (Item chair in Item.ChairItems)
|
||||
{
|
||||
if (item.CurrentHull != currentHull || !item.HasTag(Tags.ChairItem)) { continue; }
|
||||
if (chair.CurrentHull != currentHull) { continue; }
|
||||
//not possible in vanilla game, but a mod might have holdable/attachable chairs
|
||||
if (item.ParentInventory != null || item.body is { Enabled: true }) { continue; }
|
||||
var controller = item.GetComponent<Controller>();
|
||||
if (chair.ParentInventory != null || chair.body is { Enabled: true }) { continue; }
|
||||
var controller = chair.GetComponent<Controller>();
|
||||
if (controller == null || controller.User != null) { continue; }
|
||||
item.TryInteract(character, forceSelectKey: true);
|
||||
chair.TryInteract(character, forceSelectKey: true);
|
||||
}
|
||||
chairCheckTimer = chairCheckInterval;
|
||||
}
|
||||
|
||||
+75
-11
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -15,7 +16,7 @@ namespace Barotrauma
|
||||
public override bool AllowMultipleInstances => true;
|
||||
protected override bool AllowInAnySub => true;
|
||||
protected override bool AllowWhileHandcuffed => false;
|
||||
public override bool PrioritizeIfSubObjectivesActive => component != null && (component is Reactor || component is Turret);
|
||||
public override bool PrioritizeIfSubObjectivesActive => component is Reactor or Turret;
|
||||
|
||||
private readonly ItemComponent component, controller;
|
||||
private readonly Entity operateTarget;
|
||||
@@ -88,12 +89,12 @@ namespace Barotrauma
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
var reactor = component?.Item.GetComponent<Reactor>();
|
||||
var reactor = component.Item.GetComponent<Reactor>();
|
||||
if (reactor != null)
|
||||
{
|
||||
if (!isOrder)
|
||||
{
|
||||
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if (reactor.LastUserWasPlayer && character.IsOnPlayerTeam)
|
||||
{
|
||||
// The reactor was previously operated by a player -> ignore.
|
||||
Priority = 0;
|
||||
@@ -126,7 +127,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!isOrder)
|
||||
{
|
||||
var steering = component?.Item.GetComponent<Steering>();
|
||||
var steering = component.Item.GetComponent<Steering>();
|
||||
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsCaptain, onlyActive: true, onlyConnectedSubs: true)))
|
||||
{
|
||||
// Ignore if already set to autopilot or if there's a captain onboard
|
||||
@@ -137,7 +138,7 @@ namespace Barotrauma
|
||||
if (targetItem.CurrentHull == null ||
|
||||
targetItem.Submarine != character.Submarine && !isOrder ||
|
||||
targetItem.CurrentHull.FireSources.Any() ||
|
||||
HumanAIController.IsItemOperatedByAnother(target, out _) ||
|
||||
IsItemOperatedByAnother(target) ||
|
||||
Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))
|
||||
|| component.Item.IgnoreByAI(character) || useController && controller.Item.IgnoreByAI(character))
|
||||
{
|
||||
@@ -154,8 +155,8 @@ namespace Barotrauma
|
||||
else if (!OverridePriority.HasValue)
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.LowestOrderPriority * PriorityModifier);
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (reactor != null && reactor.PowerOn && reactor.FissionRate > 1 && reactor.AutoTemp && Option == "powerup")
|
||||
const float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (reactor is { PowerOn: true, FissionRate: > 1, AutoTemp: true } && Option == "powerup")
|
||||
{
|
||||
// Already on, no need to operate.
|
||||
value = 0;
|
||||
@@ -171,12 +172,12 @@ namespace Barotrauma
|
||||
Entity operateTarget = null, bool useController = false, ItemComponent controller = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option)
|
||||
{
|
||||
component = item ?? throw new ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
component = item ?? throw new ArgumentNullException(nameof(item), "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
this.requireEquip = requireEquip;
|
||||
this.operateTarget = operateTarget;
|
||||
this.useController = useController;
|
||||
if (useController) { this.controller = controller ?? component?.Item?.FindController(); }
|
||||
var target = GetTarget();
|
||||
if (useController) { this.controller = controller ?? component.Item?.FindController(); }
|
||||
ItemComponent target = GetTarget();
|
||||
if (target == null)
|
||||
{
|
||||
Abandon = true;
|
||||
@@ -320,5 +321,68 @@ namespace Barotrauma
|
||||
goToObjective = null;
|
||||
getItemObjective = null;
|
||||
}
|
||||
|
||||
private bool IsItemOperatedByAnother(ItemComponent target)
|
||||
{
|
||||
if (target?.Item == null) { return false; }
|
||||
bool isOrdered = IsOrderedToOperateTarget(HumanAIController);
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!HumanAIController.IsActive(c)) { continue; }
|
||||
if (c == character) { continue; }
|
||||
if (c.TeamID != character.TeamID) { continue; }
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (c.SelectedItem == target.Item)
|
||||
{
|
||||
// If the other character is player, don't try to operate
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is HumanAIController otherAI)
|
||||
{
|
||||
if (otherAI.ObjectiveManager.Objectives.None(o => o is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item))
|
||||
{
|
||||
// Not targeting the same item.
|
||||
continue;
|
||||
}
|
||||
bool isOtherCharacterOrdered = IsOrderedToOperateTarget(otherAI);
|
||||
switch (isOrdered)
|
||||
{
|
||||
case false when isOtherCharacterOrdered:
|
||||
// We are not ordered and the target is ordered -> let the other character operate the target item.
|
||||
return true;
|
||||
case true when !isOtherCharacterOrdered:
|
||||
// We are ordered and the other character is not -> allow to us to operate the target item.
|
||||
continue;
|
||||
default:
|
||||
{
|
||||
// Neither or both are ordered to operate this item.
|
||||
if (!IsOperatingTarget(otherAI))
|
||||
{
|
||||
// The other bot is doing something else -> stick to the target.
|
||||
continue;
|
||||
}
|
||||
if (target is Steering)
|
||||
{
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
if (character.GetSkillLevel(Tags.HelmSkill) <= c.GetSkillLevel(Tags.HelmSkill))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
bool IsOrderedToOperateTarget(HumanAIController ai) => ai.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item;
|
||||
bool IsOperatingTarget(HumanAIController ai) => ai.ObjectiveManager.CurrentObjective is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
if (HumanAIController.IsItemRepairedByAnother(Item, out _))
|
||||
if (AIObjectiveRepairItems.IsItemRepairedByAnother(character, Item))
|
||||
{
|
||||
Priority = 0;
|
||||
IsCompleted = true;
|
||||
|
||||
+53
-1
@@ -76,7 +76,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Repairables.None(r => r.RequiredSkills.Any(s => s.Identifier == RelevantSkill))) { return false; }
|
||||
}
|
||||
return !HumanAIController.IsItemRepairedByAnother(item, out _);
|
||||
return !IsItemRepairedByAnother(character, item);
|
||||
}
|
||||
|
||||
public static bool ViableForRepair(Item item, Character character, HumanAIController humanAIController)
|
||||
@@ -161,5 +161,57 @@ namespace Barotrauma
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsItemRepairedByAnother(Character character, Item target)
|
||||
{
|
||||
if (target == null) { return false; }
|
||||
bool isOrder = IsOrderedToPrioritizeTarget(character.AIController as HumanAIController);
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!HumanAIController.IsActive(c)) { continue; }
|
||||
if (c == character) { continue; }
|
||||
if (c.TeamID != character.TeamID) { continue; }
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (target.Repairables.Any(r => r.CurrentFixer == c))
|
||||
{
|
||||
// If the other character is player, don't try to repair
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is HumanAIController otherAI)
|
||||
{
|
||||
var repairItemsObjective = otherAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
|
||||
if (repairItemsObjective == null) { continue; }
|
||||
if (repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is not AIObjectiveRepairItem activeObjective || activeObjective.Item != target)
|
||||
{
|
||||
// Not targeting the same item.
|
||||
continue;
|
||||
}
|
||||
bool isTargetOrdered = IsOrderedToPrioritizeTarget(otherAI);
|
||||
switch (isOrder)
|
||||
{
|
||||
case false when isTargetOrdered:
|
||||
// We are not ordered and the target is ordered -> let the other character repair the target.
|
||||
return true;
|
||||
case true when !isTargetOrdered:
|
||||
// We are ordered and the target is not -> allow us to repair the target.
|
||||
continue;
|
||||
default:
|
||||
{
|
||||
// Neither or both are ordered to repair this item.
|
||||
if (otherAI.ObjectiveManager.CurrentObjective is not AIObjectiveRepairItems)
|
||||
{
|
||||
// The other bot is doing something else -> stick to the target.
|
||||
continue;
|
||||
}
|
||||
return target.Repairables.Max(r => r.DegreeOfSuccess(character)) <= target.Repairables.Max(r => r.DegreeOfSuccess(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
bool IsOrderedToPrioritizeTarget(HumanAIController ai) => ai.ObjectiveManager.CurrentOrder is AIObjectiveRepairItems repairOrder && repairOrder.PrioritizedItem == target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -149,7 +149,7 @@ namespace Barotrauma
|
||||
if (HumanAIController.VisibleHulls.Contains(Target.CurrentHull) && Target.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[targetname]", Target.DisplayName, FormatCapitals.No),
|
||||
("[roomname]", Target.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundunconscioustarget{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ namespace Barotrauma
|
||||
if (Target.CurrentHull?.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget",
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[targetname]", Target.DisplayName, FormatCapitals.No),
|
||||
("[roomname]", Target.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundwoundedtarget{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
@@ -287,6 +287,8 @@ namespace Barotrauma
|
||||
currentTreatmentSuitabilities,
|
||||
limb: Target.CharacterHealth.GetAfflictionLimb(affliction),
|
||||
user: character,
|
||||
checkTreatmentThreshold: true,
|
||||
checkTreatmentSuggestionThreshold: false,
|
||||
predictFutureDuration: 10.0f);
|
||||
|
||||
foreach (KeyValuePair<Identifier, float> treatmentSuitability in currentTreatmentSuitabilities)
|
||||
@@ -330,7 +332,10 @@ namespace Barotrauma
|
||||
{
|
||||
//get "overall" suitability for no specific limb at this point
|
||||
Target.CharacterHealth.GetSuitableTreatments(
|
||||
currentTreatmentSuitabilities, user: character, predictFutureDuration: 10.0f);
|
||||
currentTreatmentSuitabilities, user: character,
|
||||
checkTreatmentThreshold: true,
|
||||
checkTreatmentSuggestionThreshold: false,
|
||||
predictFutureDuration: 10.0f);
|
||||
//didn't have any suitable treatments available, try to find some medical items
|
||||
if (currentTreatmentSuitabilities.Any(s => s.Value > cprSuitability))
|
||||
{
|
||||
@@ -387,7 +392,7 @@ namespace Barotrauma
|
||||
if (Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments",
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[targetname]", Target.DisplayName, FormatCapitals.No),
|
||||
("[treatmentlist]", itemListStr, FormatCapitals.Yes)).Value,
|
||||
null, 2.0f, $"listrequiredtreatments{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
@@ -483,7 +488,7 @@ namespace Barotrauma
|
||||
if (IsCompleted && Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
string textTag = performedCpr ? "DialogTargetResuscitated" : "DialogTargetHealed";
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", Target.Name)?.Value;
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", Target.DisplayName)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{Target.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
return IsCompleted;
|
||||
|
||||
+2
-2
@@ -47,9 +47,9 @@ namespace Barotrauma
|
||||
if (objectiveManager.GetFirstActiveObjective<AIObjectiveRescue>() == null)
|
||||
{
|
||||
charactersWithMinorInjuries.Add(target);
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.DisplayName).Value,
|
||||
delay: 1.0f,
|
||||
identifier: $"notreatableafflictions{target.Name}".ToIdentifier(),
|
||||
identifier: $"notreatableafflictions{target.DisplayName}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -50,7 +50,8 @@ namespace Barotrauma
|
||||
if (OrderedCharacter.AIController is HumanAIController humanAI &&
|
||||
humanAI.ObjectiveManager.CurrentOrders.None(o => o.MatchesOrder(SuggestedOrder.Identifier, Option) && o.TargetEntity == TargetItem))
|
||||
{
|
||||
if (orderedCharacter != CommandingCharacter)
|
||||
bool orderGivenByDifferentCharacter = orderedCharacter != CommandingCharacter;
|
||||
if (orderGivenByDifferentCharacter)
|
||||
{
|
||||
CommandingCharacter.Speak(SuggestedOrder.GetChatMessage(OrderedCharacter.Name, "", givingOrderToSelf: false),
|
||||
minDurationBetweenSimilar: 5,
|
||||
@@ -62,9 +63,12 @@ namespace Barotrauma
|
||||
.WithOrderGiver(CommandingCharacter)
|
||||
.WithManualPriority(CharacterInfo.HighestManualOrderPriority);
|
||||
OrderedCharacter.SetOrder(CurrentOrder, CommandingCharacter != OrderedCharacter);
|
||||
OrderedCharacter.Speak(TextManager.Get("DialogAffirmative").Value, delay: 1.0f,
|
||||
minDurationBetweenSimilar: 5,
|
||||
identifier: ("ReceiveOrder." + SuggestedOrder.Prefab.Identifier).ToIdentifier());
|
||||
if (orderGivenByDifferentCharacter)
|
||||
{
|
||||
OrderedCharacter.Speak(TextManager.Get("DialogAffirmative").Value, delay: 1.0f,
|
||||
minDurationBetweenSimilar: 5,
|
||||
identifier: ("ReceiveOrder." + SuggestedOrder.Prefab.Identifier).ToIdentifier());
|
||||
}
|
||||
}
|
||||
TimeSinceLastAttempt = 0f;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using Barotrauma.Networking;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -89,12 +90,12 @@ namespace Barotrauma
|
||||
{
|
||||
public bool IsAlive { get; private set; }
|
||||
|
||||
private readonly List<Item> allItems;
|
||||
private readonly List<Item> thalamusItems;
|
||||
private readonly List<Structure> thalamusStructures;
|
||||
private readonly List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
private readonly List<Hull> hulls = new List<Hull>();
|
||||
private readonly List<Item> spawnOrgans = new List<Item>();
|
||||
private readonly List<Door> jammedDoors = new List<Door>();
|
||||
private readonly Item brain;
|
||||
|
||||
private bool initialCellsSpawned;
|
||||
@@ -105,7 +106,7 @@ namespace Barotrauma
|
||||
|
||||
private bool IsThalamus(MapEntityPrefab entityPrefab) => IsThalamus(entityPrefab, Config.Entity);
|
||||
|
||||
private static IEnumerable<T> GetThalamusEntities<T>(Submarine wreck, Identifier tag) where T : MapEntity => GetThalamusEntities(wreck, tag).Where(e => e is T).Select(e => e as T);
|
||||
private static IEnumerable<T> GetThalamusEntities<T>(Submarine wreck, Identifier tag) where T : MapEntity => GetThalamusEntities(wreck, tag).OfType<T>();
|
||||
|
||||
private static IEnumerable<MapEntity> GetThalamusEntities(Submarine wreck, Identifier tag) => MapEntity.MapEntityList.Where(e => e.Submarine == wreck && e.Prefab != null && IsThalamus(e.Prefab, tag));
|
||||
|
||||
@@ -122,93 +123,52 @@ namespace Barotrauma
|
||||
{
|
||||
GetConfig();
|
||||
if (Config == null) { return; }
|
||||
var thalamusPrefabs = ItemPrefab.Prefabs.Where(p => IsThalamus(p));
|
||||
var thalamusPrefabs = ItemPrefab.Prefabs.Where(IsThalamus);
|
||||
var brainPrefab = thalamusPrefabs.GetRandom(i => i.Tags.Contains(Config.Brain), Rand.RandSync.ServerAndClient);
|
||||
if (brainPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"WreckAI: Could not find any brain prefab with the tag {Config.Brain}! Cannot continue. Failed to create wreck AI.");
|
||||
DebugConsole.ThrowError($"WreckAI {wreck.Info.Name}: Could not find any brain prefab with the tag {Config.Brain}! Cannot continue. Failed to create wreck AI.", contentPackage: Config.ContentPackage);
|
||||
return;
|
||||
}
|
||||
allItems = wreck.GetItems(false);
|
||||
thalamusItems = allItems.FindAll(i => IsThalamus(((MapEntity)i).Prefab));
|
||||
hulls.AddRange(wreck.GetHulls(false));
|
||||
var potentialBrainHulls = new List<(Hull hull, float weight)>();
|
||||
thalamusItems = GetThalamusEntities<Item>(wreck, Config.Entity).ToList();
|
||||
hulls.AddRange(wreck.GetHulls(alsoFromConnectedSubs: false));
|
||||
brain = new Item(brainPrefab, Vector2.Zero, wreck);
|
||||
thalamusItems.Add(brain);
|
||||
Point minSize = brain.Rect.Size.Multiply(brain.Scale);
|
||||
// Bigger hulls are allowed, but not preferred more than what's sufficent.
|
||||
Vector2 sufficentSize = new Vector2(minSize.X * 2, minSize.Y * 1.1f);
|
||||
// Shrink the horizontal axis so that the brain is not placed in the left or right side, where we often have curved walls.
|
||||
Rectangle shrinkedBounds = ToolBox.GetWorldBounds(wreck.WorldPosition.ToPoint(), new Point(wreck.Borders.Width - 500, wreck.Borders.Height));
|
||||
foreach (Hull hull in hulls)
|
||||
{
|
||||
float distanceFromCenter = Vector2.Distance(wreck.WorldPosition, hull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1.0f, 0.5f, MathUtils.InverseLerp(0, Math.Max(shrinkedBounds.Width, shrinkedBounds.Height) / 2, distanceFromCenter));
|
||||
float horizontalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.X, sufficentSize.X, hull.Rect.Width));
|
||||
float verticalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.Y, sufficentSize.Y, hull.Rect.Height));
|
||||
float weight = verticalSizeFactor * horizontalSizeFactor * distanceFactor;
|
||||
if (hull.GetLinkedEntities<Hull>().Any())
|
||||
{
|
||||
// Ignore hulls that have any linked hulls to keep the calculations simple.
|
||||
continue;
|
||||
}
|
||||
else if (hull.ConnectedGaps.Any(g => g.Open > 0 && (!g.IsRoomToRoom || g.Position.Y < hull.Position.Y)))
|
||||
{
|
||||
// Ignore hulls that have open gaps to outside or below the center point, because we'll want the room to be full of water and not be accessible without breaking the wall.
|
||||
continue;
|
||||
}
|
||||
else if (thalamusItems.Any(i => i.CurrentHull == hull))
|
||||
{
|
||||
// Don't create the brain in a room that already has thalamus items inside it.
|
||||
continue;
|
||||
}
|
||||
else if (hull.Rect.Width < minSize.X || hull.Rect.Height < minSize.Y)
|
||||
{
|
||||
// Don't select too small rooms.
|
||||
continue;
|
||||
}
|
||||
if (weight > 0)
|
||||
{
|
||||
potentialBrainHulls.Add((hull, weight));
|
||||
}
|
||||
}
|
||||
var potentialBrainHulls = GetPotentialBrainRooms(wreck, Config, minSize, thalamusItems);
|
||||
Hull brainHull = ToolBox.SelectWeightedRandom(potentialBrainHulls.Select(pbh => pbh.hull).ToList(), potentialBrainHulls.Select(pbh => pbh.weight).ToList(), Rand.RandSync.ServerAndClient);
|
||||
var thalamusStructurePrefabs = StructurePrefab.Prefabs.Where(IsThalamus);
|
||||
if (brainHull == null)
|
||||
{
|
||||
DebugConsole.AddWarning("Wreck AI: Cannot find a proper room for the brain. Using a random room.");
|
||||
DebugConsole.ThrowError($"Wreck AI {wreck.Info.Name}: Cannot find a suitable room for the Thalamus brain. Using a random room. " +
|
||||
$"The wreck should be fixed so that there's at least one room where the following conditions are met: No linked hulls, no open gaps in the floor or to outside the sub, and no other Thalamus items present in the hull.",
|
||||
contentPackage: Config.ContentPackage);
|
||||
|
||||
brainHull = hulls.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
if (brainHull == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Wreck AI: Cannot find any room for the brain! Failed to create the Thalamus.");
|
||||
DebugConsole.ThrowError($"Wreck AI {wreck.Info.Name}: Cannot find any room for the brain! Failed to create the Thalamus.", contentPackage: Config.ContentPackage);
|
||||
return;
|
||||
}
|
||||
Debug.WriteLine($"Wreck AI {wreck.Info.Name}: Selected brain room: {brainHull.DisplayName}");
|
||||
brainHull.WaterVolume = brainHull.Volume;
|
||||
brain.SetTransform(brainHull.SimPosition, rotation: 0, findNewHull: false);
|
||||
brain.CurrentHull = brainHull;
|
||||
|
||||
// Jam the doors, mainly to prevent any mechanisms from opening them. Also makes it a little bit more difficult for the player to breach into the brain room, because they now have to break the door.
|
||||
foreach (Door door in brainHull.ConnectedGaps.Select(g => g.ConnectedDoor))
|
||||
{
|
||||
if (door == null) { continue; }
|
||||
door.IsJammed = true;
|
||||
jammedDoors.Add(door);
|
||||
}
|
||||
|
||||
var backgroundPrefab = thalamusStructurePrefabs.GetRandom(i => i.Tags.Contains(Config.BrainRoomBackground), Rand.RandSync.ServerAndClient);
|
||||
if (backgroundPrefab != null)
|
||||
{
|
||||
new Structure(brainHull.Rect, backgroundPrefab, wreck);
|
||||
}
|
||||
var horizontalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(Config.BrainRoomHorizontalWall), Rand.RandSync.ServerAndClient);
|
||||
if (horizontalWallPrefab != null)
|
||||
{
|
||||
int height = (int)horizontalWallPrefab.Size.Y;
|
||||
int halfHeight = height / 2;
|
||||
int quarterHeight = halfHeight / 2;
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top - brainHull.Rect.Height + halfHeight + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, wreck);
|
||||
}
|
||||
var verticalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(Config.BrainRoomVerticalWall), Rand.RandSync.ServerAndClient);
|
||||
if (verticalWallPrefab != null)
|
||||
{
|
||||
int width = (int)verticalWallPrefab.Size.X;
|
||||
int halfWidth = width / 2;
|
||||
int quarterWidth = halfWidth / 2;
|
||||
new Structure(new Rectangle(brainHull.Rect.Left - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Right - halfWidth - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, wreck);
|
||||
var background = new Structure(brainHull.Rect, backgroundPrefab, wreck);
|
||||
background.SpriteDepth -= 0.01f;
|
||||
}
|
||||
foreach (Item item in thalamusItems)
|
||||
{
|
||||
@@ -360,6 +320,7 @@ namespace Barotrauma
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
jammedDoors.ForEach(d => d.IsJammed = false);
|
||||
thalamusItems.ForEach(i => i.Condition = 0);
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
@@ -376,27 +337,24 @@ namespace Barotrauma
|
||||
protectiveCells.ForEach(c => c.OnDeath -= OnCellDeath);
|
||||
if (!IsClient)
|
||||
{
|
||||
if (Config != null)
|
||||
if (Config is { KillAgentsWhenEntityDies: true })
|
||||
{
|
||||
if (Config.KillAgentsWhenEntityDies)
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null));
|
||||
if (!string.IsNullOrWhiteSpace(Config.OffensiveAgent))
|
||||
{
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null));
|
||||
if (!string.IsNullOrWhiteSpace(Config.OffensiveAgent))
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
// Kills ALL offensive agents that are near the thalamus. Not the ideal solution,
|
||||
// but as long as spawning is handled via status effects, I don't know if there is any better way.
|
||||
// In practice there shouldn't be terminal cells from different thalamus organisms at the same time.
|
||||
// And if there was, the distance check should prevent killing the agents of a different organism.
|
||||
if (character.SpeciesName == Config.OffensiveAgent)
|
||||
{
|
||||
// Kills ALL offensive agents that are near the thalamus. Not the ideal solution,
|
||||
// but as long as spawning is handled via status effects, I don't know if there is any better way.
|
||||
// In practice there shouldn't be terminal cells from different thalamus organisms at the same time.
|
||||
// And if there was, the distance check should prevent killing the agents of a different organism.
|
||||
if (character.SpeciesName == Config.OffensiveAgent)
|
||||
// Sonar distance is used also for wreck positioning. No wreck should be closer to each other than this.
|
||||
float maxDistance = Sonar.DefaultSonarRange;
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Submarine.WorldPosition) < maxDistance * maxDistance)
|
||||
{
|
||||
// Sonar distance is used also for wreck positioning. No wreck should be closer to each other than this.
|
||||
float maxDistance = Sonar.DefaultSonarRange;
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Submarine.WorldPosition) < maxDistance * maxDistance)
|
||||
{
|
||||
character.Kill(CauseOfDeathType.Unknown, null);
|
||||
}
|
||||
character.Kill(CauseOfDeathType.Unknown, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,5 +473,62 @@ namespace Barotrauma
|
||||
msg.WriteBoolean(IsAlive);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static List<(Hull hull, float weight)> GetPotentialBrainRooms(Submarine wreck, WreckAIConfig wreckAI, Point minSize, IEnumerable<Item> thalamusItems = null)
|
||||
{
|
||||
var potentialBrainHulls = new List<(Hull hull, float weight)>();
|
||||
// Bigger hulls are allowed, but not preferred more than what's sufficient.
|
||||
Vector2 sufficientSize = new Vector2(minSize.X * 2, minSize.Y * 1.1f);
|
||||
Rectangle worldBounds = ToolBox.GetWorldBounds(wreck.WorldPosition.ToPoint(), new Point(wreck.Borders.Width, wreck.Borders.Height));
|
||||
thalamusItems ??= GetThalamusEntities<Item>(wreck, wreckAI.Entity);
|
||||
foreach (Hull hull in wreck.GetHulls(alsoFromConnectedSubs: false))
|
||||
{
|
||||
if (hull.GetLinkedEntities<Hull>().Any())
|
||||
{
|
||||
// Ignore hulls that have any linked hulls to keep the calculations simple.
|
||||
continue;
|
||||
}
|
||||
else if (hull.ConnectedGaps.Any(g => (g.Open > 0 || g.ConnectedDoor?.Item.Condition <= 0) && (!g.IsRoomToRoom || g.Position.Y < hull.Position.Y)))
|
||||
{
|
||||
// Ignore hulls that have open gaps to outside or below the center point, because we'll want the room to be full of water and not be accessible without breaking the wall.
|
||||
// Gaps in the broken doors are not yet open at this stage. Also Door.IsBroken is not yet up-to-date, so we'll have to check the item condition.
|
||||
continue;
|
||||
}
|
||||
else if (thalamusItems.Any(i => i.CurrentHull == hull && !i.HasTag(Tags.WireItem)))
|
||||
{
|
||||
// Don't create the brain in a room that already has thalamus items inside it.
|
||||
continue;
|
||||
}
|
||||
else if (hull.Rect.Width < minSize.X || hull.Rect.Height < minSize.Y)
|
||||
{
|
||||
// Don't select too small rooms.
|
||||
continue;
|
||||
}
|
||||
float weight = 0;
|
||||
if (hull.IsAirlock)
|
||||
{
|
||||
// Prefer something else than airlocks
|
||||
weight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float distanceFromCenter = Vector2.Distance(wreck.WorldPosition, hull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1.0f, 0.5f, MathUtils.InverseLerp(0, Math.Max(worldBounds.Width, worldBounds.Height) / 2f, distanceFromCenter));
|
||||
float horizontalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.X, sufficientSize.X, hull.Rect.Width));
|
||||
float verticalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.Y, sufficientSize.Y, hull.Rect.Height));
|
||||
weight = verticalSizeFactor * horizontalSizeFactor * distanceFactor;
|
||||
}
|
||||
if (weight > 0 || potentialBrainHulls.None())
|
||||
{
|
||||
potentialBrainHulls.Add((hull, weight));
|
||||
}
|
||||
}
|
||||
Debug.WriteLine($"Wreck AI {wreck.Info.Name}: Potential brain rooms: {potentialBrainHulls.Count}");
|
||||
foreach ((Hull hull, float weight) in potentialBrainHulls)
|
||||
{
|
||||
Debug.WriteLine($"Wreck AI: Potential brain room: {hull.DisplayName}, {weight.FormatSingleDecimal()}");
|
||||
}
|
||||
return potentialBrainHulls;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user