Unstable 0.16.3.0
This commit is contained in:
@@ -207,8 +207,10 @@ namespace Barotrauma
|
||||
} = new HashSet<Submarine>();
|
||||
|
||||
public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget);
|
||||
public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack);
|
||||
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter);
|
||||
public static bool IsTargetBeingChasedBy(Character target, Character character)
|
||||
=> character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && (enemyAI.State == AIState.Attack || enemyAI.State == AIState.Aggressive);
|
||||
public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c);
|
||||
private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character);
|
||||
|
||||
private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
|
||||
|
||||
@@ -1322,7 +1324,7 @@ namespace Barotrauma
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null &&
|
||||
(!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
|
||||
((!AIParams.TargetOuterWalls || !canAttackWalls) && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
|
||||
{
|
||||
// Target is unreachable, there's a door or wall ahead
|
||||
State = AIState.Idle;
|
||||
@@ -2385,6 +2387,24 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#region Targeting
|
||||
public static bool IsLatchedTo(Character target, Character character)
|
||||
{
|
||||
if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null)
|
||||
{
|
||||
return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter == character;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsLatchedToSomeoneElse(Character target, Character character)
|
||||
{
|
||||
if (target.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null)
|
||||
{
|
||||
return enemyAI.LatchOntoAI.IsAttached && enemyAI.LatchOntoAI.TargetCharacter != null && enemyAI.LatchOntoAI.TargetCharacter != character;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
|
||||
|
||||
//goes through all the AItargets, evaluates how preferable it is to attack the target,
|
||||
@@ -2398,6 +2418,7 @@ namespace Barotrauma
|
||||
targetingParams = null;
|
||||
bool isAnyTargetClose = false;
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float maxModifier = 5;
|
||||
foreach (AITarget aiTarget in AITarget.List)
|
||||
{
|
||||
if (aiTarget.InDetectable) { continue; }
|
||||
@@ -2537,12 +2558,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0;
|
||||
valueModifier *= leadsInside ? (IsAggressiveBoarder ? maxModifier : 1) : 0;
|
||||
}
|
||||
else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
|
||||
else if (IsAggressiveBoarder && leadsInside && canAttackWalls)
|
||||
{
|
||||
// Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open * 0.25f;
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2580,6 +2601,7 @@ namespace Barotrauma
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
// (Terminalcells)
|
||||
valueModifier *= 1 - section.gap.Open * 0.25f;
|
||||
valueModifier = Math.Max(valueModifier, 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2599,6 +2621,7 @@ namespace Barotrauma
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
valueModifier = Math.Clamp(valueModifier, 0, maxModifier);
|
||||
}
|
||||
}
|
||||
if (door != null)
|
||||
@@ -2610,7 +2633,7 @@ namespace Barotrauma
|
||||
bool isOpen = door.CanBeTraversed;
|
||||
if (!isOpen)
|
||||
{
|
||||
if (!canAttackDoors || isOutdoor && !AIParams.TargetOuterWalls) { continue; }
|
||||
if (!canAttackDoors) { continue; }
|
||||
}
|
||||
else if (!Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
@@ -2624,11 +2647,11 @@ namespace Barotrauma
|
||||
// Increase the priority if the character is outside and the door is from outside to inside
|
||||
if (door.CanBeTraversed)
|
||||
{
|
||||
valueModifier = 3;
|
||||
valueModifier = maxModifier;
|
||||
}
|
||||
else if (door.LinkedGap != null)
|
||||
{
|
||||
valueModifier = 1 + door.LinkedGap.Open;
|
||||
valueModifier = 1 + door.LinkedGap.Open * (maxModifier - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2727,6 +2750,37 @@ namespace Barotrauma
|
||||
|
||||
if (SelectedAiTarget == aiTarget)
|
||||
{
|
||||
if (Character.Submarine == null && aiTarget.Entity is ISpatialEntity spatialEntity && spatialEntity.Submarine != null)
|
||||
{
|
||||
if (targetingTag == "door" || targetingTag == "wall")
|
||||
{
|
||||
Vector2 rayStart = Character.SimPosition;
|
||||
Vector2 rayEnd = aiTarget.SimPosition + spatialEntity.Submarine.SimPosition;
|
||||
Body closestBody = Submarine.PickBody(rayStart, rayEnd, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel, allowInsideFixture: true);
|
||||
if (closestBody != null && closestBody.UserData is ISpatialEntity hit)
|
||||
{
|
||||
Vector2 hitPos = hit.SimPosition;
|
||||
if (closestBody.UserData is Submarine)
|
||||
{
|
||||
hitPos = Submarine.LastPickedPosition;
|
||||
}
|
||||
else if (hit.Submarine != null)
|
||||
{
|
||||
hitPos += hit.Submarine.SimPosition;
|
||||
}
|
||||
float subHalfWidth = spatialEntity.Submarine.Borders.Width / 2;
|
||||
float subHalfHeight = spatialEntity.Submarine.Borders.Height / 2;
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(rayEnd - hitPos);
|
||||
bool isOtherSideOfTheSub = Math.Abs(diff.X) > subHalfWidth || Math.Abs(diff.Y) > subHalfHeight;
|
||||
if (isOtherSideOfTheSub)
|
||||
{
|
||||
IgnoreTarget(aiTarget);
|
||||
ResetAITarget();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stick to the current target
|
||||
valueModifier *= 1.1f;
|
||||
}
|
||||
@@ -2757,19 +2811,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (targetParams.AttackPattern == AttackPattern.Circle)
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && targetCharacter == null)
|
||||
{
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose)
|
||||
if (targetParams.AttackPattern == AttackPattern.Circle || targetParams.AttackPattern == AttackPattern.Sweep)
|
||||
{
|
||||
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
|
||||
if (!isAnyTargetClose)
|
||||
{
|
||||
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
|
||||
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else
|
||||
{
|
||||
dist *= 5;
|
||||
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
|
||||
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else if (targetParams.AttackPattern == AttackPattern.Circle)
|
||||
{
|
||||
dist *= 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2936,9 +2993,9 @@ namespace Barotrauma
|
||||
if (HasValidPath(requireNonDirty: true)) { return; }
|
||||
wallHits.Clear();
|
||||
Structure wall = null;
|
||||
Vector2 rayStart = AttackingLimb != null ? AttackingLimb.SimPosition : SimPosition;
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
@@ -2952,7 +3009,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5);
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
@@ -2968,7 +3024,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = rayStart + Steering * 5;
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
@@ -3021,6 +3076,7 @@ namespace Barotrauma
|
||||
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces.
|
||||
if (!isTargetingDoor)
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
@@ -3032,6 +3088,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Blocked by a disabled wall.
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
@@ -3082,8 +3139,17 @@ namespace Barotrauma
|
||||
if (!(hit.UserData is Structure w)) { return false; }
|
||||
if (w.Submarine == null) { return false; }
|
||||
if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; }
|
||||
if (Character.Submarine == null && w.prefab.Tags.Contains("inner")) { return false; }
|
||||
if (!AIParams.TargetOuterWalls && !w.prefab.Tags.Contains("inner")) { return false; }
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
if (w.prefab.Tags.Contains("inner"))
|
||||
{
|
||||
if (!Character.AnimController.CanEnterSubmarine) { return false; }
|
||||
}
|
||||
else if (!AIParams.TargetOuterWalls)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
wall = w;
|
||||
return true;
|
||||
}
|
||||
@@ -3120,7 +3186,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (door.LinkedGap.Size > ConvertUnits.ToDisplayUnits(colliderWidth))
|
||||
{
|
||||
return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: 100);
|
||||
float maxDistance = Math.Max(ConvertUnits.ToDisplayUnits(colliderLength), 100);
|
||||
return SteerThroughGap(door.LinkedGap, door.LinkedGap.FlowTargetHull.WorldPosition, deltaTime, maxDistance: maxDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3584,12 +3651,12 @@ namespace Barotrauma
|
||||
|
||||
public override bool SteerThroughGap(Gap gap, Vector2 targetWorldPos, float deltaTime, float maxDistance = -1)
|
||||
{
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
bool success = base.SteerThroughGap(gap, targetWorldPos, deltaTime, maxDistance);
|
||||
if (success)
|
||||
{
|
||||
wallTarget = null;
|
||||
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
|
||||
}
|
||||
IsSteeringThroughGap = success;
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Barotrauma
|
||||
private float reactTimer;
|
||||
private float unreachableClearTimer;
|
||||
private bool shouldCrouch;
|
||||
public bool IsInsideCave { get; private set; }
|
||||
/// <summary>
|
||||
/// Resets each frame
|
||||
/// </summary>
|
||||
@@ -58,14 +57,14 @@ namespace Barotrauma
|
||||
private float obstacleRaycastTimer;
|
||||
|
||||
private readonly float enemyCheckInterval = 0.2f;
|
||||
private readonly float enemySpotDistanceOutside = 1500;
|
||||
private readonly float enemySpotDistanceOutside = 800;
|
||||
private readonly float enemySpotDistanceInside = 1000;
|
||||
private float enemycheckTimer;
|
||||
|
||||
/// <summary>
|
||||
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity.
|
||||
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders).
|
||||
/// </summary>
|
||||
public float ReportRange { get; set; } = float.PositiveInfinity;
|
||||
public float ReportRange { get; set; }
|
||||
|
||||
private float _aimSpeed = 1;
|
||||
public float AimSpeed
|
||||
@@ -167,6 +166,7 @@ namespace Barotrauma
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
reactTimer = GetReactionTime();
|
||||
SortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
ReportRange = Character.IsOnPlayerTeam ? float.PositiveInfinity : 1000;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -306,7 +306,7 @@ namespace Barotrauma
|
||||
UseIndoorSteeringOutside = false;
|
||||
}
|
||||
|
||||
if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted)
|
||||
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID))
|
||||
{
|
||||
// Spot enemies while staying outside or inside an enemy ship.
|
||||
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
|
||||
@@ -327,9 +327,13 @@ namespace Barotrauma
|
||||
float dist = toTarget.LengthSquared();
|
||||
float maxDistance = Character.Submarine == null ? enemySpotDistanceOutside : enemySpotDistanceInside;
|
||||
if (dist > maxDistance * maxDistance) { continue; }
|
||||
Vector2 forward = VectorExtensions.Forward(Character.AnimController.Collider.Rotation);
|
||||
forward.X *= Character.AnimController.Dir;
|
||||
if (Vector2.Dot(toTarget, forward) < 0.2f) { continue; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(c, Character)) { continue; }
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (head == null) { continue; }
|
||||
float rotation = head.body.TransformedRotation;
|
||||
Vector2 forward = VectorExtensions.Forward(rotation);
|
||||
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(toTarget, forward));
|
||||
if (angle > 70) { continue; }
|
||||
if (!Character.CanSeeCharacter(c)) { continue; }
|
||||
if (dist < closestDistance || closestEnemy == null)
|
||||
{
|
||||
@@ -344,8 +348,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsInsideCave = Character.CurrentHull == null && Level.Loaded?.Caves.FirstOrDefault(c => c.Area.Contains(Character.WorldPosition)) is Level.Cave;
|
||||
|
||||
if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer))
|
||||
{
|
||||
@@ -1242,7 +1244,7 @@ namespace Barotrauma
|
||||
{
|
||||
//if the other character did not witness the attack, and the character is not within report range (or capable of reporting)
|
||||
//don't react to the attack
|
||||
if (Character.IsDead || Character.IsUnconscious || !CheckReportRange(Character, otherCharacter, ReportRange))
|
||||
if (Character.IsDead || Character.IsUnconscious || otherCharacter.TeamID != Character.TeamID || !CheckReportRange(Character, otherCharacter, ReportRange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1259,8 +1261,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
// Outside -> don't react.
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
// Outside
|
||||
return attacker.Submarine == null ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
|
||||
{
|
||||
@@ -1852,7 +1854,7 @@ namespace Barotrauma
|
||||
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = HasDivingSuit(character);
|
||||
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.GetActiveObjectives<AIObjectiveFightIntruders>().Any();
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
if (isCurrentHull)
|
||||
{
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ namespace Barotrauma
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character, checkInventory: true)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
|
||||
if (target.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
// Don't repair items in rooms that have enemies inside.
|
||||
// Don't clean up items in rooms that have enemies inside.
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
+48
-10
@@ -117,7 +117,10 @@ namespace Barotrauma
|
||||
private float AimSpeed => HumanAIController.AimSpeed;
|
||||
private float AimAccuracy => HumanAIController.AimAccuracy;
|
||||
|
||||
private bool EnemyIsClose() => Enemy != null && Enemy.CurrentHull != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < 300;
|
||||
private bool IsEnemyCloserThan(float margin) =>
|
||||
Enemy != null && Enemy.CurrentHull != null &&
|
||||
character.InWater && Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition) < margin * margin ||
|
||||
HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < margin;
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -144,12 +147,19 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
spreadTimer = Rand.Range(-10f, 10f);
|
||||
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
HumanAIController.SortTimer = 0;
|
||||
}
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && Enemy != null)
|
||||
if (Enemy == null)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
|
||||
{
|
||||
@@ -160,6 +170,13 @@ namespace Barotrauma
|
||||
}
|
||||
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
|
||||
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
|
||||
if (Priority > 0)
|
||||
{
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
@@ -366,7 +383,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest;
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
|
||||
if (!isAllowedToSeekWeapons)
|
||||
{
|
||||
if (WeaponComponent == null)
|
||||
@@ -418,9 +435,16 @@ namespace Barotrauma
|
||||
onCompleted: () => RemoveSubObjective(ref seekWeaponObjective),
|
||||
onAbandon: () =>
|
||||
{
|
||||
SpeakNoWeapons();
|
||||
RemoveSubObjective(ref seekWeaponObjective);
|
||||
Mode = CombatMode.Retreat;
|
||||
if (Weapon == null)
|
||||
{
|
||||
SpeakNoWeapons();
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
else
|
||||
{
|
||||
Mode = CombatMode.Defensive;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -478,13 +502,25 @@ namespace Barotrauma
|
||||
weaponComponent = null;
|
||||
float bestPriority = 0;
|
||||
float lethalDmg = -1;
|
||||
bool enemyIsClose = EnemyIsClose();
|
||||
bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300);
|
||||
bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character);
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (prioritizeMelee)
|
||||
{
|
||||
if (weapon is MeleeWeapon)
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (!weapon.IsLoaded(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && enemyIsClose)
|
||||
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
||||
{
|
||||
// Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo).
|
||||
continue;
|
||||
@@ -693,7 +729,7 @@ namespace Barotrauma
|
||||
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1014,7 +1050,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
return;
|
||||
}
|
||||
if (Weapon.RequireAimToUse)
|
||||
@@ -1074,7 +1110,7 @@ namespace Barotrauma
|
||||
else if (!character.IsFacing(Enemy.WorldPosition))
|
||||
{
|
||||
// Don't do the facing check if we are close to the target, because it easily causes the character to get stuck here when it flips around.
|
||||
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
|
||||
SetAimTimer(Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1190,5 +1226,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAimTimer(float newTimer) => aimTimer = Math.Max(aimTimer, newTimer);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CanInteractWith(container.Item, checkLinked: false))
|
||||
{
|
||||
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(item)))
|
||||
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(ItemToContain)))
|
||||
{
|
||||
HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax);
|
||||
}
|
||||
|
||||
+1
@@ -70,6 +70,7 @@ namespace Barotrauma
|
||||
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
|
||||
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ namespace Barotrauma
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
ConditionLevel = MIN_OXYGEN,
|
||||
RemoveExisting = true
|
||||
RemoveExistingWhenNecessary = true
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
|
||||
+4
@@ -76,6 +76,10 @@ namespace Barotrauma
|
||||
// -> ignore find safety unless we need to find a diving gear
|
||||
Priority = 0;
|
||||
}
|
||||
else if (objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
|
||||
+5
@@ -401,6 +401,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!ownerItem.IsInteractable(character)) { continue; }
|
||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
||||
if (ownerItem != item.Container)
|
||||
{
|
||||
itemPriority *= 0.1f;
|
||||
}
|
||||
}
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace Barotrauma
|
||||
// TODO: Currently we never check the visibility (to the end node), which is actually unintentional.
|
||||
// I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better.
|
||||
// However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true.
|
||||
|
||||
// NOTE: This seemes to have caused an issue now Regalis11/Barotrauma#8067: namely, the bot was trying to use a waypoint that was obstructed by a shuttle
|
||||
// because obstruction was only checked when checking visibility in PathFinder. Changed that so that obstructed nodes are no longer used.
|
||||
public bool CheckVisibility { get; set; }
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
|
||||
+27
-79
@@ -7,11 +7,11 @@ namespace Barotrauma
|
||||
class AIObjectiveReturn : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "return";
|
||||
private AIObjectiveGoTo moveInsideObjective, moveInCaveObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior;
|
||||
private bool isSteeringThroughGap;
|
||||
public Submarine ReturnTarget { get; }
|
||||
|
||||
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior, isSteeringThroughGap;
|
||||
|
||||
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
@@ -112,7 +112,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
TryAddSubObjective(ref moveInsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
|
||||
@@ -137,91 +136,41 @@ namespace Barotrauma
|
||||
IsCompleted = true;
|
||||
}
|
||||
}
|
||||
else if (!isSteeringThroughGap && moveInCaveObjective == null && moveOutsideObjective == null)
|
||||
else if (!isSteeringThroughGap && moveOutsideObjective == null)
|
||||
{
|
||||
if (HumanAIController.IsInsideCave)
|
||||
Hull targetHull = null;
|
||||
float targetDistanceSquared = float.MaxValue;
|
||||
bool targetIsAirlock = false;
|
||||
foreach (var hull in ReturnTarget.GetHulls(false))
|
||||
{
|
||||
WayPoint closestOutsideWaypoint = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
foreach (var w in WayPoint.WayPointList)
|
||||
bool hullIsAirlock = hull.IsTaggedAirlock();
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
{
|
||||
if (w.Tunnel != null && w.Tunnel.Type == Level.TunnelType.Cave) { continue; }
|
||||
if (w.linkedTo.None(l => l is WayPoint linkedWaypoint && linkedWaypoint.Tunnel?.Type == Level.TunnelType.Cave)) { continue; }
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, w.WorldPosition);
|
||||
if (closestOutsideWaypoint == null || distance < closestDistance)
|
||||
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
if (targetHull == null || distanceSquared < targetDistanceSquared)
|
||||
{
|
||||
closestOutsideWaypoint = w;
|
||||
closestDistance = distance;
|
||||
targetHull = hull;
|
||||
targetDistanceSquared = distanceSquared;
|
||||
targetIsAirlock = hullIsAirlock;
|
||||
}
|
||||
}
|
||||
if (closestOutsideWaypoint != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
TryAddSubObjective(ref moveInCaveObjective,
|
||||
constructor: () => new AIObjectiveGoTo(closestOutsideWaypoint, character, objectiveManager)
|
||||
{
|
||||
endNodeFilter = n => n.Waypoint == closestOutsideWaypoint,
|
||||
AllowGoingOutside = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref moveInCaveObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable main or side path node target found for 'moveOutsideObjective'");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
TryAddSubObjective(ref moveOutsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Hull targetHull = null;
|
||||
float targetDistanceSquared = float.MaxValue;
|
||||
bool targetIsAirlock = false;
|
||||
foreach (var hull in ReturnTarget.GetHulls(false))
|
||||
{
|
||||
bool hullIsAirlock = hull.IsTaggedAirlock();
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
{
|
||||
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
if (targetHull == null || distanceSquared < targetDistanceSquared)
|
||||
{
|
||||
targetHull = hull;
|
||||
targetDistanceSquared = distanceSquared;
|
||||
targetIsAirlock = hullIsAirlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetHull != null)
|
||||
{
|
||||
RemoveSubObjective(ref moveInsideObjective);
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
TryAddSubObjective(ref moveOutsideObjective,
|
||||
constructor: () => new AIObjectiveGoTo(targetHull, character, objectiveManager)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref moveOutsideObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'");
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable target for 'moveOutsideObjective'");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HumanAIController.IsInsideCave)
|
||||
{
|
||||
RemoveSubObjective(ref moveOutsideObjective);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveSubObjective(ref moveInCaveObjective);
|
||||
}
|
||||
}
|
||||
usingEscapeBehavior = shouldUseEscapeBehavior;
|
||||
@@ -249,7 +198,6 @@ namespace Barotrauma
|
||||
{
|
||||
base.Reset();
|
||||
moveInsideObjective = null;
|
||||
moveInCaveObjective = null;
|
||||
moveOutsideObjective = null;
|
||||
usingEscapeBehavior = false;
|
||||
isSteeringThroughGap = false;
|
||||
|
||||
@@ -333,7 +333,6 @@ namespace Barotrauma
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (checkVisibility && isCharacter)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
var body = Submarine.PickBody(rayStart, node.TempPosition,
|
||||
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
|
||||
if (body != null)
|
||||
@@ -350,6 +349,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(node)) { return false; }
|
||||
if (startNodeFilter != null && !startNodeFilter(node)) { return false; }
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
// Always check the visibility for the start node
|
||||
if (!IsWaypointVisible(node, start)) { return false; }
|
||||
if (node.IsBlocked()) { return false; }
|
||||
@@ -364,6 +364,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (nodeFilter != null && !nodeFilter(node)) { return false; }
|
||||
if (endNodeFilter != null && !endNodeFilter(node)) { return false; }
|
||||
if (node.Waypoint.isObstructed) { return false; }
|
||||
// Only check the visibility for the end node when allowed (fix leaks)
|
||||
if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; }
|
||||
if (node.IsBlocked()) { return false; }
|
||||
|
||||
@@ -776,8 +776,8 @@ namespace Barotrauma
|
||||
if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
|
||||
limbDiff = Vector2.Normalize(limbDiff);
|
||||
float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
|
||||
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * Math.Min(mass, limbJoint.BodyA.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * Math.Min(mass, limbJoint.BodyB.Mass * 500), (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
|
||||
connectedLimbs.Clear();
|
||||
checkedJoints.Clear();
|
||||
|
||||
@@ -461,7 +461,7 @@ namespace Barotrauma
|
||||
ReloadAfflictions(element);
|
||||
}
|
||||
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
|
||||
public AttackResult DoDamage(Character attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
|
||||
{
|
||||
Character targetCharacter = target as Character;
|
||||
if (OnlyHumans)
|
||||
@@ -486,10 +486,10 @@ namespace Barotrauma
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.sourceBody = sourceBody;
|
||||
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker, worldPosition);
|
||||
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
|
||||
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity, worldPosition);
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
@@ -526,7 +526,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null)
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true, PhysicsBody sourceBody = null, Limb sourceLimb = null)
|
||||
{
|
||||
if (targetLimb == null)
|
||||
{
|
||||
@@ -553,7 +553,7 @@ namespace Barotrauma
|
||||
effect.sourceBody = sourceBody;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
|
||||
@@ -503,7 +503,7 @@ namespace Barotrauma
|
||||
get { return cursorPosition; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
cursorPosition = value;
|
||||
}
|
||||
}
|
||||
@@ -853,7 +853,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC;
|
||||
return IsKnockedDown || LockHands || IsBot && IsOnPlayerTeam;
|
||||
}
|
||||
}
|
||||
set { canInventoryBeAccessed = value; }
|
||||
@@ -3596,8 +3596,12 @@ namespace Barotrauma
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
if (!joint.CanBeSevered) { continue; }
|
||||
// Limb A is where we usually create the joints from. Let's not allow severing when the "parent" limb is hit, or the head can pop off when we hit the torso, for example.
|
||||
if (joint.LimbB != targetLimb) { continue; }
|
||||
// Limb A is where we start creating the joint and LimbB is where the joint ends.
|
||||
// Normally the joints have been created starting from the body, in which case we'd want to use LimbB e.g. to severe a hand when it's hit.
|
||||
// But heads are a different case, because many characters have been created so that the head is first and then comes the rest of the body.
|
||||
// If this is the case, we'll have to use LimbA to decapitate the creature when it's hit on the head. Otherwise decapitation could happen only when we hit the body, not the head.
|
||||
var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB;
|
||||
if (referenceLimb != targetLimb) { continue; }
|
||||
float probability = severLimbsProbability;
|
||||
if (!IsDead)
|
||||
{
|
||||
|
||||
@@ -1134,6 +1134,10 @@ namespace Barotrauma
|
||||
{
|
||||
head.HairWithHatElement = hairs[hairWithHatIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
head.HairWithHatElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsValidIndex(Head.BeardIndex, beards))
|
||||
|
||||
+1
-1
@@ -246,7 +246,7 @@ namespace Barotrauma
|
||||
if (huskPrefab.ControlHusk)
|
||||
{
|
||||
#if SERVER
|
||||
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.CharacterInfo.Character == character);
|
||||
var client = GameMain.Server?.ConnectedClients.FirstOrDefault(c => c.Character == character);
|
||||
if (client != null)
|
||||
{
|
||||
GameMain.Server.SetClientCharacter(client, husk);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
|
||||
public void IncreaseSkill(float value, bool increasePastMax)
|
||||
{
|
||||
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumOlympianSkill : MaximumSkill);
|
||||
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumSkillWithTalents : MaximumSkill);
|
||||
}
|
||||
|
||||
private Sprite icon;
|
||||
|
||||
@@ -556,6 +556,7 @@ namespace Barotrauma
|
||||
// TODO: We might need this or solve the cases where a limb is severed while holding on to an item
|
||||
//if (character.Params.CanInteract) { return false; }
|
||||
if (this == character.AnimController.MainLimb) { return false; }
|
||||
bool canBeSevered = Params.CanBeSeveredAlive;
|
||||
if (character.AnimController.CanWalk)
|
||||
{
|
||||
switch (type)
|
||||
@@ -571,7 +572,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return canBeSevered;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,7 +1071,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
if (damageTarget is Character targetCharacter && targetLimb != null)
|
||||
{
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body);
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound, body, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1080,7 +1081,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body);
|
||||
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound, body, this);
|
||||
}
|
||||
}
|
||||
/*if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
|
||||
|
||||
@@ -648,6 +648,9 @@ namespace Barotrauma
|
||||
[Serialize(1f, true, description:"How much damage must be done by the attack in order to be able to cut off the limb. Note that it's evaluated after the damage modifiers."), Editable(DecimalCount = 0, MinValueFloat = 0, MaxValueFloat = 1000)]
|
||||
public float MinSeveranceDamage { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "Disable if you don't want to allow severing this joint while the creature is alive. Note: Does nothing if the 'Severance Probability Modifier' in the joint settings is 0 (default). Also note that the setting doesn't override certain limitations, e.g. severing the main limb, or legs of a walking creature is not allowed."), Editable]
|
||||
public bool CanBeSeveredAlive { get; set; }
|
||||
|
||||
//how long it takes for severed limbs to fade out
|
||||
[Serialize(10f, true, "How long it takes for the severed limb to fade out"), Editable(MinValueFloat = 0, MaxValueFloat = 100, ValueStep = 1)]
|
||||
public float SeveredFadeOutTime { get; set; } = 10.0f;
|
||||
|
||||
@@ -96,8 +96,8 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(500.0f, true)]
|
||||
public float MaximumOlympianSkill
|
||||
[Serialize(200.0f, true)]
|
||||
public float MaximumSkillWithTalents
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
||||
+23
-2
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
@@ -7,9 +8,26 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
private readonly List<TargetType> targetTypes;
|
||||
|
||||
private List<PropertyConditional> conditionals = new List<PropertyConditional>();
|
||||
|
||||
public AbilityConditionCharacter(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
targetTypes = ParseTargetTypes(conditionElement.GetAttributeStringArray("targettypes", new string[0], convertToLowerInvariant: true));
|
||||
|
||||
foreach (XElement subElement in conditionElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
conditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific(AbilityObject abilityObject)
|
||||
@@ -18,7 +36,10 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (!(abilityCharacter.Character is Character character)) { return false; }
|
||||
if (!IsViableTarget(targetTypes, character)) { return false; }
|
||||
|
||||
foreach (var conditional in conditionals)
|
||||
{
|
||||
if (!conditional.Matches(character)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -978,7 +978,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
NewMessage("Level seed: " + Level.Loaded.Seed);
|
||||
NewMessage("Level size: " + Level.Loaded.Size.X+"x"+ Level.Loaded.Size.Y);
|
||||
NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
|
||||
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none") + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none"));
|
||||
NewMessage("Mirrored: " + Level.Loaded.Mirrored);
|
||||
NewMessage("Level size: " + Level.Loaded.Size.X + "x" + Level.Loaded.Size.Y);
|
||||
NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown"));
|
||||
}
|
||||
},null));
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Barotrauma
|
||||
|
||||
public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
private IEnumerable<Character> affectedNpcs;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character);
|
||||
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController)) { continue; }
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
gotoObjective.Abandon = true;
|
||||
|
||||
@@ -796,8 +796,9 @@ namespace Barotrauma
|
||||
monsterStrength += enemyAI.CombatStrength;
|
||||
}
|
||||
|
||||
if (character.CurrentHull?.Submarine != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
|
||||
if (character.CurrentHull?.Submarine?.Info != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)) &&
|
||||
character.CurrentHull.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
// Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42
|
||||
enemyDanger += enemyAI.CombatStrength / 500.0f;
|
||||
|
||||
@@ -50,7 +50,8 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
// Disabled for now, because they make balancing the missions a pain.
|
||||
int multiplier = 1;//CalculateScalingEscortedCharacterCount();
|
||||
calculatedReward = Prefab.Reward * multiplier;
|
||||
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(missionSub))}‖end‖";
|
||||
@@ -319,31 +320,33 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character character in characters)
|
||||
if (!IsClient)
|
||||
{
|
||||
if (character.Inventory == null) { continue; }
|
||||
foreach (Item item in character.Inventory.AllItemsMod)
|
||||
foreach (Character character in characters)
|
||||
{
|
||||
//item didn't spawn with the characters -> drop it
|
||||
if (!characterItems.Any(c => c.Value.Contains(item)))
|
||||
if (character.Inventory == null) { continue; }
|
||||
foreach (Item item in character.Inventory.AllItemsMod)
|
||||
{
|
||||
item.Drop(character);
|
||||
//item didn't spawn with the characters -> drop it
|
||||
if (!characterItems.Any(c => c.Value.Contains(item)))
|
||||
{
|
||||
item.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// characters that survived will take their items with them, in case players tried to be crafty and steal them
|
||||
// this needs to run here in case players abort the mission by going back home
|
||||
// TODO: I think this might feel like a bug.
|
||||
foreach (var characterItem in characterItems)
|
||||
{
|
||||
if (Survived(characterItem.Key) || !completed)
|
||||
// characters that survived will take their items with them, in case players tried to be crafty and steal them
|
||||
// this needs to run here in case players abort the mission by going back home
|
||||
foreach (var characterItem in characterItems)
|
||||
{
|
||||
foreach (Item item in characterItem.Value)
|
||||
if (Survived(characterItem.Key) || !completed)
|
||||
{
|
||||
if (!item.Removed)
|
||||
foreach (Item item in characterItem.Value)
|
||||
{
|
||||
item.Remove();
|
||||
if (!item.Removed)
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Barotrauma
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
#endif
|
||||
ShowMessage(State);
|
||||
OnMissionStateChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +146,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
|
||||
|
||||
|
||||
public Action<Mission> OnMissionStateChanged;
|
||||
|
||||
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(locations.Length == 2);
|
||||
|
||||
@@ -96,9 +96,9 @@ namespace Barotrauma
|
||||
public readonly bool RequireWreck;
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received when travelling from Pair.First to Pair.Second
|
||||
/// The mission can only be received when travelling from a location of the first type to a location of the second type
|
||||
/// </summary>
|
||||
public readonly List<Pair<string, string>> AllowedConnectionTypes;
|
||||
public readonly List<(string from, string to)> AllowedConnectionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received in these location types
|
||||
@@ -185,7 +185,14 @@ namespace Barotrauma
|
||||
|
||||
tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
|
||||
|
||||
Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", "");
|
||||
Name = TextManager.Get("MissionName." + TextIdentifier, true);
|
||||
if (Name == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Error in mission \"{Identifier}\" - could not find a name in localization files. Make sure the texts are present in the loca file or that the mission is set to share texts with another mission using the TextIdentifier attribute.");
|
||||
#endif
|
||||
Name = element.GetAttributeString("name", "");
|
||||
}
|
||||
Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", "");
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
AllowRetry = element.GetAttributeBool("allowretry", false);
|
||||
@@ -209,10 +216,20 @@ namespace Barotrauma
|
||||
FailureMessage = element.GetAttributeString("failuremessage", "");
|
||||
}
|
||||
|
||||
SonarLabel =
|
||||
TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ??
|
||||
TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ??
|
||||
element.GetAttributeString("sonarlabel", "");
|
||||
if (element.Attribute("sonarlabel") == null)
|
||||
{
|
||||
SonarLabel =
|
||||
TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ??
|
||||
TextManager.Get("missionsonarlabel.target");
|
||||
}
|
||||
else
|
||||
{
|
||||
SonarLabel =
|
||||
TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ??
|
||||
TextManager.Get(element.GetAttributeString("sonarlabel", ""), true) ??
|
||||
element.GetAttributeString("sonarlabel", "");
|
||||
}
|
||||
|
||||
SonarIconIdentifier = element.GetAttributeString("sonaricon", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
@@ -224,7 +241,7 @@ namespace Barotrauma
|
||||
|
||||
Headers = new List<string>();
|
||||
Messages = new List<string>();
|
||||
AllowedConnectionTypes = new List<Pair<string, string>>();
|
||||
AllowedConnectionTypes = new List<(string from, string to)>();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
@@ -260,9 +277,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowedConnectionTypes.Add(new Pair<string, string>(
|
||||
subElement.GetAttributeString("from", ""),
|
||||
subElement.GetAttributeString("to", "")));
|
||||
AllowedConnectionTypes.Add((subElement.GetAttributeString("from", "").ToLowerInvariant(), subElement.GetAttributeString("to", "").ToLowerInvariant()));
|
||||
}
|
||||
break;
|
||||
case "locationtypechange":
|
||||
@@ -358,13 +373,15 @@ namespace Barotrauma
|
||||
AllowedLocationTypes.Any(lt => lt.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
foreach (Pair<string, string> allowedConnectionType in AllowedConnectionTypes)
|
||||
foreach ((string fromType, string toType) in AllowedConnectionTypes)
|
||||
{
|
||||
if (allowedConnectionType.First.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedConnectionType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
if (fromType.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
fromType.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase) ||
|
||||
(fromType == "anyoutpost" && from.HasOutpost()))
|
||||
{
|
||||
if (allowedConnectionType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedConnectionType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
if (toType.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
toType.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase) ||
|
||||
(toType == "anyoutpost" && to.HasOutpost()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -345,12 +345,13 @@ namespace Barotrauma
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
int newState = State;
|
||||
if (state >= 2) { return; }
|
||||
|
||||
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
|
||||
outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange;
|
||||
if (State < 2 && CheckWinState())
|
||||
if (CheckWinState())
|
||||
{
|
||||
newState = 2;
|
||||
State = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -366,7 +367,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!outsideOfSonarRange || patrolPositions.None())
|
||||
{
|
||||
newState = 1;
|
||||
State = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
@@ -391,14 +392,13 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private bool CheckWinState() => !IsClient && characters.All(m => DeadOrCaptured(m));
|
||||
|
||||
private bool DeadOrCaptured(Character character)
|
||||
{
|
||||
return character == null || character.Removed || character.IsDead || (character.LockHands && character.Submarine == Submarine.MainSub);
|
||||
return character == null || character.Removed || character.Submarine == null || (character.LockHands && character.Submarine == Submarine.MainSub) || character.IsIncapacitated;
|
||||
}
|
||||
|
||||
public override void End()
|
||||
|
||||
@@ -149,9 +149,17 @@ namespace Barotrauma
|
||||
internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions)
|
||||
=> configureAvailableResourceCurrencies(customDimensions.Select(d => d.ToString()).ToArray());
|
||||
|
||||
private readonly Action<string[]> configureAvailableResourceItemTypes;
|
||||
internal void ConfigureAvailableResourceItemTypes(params string[] resourceItemTypes)
|
||||
=> configureAvailableResourceItemTypes(resourceItemTypes);
|
||||
|
||||
private readonly Action<bool> setEnabledInfoLog;
|
||||
internal void SetEnabledInfoLog(bool enabled)
|
||||
=> setEnabledInfoLog(enabled);
|
||||
|
||||
private readonly Action<bool> setEnabledVerboseLog;
|
||||
internal void SetEnabledVerboseLog(bool enabled)
|
||||
=> setEnabledVerboseLog(enabled);
|
||||
#endregion
|
||||
|
||||
#region Data required to fetch methods via reflection
|
||||
@@ -292,10 +300,14 @@ namespace Barotrauma
|
||||
|
||||
configureAvailableResourceCurrencies = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceCurrencies),
|
||||
new Type[] { typeof(string[]) }));
|
||||
configureAvailableResourceItemTypes = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceItemTypes),
|
||||
new Type[] { typeof(string[]) }));
|
||||
addResourceEvent = Call<ResourceFlowType, string, float, string, string>(getMethod(nameof(AddResourceEvent),
|
||||
new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) }));
|
||||
setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog),
|
||||
new Type[] { typeof(bool) }));
|
||||
setEnabledVerboseLog = Call<bool>(getMethod(nameof(SetEnabledVerboseLog),
|
||||
new Type[] { typeof(bool) }));
|
||||
|
||||
onQuit = Call(getMethod("OnQuit", Array.Empty<Type>()));
|
||||
}
|
||||
@@ -450,6 +462,7 @@ namespace Barotrauma
|
||||
try
|
||||
{
|
||||
loadedImplementation?.SetEnabledInfoLog(true);
|
||||
loadedImplementation?.SetEnabledVerboseLog(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -489,7 +502,10 @@ namespace Barotrauma
|
||||
+ AssemblyInfo.GitRevision + ":"
|
||||
+ buildConfiguration);
|
||||
loadedImplementation?.ConfigureAvailableCustomDimensions01(Enum.GetValues(typeof(CustomDimensions01)).Cast<CustomDimensions01>().ToArray());
|
||||
loadedImplementation?.ConfigureAvailableCustomDimensions02(Enum.GetValues(typeof(CustomDimensions02)).Cast<CustomDimensions02>().ToArray());
|
||||
loadedImplementation?.ConfigureAvailableResourceCurrencies(Enum.GetValues(typeof(ResourceCurrency)).Cast<ResourceCurrency>().ToArray());
|
||||
loadedImplementation?.ConfigureAvailableResourceItemTypes(
|
||||
Enum.GetValues(typeof(MoneySink)).Cast<MoneySink>().Select(s => s.ToString()).Union(Enum.GetValues(typeof(MoneySource)).Cast<MoneySource>().Select(s => s.ToString())).ToArray());
|
||||
|
||||
InitKeys();
|
||||
|
||||
@@ -521,7 +537,7 @@ namespace Barotrauma
|
||||
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
|
||||
}
|
||||
packageNames.Sort();
|
||||
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames));
|
||||
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(" ", packageNames));
|
||||
}
|
||||
loadedImplementation?.AddDesignEvent("Language:" + GameMain.Config.Language);
|
||||
}
|
||||
|
||||
@@ -220,10 +220,10 @@ namespace Barotrauma
|
||||
|
||||
private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
|
||||
{
|
||||
(0, 0.85f),
|
||||
(1, 0.125f),
|
||||
(2, 0.0225f),
|
||||
(3, 0.0025f),
|
||||
(0, 1.0f),
|
||||
(1, 0.0f),
|
||||
(2, 0.0f),
|
||||
(3, 0.0f),
|
||||
};
|
||||
|
||||
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
|
||||
|
||||
@@ -16,11 +16,13 @@ namespace Barotrauma
|
||||
{
|
||||
public ItemPrefab ItemPrefab { get; }
|
||||
public int Quantity { get; set; }
|
||||
public bool? IsStoreComponentEnabled { get; set; }
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,11 +427,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
itemSpawned(item);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
|
||||
itemSpawned(item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(item, false);
|
||||
#endif
|
||||
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
|
||||
static void itemSpawned(Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
|
||||
@@ -81,7 +81,12 @@ namespace Barotrauma
|
||||
public double TotalPlayTime;
|
||||
public int TotalPassedLevels;
|
||||
|
||||
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic }
|
||||
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic, Cargo }
|
||||
|
||||
public static bool BlocksInteraction(InteractionType interactionType)
|
||||
{
|
||||
return interactionType != InteractionType.None && interactionType != InteractionType.Cargo;
|
||||
}
|
||||
|
||||
public readonly CargoManager CargoManager;
|
||||
public UpgradeManager UpgradeManager;
|
||||
|
||||
@@ -440,6 +440,7 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
|
||||
}
|
||||
}
|
||||
GameAnalyticsManager.AddDesignEvent($"{eventId}HintManager:{(HintManager.Enabled ? "Enabled" : "Disabled")}");
|
||||
#endif
|
||||
if (GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace Barotrauma.Items.Components
|
||||
if (character != null && !CharacterUsable) { return false; }
|
||||
|
||||
CurrPowerConsumption = powerConsumption;
|
||||
Voltage = 0.0f;
|
||||
charging = true;
|
||||
timer = Duration;
|
||||
IsActive = true;
|
||||
@@ -141,7 +142,7 @@ namespace Barotrauma.Items.Components
|
||||
timer -= deltaTime;
|
||||
if (charging)
|
||||
{
|
||||
if (GetAvailableBatteryPower() >= powerConsumption)
|
||||
if (GetAvailableInstantaneousBatteryPower() >= powerConsumption)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
float neededPower = powerConsumption;
|
||||
|
||||
@@ -130,6 +130,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots))
|
||||
{
|
||||
if (item.CampaignInteractionType == CampaignMode.InteractionType.Cargo)
|
||||
{
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
}
|
||||
|
||||
if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; }
|
||||
this.picker = picker;
|
||||
|
||||
|
||||
@@ -370,8 +370,22 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
|
||||
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
|
||||
|
||||
item.SendSignal(new Signal((sub.WorldPosition.X * Physics.DisplayToRealWorldRatio).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
|
||||
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
|
||||
Vector2 pos = new Vector2(sub.WorldPosition.X * Physics.DisplayToRealWorldRatio, sub.RealWorldDepth);
|
||||
if (sonar != null && sonar.UseTransducers && sonar.CenterOnTransducers && sonar.ConnectedTransducers.Any())
|
||||
{
|
||||
pos = Vector2.Zero;
|
||||
foreach (var connectedTransducer in sonar.ConnectedTransducers)
|
||||
{
|
||||
pos += connectedTransducer.Item.WorldPosition;
|
||||
}
|
||||
pos /= sonar.ConnectedTransducers.Count();
|
||||
pos = new Vector2(
|
||||
pos.X * Physics.DisplayToRealWorldRatio,
|
||||
Level.Loaded?.GetRealWorldDepth(pos.Y) ?? (-pos.Y * Physics.DisplayToRealWorldRatio));
|
||||
}
|
||||
|
||||
item.SendSignal(new Signal(pos.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
|
||||
item.SendSignal(new Signal(pos.Y.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
|
||||
}
|
||||
|
||||
// if our tactical AI pilot has left, revert back to maintaining position
|
||||
|
||||
@@ -302,17 +302,24 @@ namespace Barotrauma.Items.Components
|
||||
/// <summary>
|
||||
/// Returns the amount of power that can be supplied by batteries directly connected to the item
|
||||
/// </summary>
|
||||
protected float GetAvailableBatteryPower()
|
||||
protected float GetAvailableInstantaneousBatteryPower()
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
if (item.Connections == null) { return 0.0f; }
|
||||
float availablePower = 0.0f;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut);
|
||||
availablePower += batteryPower;
|
||||
}
|
||||
var recipients = c.Recipients;
|
||||
foreach (Connection recipient in recipients)
|
||||
{
|
||||
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
|
||||
var battery = recipient.Item?.GetComponent<PowerContainer>();
|
||||
if (battery == null) { continue; }
|
||||
|
||||
float maxOutputPerFrame = battery.MaxOutPut / 60.0f;
|
||||
float framesPerMinute = 3600.0f;
|
||||
availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame);
|
||||
}
|
||||
}
|
||||
return availablePower;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,15 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private float deactivationTimer;
|
||||
|
||||
[Serialize(0f, false)]
|
||||
public float DeactivationTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
@@ -207,6 +216,9 @@ namespace Barotrauma.Items.Components
|
||||
get { return StickTarget != null; }
|
||||
}
|
||||
|
||||
private Category originalCollisionCategories;
|
||||
private Category originalCollisionTargets;
|
||||
|
||||
public Projectile(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
@@ -223,21 +235,26 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
if (Attack != null && Attack.DamageRange <= 0.0f && item.body != null)
|
||||
if (item.body != null)
|
||||
{
|
||||
switch (item.body.BodyShape)
|
||||
if (Attack != null && Attack.DamageRange <= 0.0f)
|
||||
{
|
||||
case PhysicsBody.Shape.Circle:
|
||||
Attack.DamageRange = item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Capsule:
|
||||
Attack.DamageRange = item.body.height / 2 + item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Rectangle:
|
||||
Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length();
|
||||
break;
|
||||
switch (item.body.BodyShape)
|
||||
{
|
||||
case PhysicsBody.Shape.Circle:
|
||||
Attack.DamageRange = item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Capsule:
|
||||
Attack.DamageRange = item.body.height / 2 + item.body.radius;
|
||||
break;
|
||||
case PhysicsBody.Shape.Rectangle:
|
||||
Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length();
|
||||
break;
|
||||
}
|
||||
Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange);
|
||||
}
|
||||
Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange);
|
||||
originalCollisionCategories = item.body.CollisionCategories;
|
||||
originalCollisionTargets = item.body.CollidesWith;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +276,10 @@ namespace Barotrauma.Items.Components
|
||||
launchPos = simPosition;
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians));
|
||||
if (DeactivationTime > 0)
|
||||
{
|
||||
deactivationTimer = DeactivationTime;
|
||||
}
|
||||
}
|
||||
|
||||
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f)
|
||||
@@ -585,7 +606,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (dropper != null)
|
||||
{
|
||||
Deactivate();
|
||||
DisableProjectileCollisions();
|
||||
Unstick();
|
||||
}
|
||||
base.Drop(dropper);
|
||||
@@ -593,6 +614,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (DeactivationTime > 0)
|
||||
{
|
||||
deactivationTimer -= deltaTime;
|
||||
if (deactivationTimer < 0)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
}
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
@@ -614,8 +643,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
//projectiles with a stickjoint don't become inactive until the stickjoint is detached
|
||||
if (stickJoint == null && !item.body.FarseerBody.IsBullet)
|
||||
{
|
||||
IsActive = false;
|
||||
{
|
||||
IsActive = false;
|
||||
if (DeactivationTime > 0 && deactivationTimer > 0)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
}
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
@@ -715,7 +748,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
{
|
||||
Deactivate();
|
||||
DisableProjectileCollisions();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -864,7 +897,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
|
||||
{
|
||||
Deactivate();
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
@@ -934,18 +967,26 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
private void DisableProjectileCollisions()
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
if (originalCollisionCategories != Category.None && originalCollisionTargets != Category.None)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionCharacter;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
item.body.CollisionCategories = originalCollisionCategories;
|
||||
item.body.CollidesWith = originalCollisionTargets;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionCharacter;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
}
|
||||
}
|
||||
IgnoredBodies.Clear();
|
||||
}
|
||||
@@ -996,7 +1037,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
stickJoint = null;
|
||||
}
|
||||
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
|
||||
if (!item.body.FarseerBody.IsBullet)
|
||||
{
|
||||
IsActive = false;
|
||||
if (DeactivationTime > 0 && deactivationTimer > 0)
|
||||
{
|
||||
DisableProjectileCollisions();
|
||||
}
|
||||
}
|
||||
item.GetComponent<Rope>()?.Snap();
|
||||
if (stickTargetCharacter != null)
|
||||
{
|
||||
|
||||
@@ -236,6 +236,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
ciElement.Connection = item.Connections?.FirstOrDefault(c => c.Name == ciElement.ConnectionName);
|
||||
}
|
||||
#if SERVER
|
||||
//make sure the clients know about the states of the checkboxes and text fields
|
||||
if (item.Submarine == null || !item.Submarine.Loading)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void UpdateLabelsProjSpecific();
|
||||
|
||||
@@ -542,7 +542,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool HasPowerToShoot()
|
||||
{
|
||||
return GetAvailableBatteryPower() >= GetPowerRequiredToShoot();
|
||||
return GetAvailableInstantaneousBatteryPower() >= GetPowerRequiredToShoot();
|
||||
}
|
||||
|
||||
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
|
||||
|
||||
@@ -365,7 +365,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (var allowedSlot in allowedSlots)
|
||||
{
|
||||
if (allowedSlot != InvSlotType.Any && !character.Inventory.IsInLimbSlot(item, allowedSlot)) { return; }
|
||||
if (allowedSlot == InvSlotType.Any) { continue; }
|
||||
foreach (Enum value in Enum.GetValues(typeof(InvSlotType)))
|
||||
{
|
||||
var slotType = (InvSlotType)value;
|
||||
if (slotType == InvSlotType.Any || slotType == InvSlotType.None) { continue; }
|
||||
if (allowedSlot.HasFlag(slotType) && !character.Inventory.IsInLimbSlot(item, slotType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
picker = character;
|
||||
|
||||
@@ -98,6 +98,14 @@ namespace Barotrauma
|
||||
private readonly ItemInventory ownInventory;
|
||||
|
||||
private Rectangle defaultRect;
|
||||
/// <summary>
|
||||
/// Unscaled rect
|
||||
/// </summary>
|
||||
public Rectangle DefaultRect
|
||||
{
|
||||
get { return defaultRect; }
|
||||
set { defaultRect = value; }
|
||||
}
|
||||
|
||||
private Dictionary<string, Connection> connections;
|
||||
|
||||
@@ -645,10 +653,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float buoyancySineMagnitude;
|
||||
private float buoyancySineFrequency;
|
||||
private float buoyancyRandomForce;
|
||||
|
||||
public bool FireProof
|
||||
{
|
||||
get { return Prefab.FireProof; }
|
||||
@@ -767,7 +771,7 @@ namespace Barotrauma
|
||||
get { return Position.X; }
|
||||
private set
|
||||
{
|
||||
Move(new Vector2((value - Position.X) * Scale, 0.0f));
|
||||
Move(new Vector2(value * Scale, 0.0f));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -778,7 +782,7 @@ namespace Barotrauma
|
||||
get { return Position.Y; }
|
||||
private set
|
||||
{
|
||||
Move(new Vector2(0.0f, (value - Position.Y) * Scale));
|
||||
Move(new Vector2(0.0f, value * Scale));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,7 +855,16 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "body":
|
||||
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale);
|
||||
float density = subElement.GetAttributeFloat("density", 10.0f);
|
||||
float minDensity = subElement.GetAttributeFloat("mindensity", density);
|
||||
float maxDensity = subElement.GetAttributeFloat("maxdensity", density);
|
||||
if (minDensity < maxDensity)
|
||||
{
|
||||
var rand = new Random(ID);
|
||||
density = MathHelper.Lerp(minDensity, maxDensity, (float)rand.NextDouble());
|
||||
}
|
||||
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density);
|
||||
|
||||
string collisionCategory = subElement.GetAttributeString("collisioncategory", null);
|
||||
if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0)
|
||||
{
|
||||
@@ -879,9 +892,6 @@ namespace Barotrauma
|
||||
}
|
||||
body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f);
|
||||
body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f);
|
||||
buoyancySineMagnitude = subElement.GetAttributeFloat("buoyancysinemagnitude", 0f);
|
||||
buoyancySineFrequency = subElement.GetAttributeFloat("buoyancysinefrequency", 0f);
|
||||
buoyancyRandomForce = subElement.GetAttributeFloat("buoyancyrandom", 0f);
|
||||
body.UserData = this;
|
||||
break;
|
||||
case "trigger":
|
||||
@@ -1600,7 +1610,7 @@ namespace Barotrauma
|
||||
float damageAmount = attack.GetItemDamage(deltaTime);
|
||||
Condition -= damageAmount;
|
||||
|
||||
if (damageAmount > 0)
|
||||
if (damageAmount >= Prefab.OnDamagedThreshold)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
}
|
||||
@@ -1729,7 +1739,7 @@ namespace Barotrauma
|
||||
UpdateNetPosition(deltaTime);
|
||||
if (inWater)
|
||||
{
|
||||
ApplyWaterForces(deltaTime);
|
||||
ApplyWaterForces();
|
||||
CurrentHull?.ApplyFlowForces(deltaTime, this);
|
||||
}
|
||||
}
|
||||
@@ -1818,24 +1828,16 @@ namespace Barotrauma
|
||||
transformDirty = false;
|
||||
}
|
||||
|
||||
private float sineTime;
|
||||
/// <summary>
|
||||
/// Applies buoyancy, drag and angular drag caused by water
|
||||
/// </summary>
|
||||
private void ApplyWaterForces(float deltaTime)
|
||||
private void ApplyWaterForces()
|
||||
{
|
||||
if (body.Mass <= 0.0f || body.Density <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (buoyancySineFrequency > 0)
|
||||
{
|
||||
if (sineTime >= float.MaxValue)
|
||||
{
|
||||
sineTime = float.MinValue;
|
||||
}
|
||||
sineTime += deltaTime * buoyancySineFrequency;
|
||||
}
|
||||
|
||||
float forceFactor = 1.0f;
|
||||
if (CurrentHull != null)
|
||||
{
|
||||
@@ -1854,10 +1856,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 drag = body.LinearVelocity * volume;
|
||||
|
||||
float sine = (float)Math.Sin(sineTime) * buoyancySineMagnitude;
|
||||
Vector2 sineForce = Vector2.UnitY * sine * volume;
|
||||
Vector2 randomForce = Vector2.UnitY * Rand.Range(-buoyancyRandomForce, buoyancyRandomForce, Rand.RandSync.Unsynced) * volume;
|
||||
body.ApplyForce((uplift - drag) * 10.0f + sineForce + randomForce);
|
||||
body.ApplyForce((uplift - drag) * 10.0f);
|
||||
|
||||
//apply simple angular drag
|
||||
body.ApplyTorque(body.AngularVelocity * volume * -0.05f);
|
||||
@@ -1869,9 +1868,17 @@ namespace Barotrauma
|
||||
if (transformDirty) { return false; }
|
||||
|
||||
var projectile = GetComponent<Projectile>();
|
||||
if (projectile?.IgnoredBodies != null)
|
||||
if (projectile != null)
|
||||
{
|
||||
if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; }
|
||||
//ignore character colliders (a projectile only hits limbs)
|
||||
if (f2.CollisionCategories == Physics.CollisionCharacter && f2.Body.UserData is Character)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (projectile.IgnoredBodies != null)
|
||||
{
|
||||
if (projectile.IgnoredBodies.Contains(f2.Body)) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
contact.GetWorldManifold(out Vector2 normal, out _);
|
||||
@@ -2216,7 +2223,7 @@ namespace Barotrauma
|
||||
foreach (Rectangle trigger in Prefab.Triggers)
|
||||
{
|
||||
transformedTrigger = TransformTrigger(trigger, true);
|
||||
if (Submarine.RectContains(transformedTrigger, worldPosition)) return true;
|
||||
if (Submarine.RectContains(transformedTrigger, worldPosition)) { return true; }
|
||||
}
|
||||
|
||||
transformedTrigger = Rectangle.Empty;
|
||||
@@ -2230,7 +2237,10 @@ namespace Barotrauma
|
||||
|
||||
public bool TryInteract(Character user, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceUseKey = false)
|
||||
{
|
||||
if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; }
|
||||
if (CampaignMode.BlocksInteraction(CampaignInteractionType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool picked = false, selected = false;
|
||||
#if CLIENT
|
||||
|
||||
@@ -504,6 +504,9 @@ namespace Barotrauma
|
||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float OnDamagedThreshold { get; set; }
|
||||
|
||||
[Serialize(0.0f, false)]
|
||||
public float SonarSize
|
||||
{
|
||||
|
||||
@@ -1723,7 +1723,7 @@ namespace Barotrauma
|
||||
{
|
||||
vertices[j] += position;
|
||||
}
|
||||
var newChunk = new LevelWall(vertices, GenerationParams.WallColor, this);
|
||||
var newChunk = new LevelWall(vertices, GenerationParams.WallColor, this, createBody: false);
|
||||
AbyssIslands.Add(new AbyssIsland(islandArea, newChunk.Cells));
|
||||
continue;
|
||||
}
|
||||
@@ -1842,7 +1842,7 @@ namespace Barotrauma
|
||||
Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2);
|
||||
|
||||
int radius = Math.Max(caveSize.X, caveSize.Y) / 2;
|
||||
var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.5f, asCloseAsPossible: true, allowedArea);
|
||||
var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.25f, asCloseAsPossible: true, allowedArea);
|
||||
|
||||
GenerateCave(caveParams, parentTunnel, cavePos, caveSize);
|
||||
|
||||
@@ -2107,12 +2107,42 @@ namespace Barotrauma
|
||||
|
||||
private Point FindPosAwayFromMainPath(double minDistance, bool asCloseAsPossible, Rectangle? limits = null)
|
||||
{
|
||||
var validPoints = distanceField.FindAll(d => d.distance >= minDistance && (limits == null || limits.Value.Contains(d.point)));
|
||||
validPoints.RemoveAll(d => d.point.Y < GetBottomPosition(d.point.X).Y + minDistance);
|
||||
if (asCloseAsPossible || !validPoints.Any())
|
||||
var pointsAboveBottom = distanceField.FindAll(d => d.point.Y > GetBottomPosition(d.point.X).Y + minDistance);
|
||||
if (pointsAboveBottom.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in FindPosAwayFromMainPath: no valid positions above the bottom of the sea floor. Has the position of the sea floor been set too high up?");
|
||||
return distanceField[Rand.Int(distanceField.Count, Rand.RandSync.Server)].point;
|
||||
}
|
||||
|
||||
var validPoints = pointsAboveBottom.FindAll(d => d.distance >= minDistance && (limits == null || limits.Value.Contains(d.point)));
|
||||
if (!validPoints.Any())
|
||||
{
|
||||
DebugConsole.AddWarning("Failed to find a valid position far enough from the main path. Choosing the furthest possible position.\n" + Environment.StackTrace);
|
||||
if (limits != null)
|
||||
{
|
||||
//try choosing something within the specified limits
|
||||
validPoints = pointsAboveBottom.FindAll(d => limits.Value.Contains(d.point));
|
||||
}
|
||||
if (!validPoints.Any())
|
||||
{
|
||||
//couldn't find anything, let's just go with the furthest one
|
||||
validPoints = pointsAboveBottom;
|
||||
}
|
||||
(Point position, double distance) furthestPoint = validPoints.First();
|
||||
foreach (var point in validPoints)
|
||||
{
|
||||
if (point.distance > furthestPoint.distance)
|
||||
{
|
||||
furthestPoint = point;
|
||||
}
|
||||
}
|
||||
return furthestPoint.position;
|
||||
}
|
||||
|
||||
if (asCloseAsPossible)
|
||||
{
|
||||
if (!validPoints.Any()) { validPoints = distanceField; }
|
||||
(Point position, double distance) closestPoint = validPoints.First();
|
||||
(Point position, double distance) closestPoint = validPoints.First();
|
||||
foreach (var point in validPoints)
|
||||
{
|
||||
if (point.distance < closestPoint.distance)
|
||||
@@ -2172,7 +2202,7 @@ namespace Barotrauma
|
||||
{
|
||||
double xDiff = Math.Abs(point.X - ruinPos.X);
|
||||
double yDiff = Math.Abs(point.Y - ruinPos.Y);
|
||||
if (xDiff < ruinSize || yDiff < ruinSize)
|
||||
if (xDiff < ruinSize && yDiff < ruinSize)
|
||||
{
|
||||
shortestDistSqr = 0.0f;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
set { moveState = MathHelper.Clamp(value, 0.0f, MathHelper.TwoPi); }
|
||||
}
|
||||
|
||||
public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false)
|
||||
public LevelWall(List<Vector2> vertices, Color color, Level level, bool giftWrap = false, bool createBody = true)
|
||||
{
|
||||
this.level = level;
|
||||
this.color = color;
|
||||
@@ -74,14 +74,17 @@ namespace Barotrauma
|
||||
wallCell.Edges[i].IsSolid = true;
|
||||
}
|
||||
Cells = new List<VoronoiCell>() { wallCell };
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
if (triangles.Count == 0)
|
||||
if (createBody)
|
||||
{
|
||||
throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString())));
|
||||
}
|
||||
Body = CaveGenerator.GeneratePolygons(Cells, level, out triangles);
|
||||
if (triangles.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Failed to generate a wall (not enough triangles). Original vertices: " + string.Join(", ", originalVertices.Select(v => v.ToString())));
|
||||
}
|
||||
#if CLIENT
|
||||
GenerateVertices();
|
||||
GenerateVertices();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public LevelWall(List<Vector2> edgePositions, Vector2 extendAmount, Color color, Level level)
|
||||
|
||||
@@ -1776,7 +1776,7 @@ namespace Barotrauma
|
||||
if (connectedWp.isObstructed) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition);
|
||||
var body = Submarine.PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false);
|
||||
var body = PickBody(start, end, null, Physics.CollisionLevel, allowInsideFixture: false);
|
||||
if (body != null)
|
||||
{
|
||||
connectedWp.isObstructed = true;
|
||||
@@ -1803,7 +1803,7 @@ namespace Barotrauma
|
||||
foreach (var connection in node.connections)
|
||||
{
|
||||
var connectedWp = connection.Waypoint;
|
||||
if (connectedWp.isObstructed) { continue; }
|
||||
if (connectedWp.isObstructed || connectedWp.Ladders != null) { continue; }
|
||||
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
|
||||
Vector2 end = ConvertUnits.ToSimUnits(connectedWp.WorldPosition) - otherSub.SimPosition;
|
||||
var body = PickBody(start, end, null, Physics.CollisionWall, allowInsideFixture: true);
|
||||
|
||||
@@ -315,14 +315,33 @@ namespace Barotrauma
|
||||
set { FarseerBody.BodyType = value; }
|
||||
}
|
||||
|
||||
private Category _collisionCategories;
|
||||
|
||||
public Category CollisionCategories
|
||||
{
|
||||
set { FarseerBody.CollisionCategories = value; }
|
||||
set
|
||||
{
|
||||
_collisionCategories = value;
|
||||
FarseerBody.CollisionCategories = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return _collisionCategories;
|
||||
}
|
||||
}
|
||||
|
||||
private Category _collidesWith;
|
||||
public Category CollidesWith
|
||||
{
|
||||
set { FarseerBody.CollidesWith = value; }
|
||||
set
|
||||
{
|
||||
_collidesWith = value;
|
||||
FarseerBody.CollidesWith = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return _collidesWith;
|
||||
}
|
||||
}
|
||||
|
||||
public PhysicsBody(XElement element, float scale = 1.0f) : this(element, Vector2.Zero, scale) { }
|
||||
@@ -383,12 +402,12 @@ namespace Barotrauma
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f)
|
||||
public PhysicsBody(XElement element, Vector2 position, float scale = 1.0f, float? forceDensity = null)
|
||||
{
|
||||
float radius = ConvertUnits.ToSimUnits(element.GetAttributeFloat("radius", 0.0f)) * scale;
|
||||
float height = ConvertUnits.ToSimUnits(element.GetAttributeFloat("height", 0.0f)) * scale;
|
||||
float width = ConvertUnits.ToSimUnits(element.GetAttributeFloat("width", 0.0f)) * scale;
|
||||
density = Math.Max(element.GetAttributeFloat("density", 10.0f), MinDensity);
|
||||
density = Math.Max(forceDensity ?? element.GetAttributeFloat("density", 10.0f), MinDensity);
|
||||
CreateBody(width, height, radius, density);
|
||||
Enum.TryParse(element.GetAttributeString("bodytype", "Dynamic"), out BodyType bodyType);
|
||||
FarseerBody.BodyType = bodyType;
|
||||
|
||||
@@ -245,6 +245,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory);
|
||||
|
||||
Character.Controlled?.UpdateLocalCursor(cam);
|
||||
#endif
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
|
||||
@@ -929,6 +929,21 @@ namespace Barotrauma
|
||||
(int)structure.Prefab.ScaledSize.Y);
|
||||
}
|
||||
}
|
||||
else if (entity is Item item)
|
||||
{
|
||||
if (!item.ResizeHorizontal)
|
||||
{
|
||||
item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
|
||||
(int)(item.Prefab.Size.X * item.Prefab.Scale),
|
||||
item.Rect.Height);
|
||||
}
|
||||
if (!item.ResizeVertical)
|
||||
{
|
||||
item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
|
||||
item.Rect.Width,
|
||||
(int)(item.Prefab.Size.Y * item.Prefab.Scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property))
|
||||
|
||||
@@ -133,7 +133,8 @@ namespace Barotrauma
|
||||
Target,
|
||||
Limb,
|
||||
MainLimb,
|
||||
Collider
|
||||
Collider,
|
||||
Random
|
||||
}
|
||||
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
@@ -1487,7 +1488,7 @@ namespace Barotrauma
|
||||
|
||||
if (giveTalentInfo.GiveRandom)
|
||||
{
|
||||
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
|
||||
targetCharacter.GiveTalent(viableTalents.GetRandom(Rand.RandSync.Unsynced), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1542,7 +1543,7 @@ namespace Barotrauma
|
||||
var characters = new List<Character>();
|
||||
for (int i = 0; i < characterSpawnInfo.Count; i++)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Server) + characterSpawnInfo.Offset,
|
||||
Entity.Spawner.AddToSpawnQueue(characterSpawnInfo.SpeciesName, position + Rand.Vector(characterSpawnInfo.Spread, Rand.RandSync.Unsynced) + characterSpawnInfo.Offset,
|
||||
onSpawn: newCharacter =>
|
||||
{
|
||||
if (newCharacter.AIController is EnemyAIController enemyAi &&
|
||||
@@ -1563,7 +1564,7 @@ namespace Barotrauma
|
||||
|
||||
if (spawnItemRandomly)
|
||||
{
|
||||
SpawnItem(spawnItems.GetRandom());
|
||||
SpawnItem(spawnItems.GetRandom(Rand.RandSync.Unsynced));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1582,7 +1583,7 @@ namespace Barotrauma
|
||||
switch (chosenItemSpawnInfo.SpawnPosition)
|
||||
{
|
||||
case ItemSpawnInfo.SpawnPositionType.This:
|
||||
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem =>
|
||||
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
|
||||
{
|
||||
Projectile projectile = newItem.GetComponent<Projectile>();
|
||||
if (projectile != null && user != null && sourceBody != null && entity != null)
|
||||
@@ -1597,7 +1598,7 @@ namespace Barotrauma
|
||||
}
|
||||
float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread));
|
||||
var worldPos = sourceBody.Position;
|
||||
float rotation = chosenItemSpawnInfo.Rotation;
|
||||
float rotation = 0;
|
||||
if (user.Submarine != null)
|
||||
{
|
||||
worldPos += user.Submarine.Position;
|
||||
@@ -1614,11 +1615,14 @@ namespace Barotrauma
|
||||
rotation = sourceBody.TransformedRotation;
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Collider:
|
||||
rotation = user.AnimController.Collider.Rotation;
|
||||
rotation = user.AnimController.Collider.Rotation + MathHelper.PiOver2;
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.MainLimb:
|
||||
rotation = user.AnimController.MainLimb.body.TransformedRotation;
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Random:
|
||||
DebugConsole.ShowError("Random rotation is not supported for Projectiles.");
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType);
|
||||
}
|
||||
@@ -1631,19 +1635,37 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
float rotation = MathHelper.ToRadians(chosenItemSpawnInfo.Rotation);
|
||||
if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Limb)
|
||||
switch (chosenItemSpawnInfo.RotationType)
|
||||
{
|
||||
if (sourceBody != null)
|
||||
{
|
||||
rotation += sourceBody.Rotation;
|
||||
}
|
||||
}
|
||||
else if (chosenItemSpawnInfo.RotationType == ItemSpawnInfo.SpawnRotationType.Collider)
|
||||
{
|
||||
if (entity is Character character)
|
||||
{
|
||||
rotation += character.AnimController.Collider.Rotation;
|
||||
}
|
||||
case ItemSpawnInfo.SpawnRotationType.Fixed:
|
||||
if (sourceBody != null)
|
||||
{
|
||||
rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.Rotation);
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Limb:
|
||||
if (sourceBody != null)
|
||||
{
|
||||
rotation += sourceBody.Rotation;
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Collider:
|
||||
if (entity is Character character)
|
||||
{
|
||||
rotation += character.AnimController.Collider.Rotation + MathHelper.PiOver2;
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.MainLimb:
|
||||
if (entity is Character c)
|
||||
{
|
||||
rotation = c.AnimController.MainLimb.body.TransformedRotation;
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnRotationType.Random:
|
||||
rotation = Rand.Range(0f, MathHelper.TwoPi, Rand.RandSync.Unsynced);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType);
|
||||
}
|
||||
body.SetTransform(newItem.SimPosition, rotation);
|
||||
body.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,81 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.16.3.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Changes and additions:
|
||||
- Optimized multiplayer store interface.
|
||||
- Set supercapacitors on subs to be charging by default (unstable only).
|
||||
- Added damage sounds to doors when they take 10 or more damage.
|
||||
- Made nav terminals output the position of the transducer instead of the sub when using the new "center on transducer" setting (unstable only).
|
||||
- Made outpost containers' sprite depths more consistent with other containers.
|
||||
- Added tags to outpost medical compartments and made them linkable.
|
||||
- Mission completion and failure icons are now displayed mid-round in the tab menu.
|
||||
- Monster mission reward and difficulty level adjustments.
|
||||
- Adjustments to the random monster spawn events.
|
||||
- The new monster variants now also spawn in the multiplayer single mission -mode rounds.
|
||||
- More polished monster variants (still WIP)
|
||||
- Added a ranged attack for Crawler Broodmother.
|
||||
- An overall balance and behavior fix pass on all monsters. Feedback is welcome.
|
||||
- Moloch Pupa and Hammerhead Matriarch now also drop some loot.
|
||||
- Adjusted the loot dropped by Crawler Broodmother, Giant Spineling, and Bonethresher.
|
||||
- Crawler Eggs now deconstruct into suplhuric acid (and adrenaline gland, if they are not the smallest variants).
|
||||
- Changed the small arms max stack sizes to either 6 or 12 when the clip size is 6. Makes it less tedious to use the extra ammunition.
|
||||
- Tigerthreshers can now target doors.
|
||||
- Monsters that try to get inside the sub, should now notice and priorize doors more overall. Affects e.g. Mudraptors, and to lesser extent Crawlers (and Tigerthreshers).
|
||||
- Reduced the range where the bots can spot enemies outside of the sub.
|
||||
- Improved bots' ability to return back to the submarine from caves.
|
||||
|
||||
Talent nerfing:
|
||||
- Removed the special stat boosts from "Olympian" (now it only increases the skill cap to 200).
|
||||
- Halved the amount of damage "Still Kicking" heals (100 -> 50)
|
||||
- Reduced gunshot wounds inflicted by handcannon.
|
||||
- "True Potential" only has a chance of instakilling things smaller than a moloch.
|
||||
- Halved damage buff from "Quickdraw" (80% -> 40%).
|
||||
- Reduced skill gain from "Field Medic" (7 -> 3).
|
||||
- Nerfed "Warlord" (20% chance of doubling the damage -> 5% chance).
|
||||
- Reduced damage buff from "Expert Commando" (40% -> 20%).
|
||||
|
||||
Fixes:
|
||||
- Misc fixes to Barsuk, Herja, Winterhalter and Orca 2.
|
||||
- Fixed crashing when dragging a docking port in the sub editor (unstable only).
|
||||
- Fixed turrets being able to pull power from supercapacitor's power_in connection (unstable only).
|
||||
- Fixed cursor position jittering when the sub is moving fast.
|
||||
- Fixed discharge coils in Berilia and Orca 2 being connected to junction boxes instead of supercapacitors.
|
||||
- Fixed wall bodies generating twice on abyss islands without caves, and the 1st generated wall not getting mirrored along with the level, leading to "invisible walls" in some areas of the abyss in mirrored levels.
|
||||
- Pirates that are outside or unconscious count as being dead in the pirate missions. Fixes pirate missions failing if e.g. one of the pirates gets stranded outside their sub.
|
||||
- Fixed some turrets being possible to power with batteries, even though the maximum power output of the batteries shouldn't be high enough.
|
||||
- Fixed bots dropping the syringe inside PUCS when replacing the oxygen tank.
|
||||
- Fixed spineling mission using "Giant Spineling" as the sonar label on all the spinelings (unstable only).
|
||||
- Fixed bots sometimes failing to find a path to a docked shuttle or drone.
|
||||
- Fixed sound effects not playing when a monster hits the sub's inner wall.
|
||||
- Fixed correct sprite not being used in the great sea on the campaign map.
|
||||
- Fixed "settings" text overlapping in the settings menu when using a very large text size.
|
||||
- EventManager doesn't consider monsters in a docked non-player sub (e.g. abandoned outpost) to be "inside the sub". Fixes intensity always being at 100% in monster-infested outposts.
|
||||
- Fixed outpost cabinet's sprite having empty space above it.
|
||||
- Fixed inability to put syringe guns, toy hammers, welding tools, plasma cutters and sprayers in weapon holders.
|
||||
- Fixed the SMG magazine recycle recipe requiring a full magazine instead of an empty one.
|
||||
- Fixed monsters being unable to target inner walls when they are technically outside of the sub (= when there's no hull where they are). Such places, between the outer and the inner walls, can be found e.g. in Humpback.
|
||||
- Fixed escort missions giving huge rewards in higher difficulty levels.
|
||||
- Fixed NPCs reacting to combat between other characters when they shouldn't (e.g. when they don't witness it).
|
||||
- Fixed the drug dealer in "heartofgold" fleeing from the other bandits.
|
||||
- Fixed bandits (and pirates?) fleeing from the player after being in a combat for a while (unstable only).
|
||||
- Fixed mudraptors being unable to hit the targets that are very near.
|
||||
- Fixed tigerthreshers getting stuck on doors instead of attacking them (unstable only).
|
||||
- Fixed monsters sometimes getting stuck near the Humpback's bottom railgun.
|
||||
- Fixed monsters getting stuck on trying to reach open gaps that are on the other side of the sub.
|
||||
- Fixed decapitating not working as it should.
|
||||
- Fixed being able to grab hostile NPCs.
|
||||
- Fixed bots not always reacting to monsters when they should be able to see them, while swimming outside.
|
||||
- Fixed bots accidentally damaging friendly characters while trying to hit Swarmfeeders latched on to them.
|
||||
- Fixed bots not using melee weapons when there's Swarmfeeders latched on to them.
|
||||
- Fixed bots being able to shoot without any delay if they already have a weapon equipped.
|
||||
- Fixed some monsters, like crawlers, trying to target walls with lots of gaps even though there are better targets closer to them.
|
||||
- Fixed bots taking battery cells from portable pumps without considering their condition when acting on the Recharge Battery Cells order.
|
||||
|
||||
Modding:
|
||||
- Fixed crashing in multiplayer when there are spectators in the server and someone reaches the final stage of a modded husk affliction that allows remaining in control of the final form.
|
||||
- Fixed wearables that are equipped into multiple slots (e.g. InnerClothes+OuterClothes) not being visible when worn.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.16.2.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
@@ -30,6 +108,8 @@ Fixes:
|
||||
- Fixed some hairs clipping through hats (unstable only).
|
||||
- Fixed bots returning to the sub even when they have an active wait order. Happened when the order was given inside and then when e.g. the character is controlled by the player, and then when the player changes the character, the bot falls to the "find safety objective", because it's not allowed to stay outside.
|
||||
- Fixed aggressive boarders not being aggressive enough inside the player sub, because they couldn't target things that were blocked by a wall.
|
||||
- Fixed Molochs not doing anything when there's babies around.
|
||||
- Fixed Mudraptors not staying together in swarms.
|
||||
- Adjusted threshers' targeting priorities. Tigerthreshers can now damage doors, but should do so only when they get inside.
|
||||
- Fixed crouching animation not appearing in MP when moving backwards while crouching (unstable only).
|
||||
- Fixed crashing when trying to send a messagebox to clients using console commands (unstable only).
|
||||
|
||||
Reference in New Issue
Block a user