(7788ec72a) Test issuing orders automatically.

This commit is contained in:
Joonas Rikkonen
2019-05-16 05:03:49 +03:00
parent d00e2975ba
commit 60f52375e6
202 changed files with 2714 additions and 5825 deletions
@@ -14,11 +14,6 @@ namespace Barotrauma
{
public static bool DisableEnemyAI;
/// <summary>
/// Enable the character to attack the outposts and the characters inside them. Disabled by default.
/// </summary>
public bool TargetOutposts;
class WallTarget
{
public Vector2 Position;
@@ -122,7 +117,6 @@ namespace Barotrauma
private readonly float memoryFadeTime = 0.5f;
public LatchOntoAI LatchOntoAI { get; private set; }
public SwarmBehavior SwarmBehavior { get; private set; }
public bool AttackHumans
{
@@ -216,10 +210,6 @@ namespace Barotrauma
case "latchonto":
LatchOntoAI = new LatchOntoAI(subElement, this);
break;
case "swarm":
case "swarmbehavior":
SwarmBehavior = new SwarmBehavior(subElement, this);
break;
case "targetpriority":
targetingPriorities.Add(subElement.GetAttributeString("tag", "").ToLowerInvariant(), new TargetingPriority(subElement));
break;
@@ -320,9 +310,8 @@ namespace Barotrauma
{
State = AIState.Idle;
}
else if (Character.Health < fleeHealthThreshold && SwarmBehavior == null)
else if (Character.Health < fleeHealthThreshold)
{
// Don't flee from damage if in a swarm.
State = AIState.Escape;
}
else if (targetingPriority != null)
@@ -331,6 +320,8 @@ namespace Barotrauma
}
}
LatchOntoAI?.Update(this, deltaTime);
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
{
State = AIState.Idle;
@@ -369,14 +360,11 @@ namespace Barotrauma
throw new NotImplementedException();
}
LatchOntoAI?.Update(this, deltaTime);
IsSteeringThroughGap = false;
if (SwarmBehavior != null)
{
SwarmBehavior.IsActive = State == AIState.Idle && Character.CurrentHull == null;
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
// Just some debug code that makes the characters to follow the mouse cursor
//run = true;
//Vector2 mousePos = ConvertUnits.ToSimUnits(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
//steeringManager.SteeringSeek(mousePos, Character.AnimController.GetCurrentSpeed(run));
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run));
}
@@ -499,7 +487,7 @@ namespace Barotrauma
}
else
{
if (!IsLatchedOnSub)
if (!IsProperlyLatchedOnSub)
{
UpdateWallTarget();
}
@@ -558,7 +546,7 @@ namespace Barotrauma
{
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
if (section?.gap != null && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
if (section?.gap != null && section.gap.IsRoomToRoom && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime))
{
return;
}
@@ -799,21 +787,9 @@ namespace Barotrauma
}
}
public bool IsSteeringThroughGap { get; private set; }
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
{
IsSteeringThroughGap = true;
SelectedAiTarget = wall.AiTarget;
wallTarget = null;
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
Hull targetHull = section.gap?.FlowTargetHull;
float distance = Vector2.Distance(Character.WorldPosition, targetWorldPos);
float maxDistance = Math.Min(wall.Rect.Width, wall.Rect.Height);
if (distance > maxDistance)
{
return false;
}
if (targetHull != null)
{
if (wall.IsHorizontal)
@@ -824,7 +800,16 @@ namespace Barotrauma
{
targetWorldPos.X = targetHull.WorldRect.Center.X;
}
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
LatchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
if (steeringManager is IndoorsSteeringManager)
{
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
}
else
{
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos));
}
return true;
}
return false;
@@ -1056,43 +1041,19 @@ namespace Barotrauma
#region Targeting
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
private bool IsProperlyLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub && SelectedAiTarget?.Entity == wallTarget?.Structure;
//goes through all the AItargets, evaluates how preferable it is to attack the target,
//whether the Character can see/hear the target and chooses the most preferable target within
//sight/hearing range
public AITarget UpdateTargets(Character character, out TargetingPriority priority)
{
if ((SelectedAiTarget != null || wallTarget != null) && IsLatchedOnSub)
if (IsProperlyLatchedOnSub)
{
var wall = SelectedAiTarget.Entity as Structure;
if (wall == null)
{
wall = wallTarget?.Structure;
}
// The target is not a wall or it's not the same as we are attached to -> release
bool releaseTarget = wall == null || !wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB);
if (!releaseTarget)
{
for (int i = 0; i < wall.Sections.Length; i++)
{
if (CanPassThroughHole(wall, i))
{
releaseTarget = true;
}
}
}
if (releaseTarget)
{
SelectedAiTarget = null;
wallTarget = null;
LatchOntoAI.DeattachFromBody();
}
else if (SelectedAiTarget?.Entity == wallTarget?.Structure)
{
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
AITarget newTarget = null;
priority = null;
@@ -1107,10 +1068,9 @@ namespace Barotrauma
continue;
}
if (target.Type == AITarget.TargetType.HumanOnly) { continue; }
if (!TargetOutposts)
{
if (target.Entity.Submarine != null && target.Entity.Submarine.IsOutpost) { continue; }
}
// Don't attack outposts.
if (target.Entity.Submarine != null && target.Entity.Submarine.IsOutpost) { continue; }
Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the Character itself
if (targetCharacter == character) continue;
@@ -1175,7 +1135,7 @@ namespace Barotrauma
else if (target.Entity != null)
{
//skip the target if it's a room and the character is already inside a sub
if (character.CurrentHull != null && target.Entity is Hull) { continue; }
if (character.CurrentHull != null && target.Entity is Hull) continue;
Door door = null;
if (target.Entity is Item item)
@@ -1204,37 +1164,23 @@ namespace Barotrauma
// Ignore structures that doesn't have a body (not walls)
continue;
}
if (s.IsPlatform)
{
continue;
}
if (character.CurrentHull != null)
{
// Ignore walls when inside.
continue;
}
valueModifier = 1;
float wallMaxHealth = 400; // Anything more than this is ignored -> 200 = 1
// Prefer weaker targets.
valueModifier *= MathHelper.Lerp(1.5f, 0.5f, MathUtils.InverseLerp(0, 1, s.Health / wallMaxHealth));
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 1 : 0;
if (aggressiveBoarding)
{
var hulls = s.Submarine.GetHulls(false);
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
if (section.gap != null)
if (CanPassThroughHole(s, i))
{
if (CanPassThroughHole(s, i))
{
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null && hulls.Any(h => h.Rect.Intersects(section.rect));
valueModifier *= leadsInside ? 5 : 0;
}
else
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
// Ignore walls that can be passed through
valueModifier = 0;
break;
}
else if (section.gap != null)
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
}
}
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -54,6 +55,28 @@ namespace Barotrauma
objectiveManager = new AIObjectiveManager(c);
objectiveManager.AddObjective(new AIObjectiveFindSafety(c));
objectiveManager.AddObjective(new AIObjectiveIdle(c));
// TODO: do this only when the player hasn't issued an order for a while
foreach (var automaticOrder in c.Info.Job.Prefab.AutomaticOrders)
{
var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag);
// TODO: Similar code is used in CrewManager:815-> DRY
var matchingItems = orderPrefab.ItemIdentifiers.Any() ?
Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) :
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType));
matchingItems.RemoveAll(it => it.Submarine != c.Submarine);
var item = matchingItems.GetRandom();
var order = new Order(orderPrefab, item ?? c.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType));
SetOrder(order, automaticOrder.option, c, true);
//#if CLIENT
// GameMain.GameSession?.CrewManager?.SetCharacterOrder(c, order, automaticOrder.option, c);
//#endif
// if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(order, order.FadeOutTime))
// {
// Character.Speak(order.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: true), ChatMessageType.Order);
// }
}
updateObjectiveTimer = Rand.Range(0.0f, UpdateObjectiveInterval);
@@ -267,7 +290,7 @@ namespace Barotrauma
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
{
Character.Speak(
newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order);
}
}
}
@@ -286,18 +309,18 @@ namespace Barotrauma
if (Character.PressureTimer > 50.0f && Character.CurrentHull != null)
{
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.DisplayName), null, 0, "pressure", 30.0f);
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
}
}
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
// Damage from falling etc.
if (Character.LastDamageSource == null) { return; }
float damage = attackResult.Damage;
if (damage <= 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
// Ignore damage from falling etc that we shouldn't react to.
if (Character.LastDamageSource == null) { return; }
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
}
else if (IsFriendly(attacker))
@@ -223,10 +223,8 @@ namespace Barotrauma
}
}
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
//only humanoids can climb ladders
if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
if (character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
{
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
@@ -236,7 +234,7 @@ namespace Barotrauma
}
var collider = character.AnimController.Collider;
if (character.IsClimbing && !isDiving)
if (character.IsClimbing)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
@@ -280,12 +278,6 @@ namespace Barotrauma
}
else if (character.AnimController.InWater)
{
// If the character is underwater, we don't need the ladders anymore
if (character.IsClimbing && isDiving)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
}
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
{
currentPath.SkipToNextNode();
@@ -396,18 +388,6 @@ namespace Barotrauma
buttonPressCooldown = ButtonPressInterval;
break;
}
else
{
if (!door.HasRequiredItems(character, false) && shouldBeOpen)
{
currentPath.Unreachable = true;
return;
}
door.Item.TryInteract(character, false, true, true);
buttonPressCooldown = ButtonPressInterval;
break;
}
}
}
}
@@ -428,18 +408,20 @@ namespace Barotrauma
//door closed and the character can't open doors -> node can't be traversed
if (!canOpenDoors || character.LockHands) { return null; }
if (!canBreakDoors)
{
//door closed and the character can't open doors -> node can't be traversed
if (!canOpenDoors || character.LockHands) return null;
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>();
if (!doorButtons.Any())
{
if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; }
}
if (!doorButtons.Any()) return null;
foreach (Controller button in doorButtons)
{
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
Math.Sign(node.Position.X - nextNode.Position.X)) { continue; }
Math.Sign(node.Position.X - nextNode.Position.X)) continue;
if (!button.HasRequiredItems(character, false)) { return null; }
if (!button.HasRequiredItems(character, false)) return null;
}
}
}
@@ -54,7 +54,7 @@ namespace Barotrauma
get { return attachJoints.Count > 0; }
}
public bool IsAttachedToSub => IsAttached && (attachTargetBody?.UserData is Submarine || attachTargetBody?.UserData is Entity entity && entity.Submarine != null);
public bool IsAttachedToSub => IsAttached && attachTargetBody?.UserData is Entity entity && (entity is Submarine sub || entity?.Submarine != null);
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
{
@@ -190,7 +190,7 @@ namespace Barotrauma
case AIController.AIState.Attack:
if (enemyAI.AttackingLimb != null)
{
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
if (attachToSub && wallAttachPos != Vector2.Zero && attachTargetBody != null)
{
// is not attached or is attached to something else
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
@@ -44,7 +44,6 @@ namespace Barotrauma
private AIObjectiveContainItem reloadWeaponObjective;
private Hull retreatTarget;
private AIObjectiveGoTo retreatObjective;
private AIObjectiveFindSafety findSafety;
private float coolDownTimer;
@@ -61,9 +60,7 @@ namespace Barotrauma
{
Enemy = enemy;
coolDownTimer = CoolDown;
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>();
findSafety.Priority = 0;
findSafety.unreachable.Clear();
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 0;
Mode = mode;
if (Enemy == null)
{
@@ -178,7 +175,7 @@ namespace Barotrauma
{
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
{
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
retreatTarget = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(new List<Hull>() { character.CurrentHull });
}
if (retreatTarget != null)
{
@@ -280,6 +277,7 @@ namespace Barotrauma
{
abandon = true;
SteeringManager.Reset();
//HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
public override bool IsCompleted()
@@ -79,44 +79,26 @@ namespace Barotrauma
foreach (FireSource fs in targetHull.FireSources)
{
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
bool move = !inRange;
if (inRange || useExtinquisherTimer > 0.0f)
if (targetHull == character.CurrentHull && (inRange || useExtinquisherTimer > 0.0f))
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f)
{
useExtinquisherTimer = 0.0f;
}
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
character.SetInput(InputType.Aim, false, true);
}
Limb sightLimb = null;
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
}
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
if (!character.CanSeeTarget(fs, sightLimb))
{
move = true;
}
else
{
move = false;
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
}
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
}
return;
}
if (move)
else
{
//go to the first firesource
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
@@ -127,8 +109,8 @@ namespace Barotrauma
{
gotoObjective.TryComplete(deltaTime);
}
break;
}
break;
}
}
}
@@ -18,7 +18,7 @@ namespace Barotrauma
const float SearchHullInterval = 3.0f;
const float clearUnreachableInterval = 30;
public readonly List<Hull> unreachable = new List<Hull>();
private List<Hull> unreachable = new List<Hull>();
private float currenthullSafety;
private float unreachableClearTimer;
@@ -60,16 +60,11 @@ namespace Barotrauma
else
{
divingGearObjective = null;
// Reduce the timer so that we get a safe hull target faster.
searchHullTimer = Math.Min(1, searchHullTimer);
// Reset the timer so that we get a safe hull target.
searchHullTimer = 0;
}
}
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
searchHullTimer = Math.Min(1, searchHullTimer);
}
if (unreachableClearTimer > 0)
{
unreachableClearTimer -= deltaTime;
@@ -84,7 +79,7 @@ namespace Barotrauma
{
searchHullTimer -= deltaTime;
}
else
else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
var bestHull = FindBestHull();
if (bestHull != null && bestHull != currentHull)
@@ -193,17 +188,10 @@ namespace Barotrauma
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;
//skip the hull if the safety is already less than the best hull
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
if (hullSafety < bestValue) { continue; }
// Each unsafe node reduces the hull safety value.
// Ignore current hull, 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.Unreachable)
{
unreachable.Add(hull);
continue;
}
if (path.Unreachable) { continue; }
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
@@ -231,6 +219,7 @@ namespace Barotrauma
}
}
}
// Huge preference for closer targets
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
@@ -101,22 +101,10 @@ namespace Barotrauma
HumanAIController.AnimController.Crouching = true;
}
//float reach = HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range);
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
if (canReach)
{
Limb sightLimb = null;
if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.RightHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
}
else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
canReach = character.CanSeeTarget(leak, sightLimb);
}
else
bool cannotReach = ConvertUnits.ToSimUnits(gapDiff.Length()) > reach;
if (cannotReach)
{
if (gotoObjective != null)
{
@@ -142,6 +130,7 @@ namespace Barotrauma
AddSubObjective(gotoObjective);
}
}
canReach = character.CanSeeTarget(leak, sightLimb);
}
if (gotoObjective == null || gotoObjective.IsCompleted())
{
@@ -26,9 +26,6 @@ namespace Barotrauma
private float standStillTimer;
private float walkDuration;
private readonly List<Hull> targetHulls = new List<Hull>(20);
private readonly List<float> hullWeights = new List<float>(20);
public AIObjectiveIdle(Character character) : base(character, "")
{
standStillTimer = Rand.Range(-10.0f, 10.0f);
@@ -109,7 +106,7 @@ namespace Barotrauma
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 would block all paths from the current hull to the target hull.
// 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, randomHull.SimPosition);
if (path.Unreachable ||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull)))
@@ -131,8 +128,8 @@ namespace Barotrauma
character.AIController.SelectTarget(currentTarget.AiTarget);
string errorMsg = null;
#if DEBUG
bool isRoomNameFound = currentTarget.DisplayName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
bool isRoomNameFound = currentTarget.RoomName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")";
#endif
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg);
PathSteering.SetPath(path);
@@ -233,9 +230,13 @@ namespace Barotrauma
}
}
private readonly List<Hull> targetHulls = new List<Hull>(20);
private readonly List<float> hullWeights = new List<float>(20);
private void FindTargetHulls()
{
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
targetHulls.Clear();
hullWeights.Clear();
foreach (var hull in Hull.hullList)
@@ -265,6 +266,7 @@ namespace Barotrauma
hullWeights.Add(weight);
}
}
}
private bool IsForbidden(Hull hull)
@@ -83,12 +83,12 @@ namespace Barotrauma
return;
}
}
if (repairTool == null)
{
FindRepairTool();
}
if (character.CurrentHull == Item.CurrentHull && character.CanInteractWith(Item))
if (character.CanInteractWith(Item))
{
if (repairTool == null)
{
FindRepairTool();
}
if (repairTool != null)
{
OperateRepairTool(deltaTime);
@@ -83,7 +83,7 @@ namespace Barotrauma
if (character.SelectedCharacter == null)
{
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName),
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.RoomName),
null, 1.0f,
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
}
@@ -3,6 +3,7 @@ using System.Linq;
namespace Barotrauma
{
// TODO: Ensure that this works well enough. Consider using AIObjectiveLoop class.
class AIObjectiveRescueAll : AIObjective
{
public override string DebugTag => "rescue all";
@@ -293,27 +293,6 @@ namespace Barotrauma
return;
}
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
//stop dragging if there's something between the pull limb and the target
Vector2 sourceSimPos = mouthLimb.SimPosition;
Vector2 targetSimPos = target.SimPosition;
if (character.Submarine != null && character.SelectedCharacter.Submarine == null)
{
targetSimPos -= character.Submarine.SimPosition;
}
else if (character.Submarine == null && character.SelectedCharacter.Submarine != null)
{
sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition;
}
var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
if (body != null)
{
character.DeselectCharacter();
return;
}
}
Character targetCharacter = target;
float eatSpeed = character.Mass / targetCharacter.Mass * 0.1f;
eatTimer += deltaTime * eatSpeed;
@@ -609,7 +588,6 @@ namespace Barotrauma
}
}
float prevWalkPos = WalkPos;
WalkPos -= MainLimb.LinearVelocity.X * (CurrentAnimationParams.CycleSpeed / RagdollParams.JointScale / 100.0f);
Vector2 transformedStepSize = Vector2.Zero;
@@ -645,11 +623,6 @@ namespace Barotrauma
bool playFootstepSound = false;
if (limb.type == LimbType.LeftFoot)
{
if (Math.Sign(Math.Sin(prevWalkPos)) > 0 && Math.Sign(transformedStepSize.Y) < 0)
{
playFootstepSound = true;
}
limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
limb.DebugTargetPos = footPos + new Vector2(
transformedStepSize.X + movement.X * 0.1f,
@@ -658,20 +631,13 @@ namespace Barotrauma
}
else if (limb.type == LimbType.RightFoot)
{
if (Math.Sign(Math.Sin(prevWalkPos)) < 0 && Math.Sign(transformedStepSize.Y) > 0)
{
playFootstepSound = true;
}
limb.DebugRefPos = footPos + Vector2.UnitX * movement.X * 0.1f;
limb.DebugTargetPos = footPos + new Vector2(
-transformedStepSize.X + movement.X * 0.1f,
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f);
limb.MoveToPos(limb.DebugTargetPos, FootMoveForce);
}
#if CLIENT
if (playFootstepSound) { PlayImpactSound(limb); }
#endif
if (CurrentGroundedParams.FootAnglesInRadians.ContainsKey(limb.limbParams.ID))
{
SmoothRotateWithoutWrapping(limb,
@@ -751,6 +717,22 @@ namespace Barotrauma
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
{
//make sure the angle "has the same number of revolutions" as the reference limb
//(e.g. we don't want to rotate the legs to 0 if the torso is at 360, because that'd blow up the hip joints)
while (referenceLimb.Rotation - angle > MathHelper.TwoPi)
{
angle += MathHelper.TwoPi;
}
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
}
public override void Flip()
{
base.Flip();
@@ -534,12 +534,7 @@ namespace Barotrauma
Limb leftLeg = GetLimb(LimbType.LeftLeg);
Limb rightLeg = GetLimb(LimbType.RightLeg);
float limpAmount =
character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) +
character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f);
float walkCycleMultiplier = 1.0f;
if (Stairs != null)
{
@@ -560,8 +555,7 @@ namespace Barotrauma
//TODO: take into account that the feet aren't necessarily in CurrentHull
//full slowdown (1.5f) when water is up to the torso
surfaceY = ConvertUnits.ToSimUnits(currentHull.Surface);
float bottomPos = Math.Max(colliderPos.Y, currentHull.Rect.Y - currentHull.Rect.Height);
slowdownAmount = MathHelper.Clamp((surfaceY - bottomPos) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
slowdownAmount = MathHelper.Clamp((surfaceY - colliderPos.Y) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f;
}
float maxSpeed = Math.Max(TargetMovement.Length() - slowdownAmount, 1.0f);
@@ -570,10 +564,11 @@ namespace Barotrauma
float walkPosX = (float)Math.Cos(WalkPos);
float walkPosY = (float)Math.Sin(WalkPos);
Vector2 stepSize = StepSize.Value;
stepSize.X *= walkPosX;
stepSize.Y *= walkPosY;
stepSize.Y *= walkPosY;
float footMid = colliderPos.X;
if (limpAmount > 0.0f)
@@ -594,7 +589,7 @@ namespace Barotrauma
movement.Y = 0.0f;
if (torso == null) { return; }
bool isNotRemote = true;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer;
@@ -698,7 +693,7 @@ namespace Barotrauma
//make the character limp if the feet are damaged
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true);
footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
footPos *= MathHelper.Lerp(1.0f, 0.5f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
if (onSlope && Stairs == null)
{
@@ -788,19 +783,13 @@ namespace Barotrauma
//get the upper arm to point downwards
var arm = GetLimb(armType);
if (Math.Abs(arm.body.AngularVelocity) < 10.0f)
{
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.1f, 0.1f), arm.Mass * 10.0f);
}
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.1f, 0.1f), arm.Mass * 10.0f);
//get the elbow to a neutral rotation
if (Math.Abs(hand.body.AngularVelocity) < 10.0f)
{
LimbJoint elbow =
LimbJoint elbow =
GetJointBetweenLimbs(armType, hand.type) ??
GetJointBetweenLimbs(armType, foreArmType);
hand.body.ApplyTorque(MathHelper.Clamp(-elbow.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 10.0f);
}
hand.body.ApplyTorque(-elbow.JointAngle * hand.Mass * 10.0f);
}
}
}
@@ -1138,12 +1127,12 @@ namespace Barotrauma
#if CLIENT
if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", leftFoot.WorldPosition, hullGuess: currentHull);
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: leftFoot.WorldPosition);
leftFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", rightFoot.WorldPosition, hullGuess: currentHull);
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: rightFoot.WorldPosition);
rightFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
#endif
@@ -1194,8 +1183,7 @@ namespace Barotrauma
isClimbing = false;
}
}
else if ((character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right)) &&
(!character.IsKeyDown(InputType.Up) && !character.IsKeyDown(InputType.Down)))
else if (character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right))
{
isClimbing = false;
}
@@ -84,7 +84,6 @@ namespace Barotrauma
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
//DebugConsole.NewMessage("[RagollParams] Using the default folder.");
folder = GetDefaultFolder(speciesName);
}
return folder;
@@ -674,6 +674,8 @@ namespace Barotrauma
velocity -= ((Submarine)f2.Body.UserData).Velocity;
}
if (character.Submarine == null && f2.Body.UserData is Submarine) velocity -= ((Submarine)f2.Body.UserData).Velocity;
float impact = Vector2.Dot(velocity, -normal);
if (f1.Body == Collider.FarseerBody || !Collider.Enabled)
{
@@ -692,11 +694,6 @@ namespace Barotrauma
character.AddDamage(impactPos, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate((impact - ImpactTolerance) * 10.0f) }, 0.0f, true);
strongestImpact = Math.Max(strongestImpact, impact - ImpactTolerance);
character.ApplyStatusEffects(ActionType.OnImpact, 1.0f);
//briefly disable impact damage
//otherwise the character will take damage multiple times when for example falling,
//because we use the velocity of the collider to determine the impact
//(i.e. the character would take damage until the collider hits the floor and stops)
character.DisableImpactDamageTimer = 0.25f;
}
}
}
@@ -704,7 +701,7 @@ namespace Barotrauma
ImpactProjSpecific(impact, f1.Body);
}
public void SeverLimbJoint(LimbJoint limbJoint, bool playSound = true)
public void SeverLimbJoint(LimbJoint limbJoint)
{
if (!limbJoint.CanBeSevered || limbJoint.IsSevered)
{
@@ -733,7 +730,7 @@ namespace Barotrauma
}
}
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound = true);
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint);
private void GetConnectedLimbs(List<Limb> connectedLimbs, List<LimbJoint> checkedJoints, Limb limb)
{
@@ -80,13 +80,13 @@ namespace Barotrauma
public HitDetection HitDetectionType { get; private set; }
[Serialize(AIBehaviorAfterAttack.FallBack, true), Editable(ToolTip = "The preferred AI behavior after the attack.")]
public AIBehaviorAfterAttack AfterAttack { get; set; }
public AIBehaviorAfterAttack AfterAttack { get; private set; }
[Serialize(false, true), Editable(ToolTip = "Should the ai try to reverse when aiming with this attack?")]
public bool Reverse { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target before the AI tries to attack.")]
public float Range { get; set; }
public float Range { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f, ToolTip = "Min distance from the attack limb to the target to do damage. In distance based hit detection, the hit will be registered as soon as the target is within the damage range, unless the attack duration has expired.")]
public float DamageRange { get; set; }
@@ -95,19 +95,19 @@ namespace Barotrauma
public float Duration { get; private set; }
[Serialize(5f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How long the AI waits between the attacks.")]
public float CoolDown { get; set; } = 5;
public float CoolDown { get; private set; } = 5;
[Serialize(0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "Used as the attack cooldown between different kind of attacks. Does not have effect, if set to 0.")]
public float SecondaryCoolDown { get; set; } = 0;
public float SecondaryCoolDown { get; private set; } = 0;
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 1, DecimalCount = 2, ToolTip = "Random factor applied to all cooldowns. Example: 0.1 -> adds a random value between -10% and 10% of the cooldown. Min 0 (default), Max 1 (could disable or double the cooldown in extreme cases).")]
public float CoolDownRandomFactor { get; private set; } = 0;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float StructureDamage { get; set; }
public float StructureDamage { get; private set; }
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float ItemDamage { get; set; }
public float ItemDamage { get; private set; }
/// <summary>
/// Legacy support. Use Afflictions.
@@ -283,7 +283,6 @@ namespace Barotrauma
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
continue;
}
}
else
@@ -293,7 +292,6 @@ namespace Barotrauma
if (afflictionPrefab == null)
{
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.");
continue;
}
}
@@ -426,7 +426,7 @@ namespace Barotrauma
}
}
public bool CanSpeak;
private bool canSpeak;
private bool speechImpedimentSet;
@@ -436,7 +436,7 @@ namespace Barotrauma
{
get
{
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
if (!canSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
return speechImpediment;
}
set
@@ -710,7 +710,7 @@ namespace Barotrauma
displayName = TextManager.Get($"Character.{Path.GetFileName(Path.GetDirectoryName(file))}", true);
IsHumanoid = doc.Root.GetAttributeBool("humanoid", false);
CanSpeak = doc.Root.GetAttributeBool("canspeak", false);
canSpeak = doc.Root.GetAttributeBool("canspeak", false);
needsAir = doc.Root.GetAttributeBool("needsair", false);
Noise = doc.Root.GetAttributeFloat("noise", 100f);
@@ -853,7 +853,7 @@ namespace Barotrauma
{
if (characterConfigFiles == null)
{
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character, searchAllContentPackages: true);
characterConfigFiles = GameMain.Instance.GetFilesOfType(ContentType.Character);
}
return characterConfigFiles;
}
@@ -1103,14 +1103,14 @@ namespace Barotrauma
if (leftFoot != null)
{
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true);
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
}
var rightFoot = AnimController.GetLimb(LimbType.RightFoot);
if (rightFoot != null)
{
float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
speed *= MathHelper.Lerp(1.0f, 0.4f, MathHelper.Clamp(footAfflictionStrength / 80.0f, 0.0f, 1.0f));
speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f));
}
return speed;
@@ -1428,6 +1428,26 @@ namespace Barotrauma
return (wall == null || !wall.CastShadow) && (door == null || door.IsOpen);
}
public bool CanSeeCharacter(Character character, Vector2 sourceWorldPos)
{
Vector2 diff = ConvertUnits.ToSimUnits(character.WorldPosition - sourceWorldPos);
Body closestBody = null;
if (character.Submarine == null)
{
closestBody = Submarine.CheckVisibility(sourceWorldPos, sourceWorldPos + diff);
if (closestBody == null) return true;
}
else
{
closestBody = Submarine.CheckVisibility(character.WorldPosition, character.WorldPosition - diff);
if (closestBody == null) return true;
}
Structure wall = closestBody.UserData as Structure;
return wall == null || !wall.CastShadow;
}
public bool HasEquippedItem(Item item)
{
for (int i = 0; i < Inventory.Capacity; i++)
@@ -1568,8 +1588,9 @@ namespace Barotrauma
}
}
}
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; }
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) return false;
Pickable pickableComponent = item.GetComponent<Pickable>();
if (pickableComponent != null && (pickableComponent.Picker != null && !pickableComponent.Picker.IsDead)) { return false; }
@@ -2023,10 +2044,6 @@ namespace Barotrauma
{
IsRagdolled = IsForceRagdolled;
}
else if (IsRemotePlayer)
{
IsRagdolled = IsKeyDown(InputType.Ragdoll);
}
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f))
{
@@ -2155,8 +2172,7 @@ namespace Barotrauma
public void Speak(string message, ChatMessageType? messageType = null, float delay = 0.0f, string identifier = "", float minDurationBetweenSimilar = 0.0f)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(message)) { return; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
//already sent a similar message a moment ago
if (!string.IsNullOrEmpty(identifier) && minDurationBetweenSimilar > 0.0f &&
@@ -2165,6 +2181,7 @@ namespace Barotrauma
{
return;
}
aiChatMessageQueue.Add(new AIChatMessage(message, messageType, identifier, delay));
}
@@ -2581,9 +2598,11 @@ namespace Barotrauma
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
#endif
CharacterList.Remove(this);
#if CLIENT
GameMain.GameSession?.CrewManager?.RemoveCharacter(this);
#endif
if (Controlled == this) { Controlled = null; }
CharacterList.Remove(this);
if (Inventory != null)
{
@@ -399,7 +399,7 @@ namespace Barotrauma
{
ID = idCounter;
idCounter++;
Name = element.GetAttributeString("name", "");
Name = element.GetAttributeString("name", "unnamed");
string genderStr = element.GetAttributeString("gender", "male").ToLowerInvariant();
File = element.GetAttributeString("file", "");
SourceElement = GetConfig(File).Root;
@@ -423,29 +423,6 @@ namespace Barotrauma
element.GetAttributeInt("beardindex", -1),
element.GetAttributeInt("moustacheindex", -1),
element.GetAttributeInt("faceattachmentindex", -1));
if (string.IsNullOrEmpty(Name))
{
if (SourceElement.Element("name") != null)
{
string firstNamePath = SourceElement.Element("name").GetAttributeString("firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
Name = ToolBox.GetRandomLine(firstNamePath);
}
string lastNamePath = SourceElement.Element("name").GetAttributeString("lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (Head.gender == Gender.Female) ? "female" : "male");
if (Name != "") Name += " ";
Name += ToolBox.GetRandomLine(lastNamePath);
}
}
}
StartItemsGiven = element.GetAttributeBool("startitemsgiven", false);
string personalityName = element.GetAttributeString("personality", "");
ragdollFileName = element.GetAttributeString("ragdoll", string.Empty);
@@ -729,8 +706,6 @@ namespace Barotrauma
partial void LoadAttachmentSprites();
// TODO: change the formula so that it's not linear and so that it takes into account the usefulness of the skill
// -> give a weight to each skill, because some are much more valuable than others?
private int CalculateSalary()
{
if (Name == null || Job == null) return 0;
@@ -231,8 +231,7 @@ namespace Barotrauma
: afflictions.Concat(limbHealths.SelectMany(lh => lh.Afflictions.Where(limbHealthFilter)));
}
private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex];
private LimbHealth GetMathingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb));
private LimbHealth GetMathingLimbHealth(Affliction affliction) => limbHealths[Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb).HealthIndex];
/// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
@@ -266,12 +265,6 @@ namespace Barotrauma
public Affliction GetAffliction(string afflictionType, Limb limb)
{
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
{
DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name +
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
return null;
}
foreach (Affliction affliction in limbHealths[limb.HealthIndex].Afflictions)
{
if (affliction.Prefab.AfflictionType == afflictionType) return affliction;
@@ -473,13 +466,7 @@ namespace Barotrauma
private void AddLimbAffliction(Limb limb, Affliction newAffliction)
{
if (!newAffliction.Prefab.LimbSpecific || limb == null) return;
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
{
DebugConsole.ThrowError("Limb health index out of bounds. Character\"" + Character.Name +
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
return;
}
if (!newAffliction.Prefab.LimbSpecific) return;
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction);
}
@@ -624,12 +611,6 @@ namespace Barotrauma
partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime);
public void SetVitality(float newVitality)
{
maxVitality = newVitality;
CalculateVitality();
}
public void CalculateVitality()
{
Vitality = MaxVitality;
@@ -1,17 +1,32 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
public class AutomaticOrder
{
public string aiTag;
public string option;
public float priority;
public AutomaticOrder(XElement element)
{
aiTag = element.GetAttributeString("aitag", null);
option = element.GetAttributeString("option", null);
priority = element.GetAttributeFloat("priority", 0);
}
}
partial class JobPrefab
{
public static List<JobPrefab> List;
public readonly XElement Items;
public readonly List<string> ItemNames;
public List<SkillPrefab> Skills;
public readonly XElement Items;
public readonly List<string> ItemNames = new List<string>();
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
public readonly List<AutomaticOrder> AutomaticOrders = new List<AutomaticOrder>();
[Serialize("1,1,1,1", false)]
public Color UIColor
@@ -111,10 +126,6 @@ namespace Barotrauma
Name = TextManager.Get("JobName." + Identifier);
Description = TextManager.Get("JobDescription." + Identifier);
ItemNames = new List<string>();
Skills = new List<SkillPrefab>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -155,7 +166,10 @@ namespace Barotrauma
foreach (XElement skillElement in subElement.Elements())
{
Skills.Add(new SkillPrefab(skillElement));
}
}
break;
case "automaticorders":
subElement.Elements().ForEach(order => AutomaticOrders.Add(new AutomaticOrder(order)));
break;
}
}
@@ -94,7 +94,7 @@ namespace Barotrauma
}
}
partial class Limb : ISerializableEntity, ISpatialEntity
partial class Limb : ISerializableEntity
{
// Note: not used
private const float LimbDensity = 15;
@@ -155,8 +155,6 @@ namespace Barotrauma
}
}
public Submarine Submarine => character.Submarine;
public Vector2 WorldPosition
{
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }