Unstable 0.1400.2.0 (a mimir edition)
This commit is contained in:
@@ -16,6 +16,13 @@ namespace Barotrauma
|
||||
|
||||
public enum CirclePhase { Start, CloseIn, FallBack, Advance, Strike }
|
||||
|
||||
public enum WallTargetingMethod
|
||||
{
|
||||
Target = 0x1,
|
||||
Heading = 0x2,
|
||||
Steering = 0x4
|
||||
}
|
||||
|
||||
partial class EnemyAIController : AIController
|
||||
{
|
||||
public static bool DisableEnemyAI;
|
||||
@@ -54,7 +61,7 @@ namespace Barotrauma
|
||||
private float attackLimbResetTimer;
|
||||
|
||||
private bool IsAttackRunning => AttackingLimb != null && AttackingLimb.attack.IsRunning;
|
||||
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0;
|
||||
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0 || _previousAttackingLimb != null && _previousAttackingLimb.attack.CoolDownTimer > 0;
|
||||
public float CombatStrength => AIParams.CombatStrength;
|
||||
private float Sight => AIParams.Sight;
|
||||
private float Hearing => AIParams.Hearing;
|
||||
@@ -63,14 +70,18 @@ namespace Barotrauma
|
||||
|
||||
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
|
||||
|
||||
//the limb selected for the current attack
|
||||
private Limb _attackingLimb;
|
||||
private Limb _previousAttackingLimb;
|
||||
public Limb AttackingLimb
|
||||
{
|
||||
get { return _attackingLimb; }
|
||||
private set
|
||||
{
|
||||
attackLimbResetTimer = 0;
|
||||
if (_attackingLimb != value)
|
||||
{
|
||||
_previousAttackingLimb = _attackingLimb;
|
||||
}
|
||||
_attackingLimb = value;
|
||||
attackVector = null;
|
||||
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
|
||||
@@ -342,6 +353,10 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
else
|
||||
{
|
||||
targetingTag = "equal";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,10 +495,6 @@ namespace Barotrauma
|
||||
{
|
||||
CharacterParams.TargetParams targetingParams = null;
|
||||
UpdateTargets(Character, out targetingParams);
|
||||
if (!IsLatchedOnSub)
|
||||
{
|
||||
UpdateWallTarget(requiredHoleCount);
|
||||
}
|
||||
updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f);
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
@@ -494,6 +505,10 @@ namespace Barotrauma
|
||||
selectedTargetingParams = targetingParams;
|
||||
State = targetingParams.State;
|
||||
}
|
||||
if (SelectedAiTarget?.Entity != null && !IsLatchedOnSub && State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive)
|
||||
{
|
||||
UpdateWallTarget(requiredHoleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,12 +1019,8 @@ namespace Barotrauma
|
||||
if (!w.SectionBodyDisabled(i))
|
||||
{
|
||||
isBroken = false;
|
||||
Vector2 sectionPos = w.SectionPosition(i);
|
||||
Vector2 sectionPos = w.SectionPosition(i, world: true);
|
||||
attackWorldPos = sectionPos;
|
||||
if (w.Submarine != null)
|
||||
{
|
||||
attackWorldPos += w.Submarine.Position;
|
||||
}
|
||||
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
|
||||
break;
|
||||
}
|
||||
@@ -1027,18 +1038,19 @@ namespace Barotrauma
|
||||
bool pursue = false;
|
||||
if (IsCoolDownRunning)
|
||||
{
|
||||
if (AttackingLimb.attack.CoolDownTimer >= AttackingLimb.attack.CoolDown + AttackingLimb.attack.CurrentRandomCoolDown - AttackingLimb.attack.AfterAttackDelay)
|
||||
var currentAttackLimb = AttackingLimb ?? _previousAttackingLimb;
|
||||
if (currentAttackLimb.attack.CoolDownTimer >= currentAttackLimb.attack.CoolDown + currentAttackLimb.attack.CurrentRandomCoolDown - currentAttackLimb.attack.AfterAttackDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (AttackingLimb.attack.AfterAttack)
|
||||
switch (currentAttackLimb.attack.AfterAttack)
|
||||
{
|
||||
case AIBehaviorAfterAttack.Pursue:
|
||||
case AIBehaviorAfterAttack.PursueIfCanAttack:
|
||||
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
|
||||
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
{
|
||||
canAttack = false;
|
||||
pursue = true;
|
||||
@@ -1051,13 +1063,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
if (currentAttackLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
canAttack = false;
|
||||
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
{
|
||||
// Fall back if cannot attack.
|
||||
UpdateFallBack(attackWorldPos, deltaTime, true);
|
||||
@@ -1068,7 +1080,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
|
||||
var newLimb = GetAttackLimb(attackWorldPos, currentAttackLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
@@ -1077,7 +1089,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
{
|
||||
canAttack = false;
|
||||
pursue = true;
|
||||
@@ -1099,26 +1111,26 @@ namespace Barotrauma
|
||||
break;
|
||||
case AIBehaviorAfterAttack.FallBackUntilCanAttack:
|
||||
case AIBehaviorAfterAttack.FollowThroughUntilCanAttack:
|
||||
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
|
||||
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
if (currentAttackLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
|
||||
var newLimb = GetAttackLimb(attackWorldPos, currentAttackLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
@@ -1127,7 +1139,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1135,13 +1147,13 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Cooldown not yet expired -> steer away from the target
|
||||
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIBehaviorAfterAttack.IdleUntilCanAttack:
|
||||
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
|
||||
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
|
||||
{
|
||||
// No (valid) secondary cooldown defined.
|
||||
UpdateIdle(deltaTime, followLastTarget: false);
|
||||
@@ -1149,7 +1161,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
if (currentAttackLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// Don't allow attacking when the attack target has just changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
@@ -1160,7 +1172,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
|
||||
var newLimb = GetAttackLimb(attackWorldPos, currentAttackLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
// Attack with the new limb
|
||||
@@ -1258,7 +1270,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it (the attack takes some time to perform)
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
|
||||
if (wallTarget != null)
|
||||
{
|
||||
if (wallTarget.Structure.Submarine != null)
|
||||
@@ -1283,9 +1295,14 @@ namespace Barotrauma
|
||||
|
||||
Vector2 CalculateMargin(Vector2 targetVelocity)
|
||||
{
|
||||
if (targetVelocity == Vector2.Zero) { return targetVelocity; }
|
||||
if (targetVelocity == Vector2.Zero) { return Vector2.Zero; }
|
||||
float diff = AttackingLimb.attack.Range - AttackingLimb.attack.DamageRange;
|
||||
if (diff <= 0 || toTarget.LengthSquared() <= MathUtils.Pow2(AttackingLimb.attack.DamageRange)) { return Vector2.Zero; }
|
||||
float dot = Vector2.Dot(Vector2.Normalize(targetVelocity), Vector2.Normalize(Character.AnimController.Collider.LinearVelocity));
|
||||
return ConvertUnits.ToDisplayUnits(targetVelocity) * AttackingLimb.attack.Duration * dot;
|
||||
if (dot <= 0 || !MathUtils.IsValid(dot)) { return Vector2.Zero; }
|
||||
float distanceOffset = diff * AttackingLimb.attack.Duration;
|
||||
// Intentionally omit the unit conversion because we use distanceOffset as a multiplier.
|
||||
return targetVelocity * distanceOffset * dot;
|
||||
}
|
||||
|
||||
// Check that we can reach the target
|
||||
@@ -1649,7 +1666,7 @@ namespace Barotrauma
|
||||
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f);
|
||||
}
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
if (SelectedAiTarget?.Entity is Character || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2))
|
||||
if (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2))
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30);
|
||||
}
|
||||
@@ -1813,9 +1830,8 @@ namespace Barotrauma
|
||||
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!AIParams.HasTag("equal"))
|
||||
{
|
||||
// Equal strength
|
||||
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
|
||||
}
|
||||
}
|
||||
@@ -2152,6 +2168,10 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
else
|
||||
{
|
||||
targetingTag = "equal";
|
||||
}
|
||||
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
|
||||
{
|
||||
if (SelectedAiTarget == aiTarget)
|
||||
@@ -2619,91 +2639,163 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private WallTarget wallTarget;
|
||||
|
||||
private readonly List<(Body, int, Vector2)> wallHits = new List<(Body, int, Vector2)>(3);
|
||||
private void UpdateWallTarget(int requiredHoleCount)
|
||||
{
|
||||
wallTarget = null;
|
||||
if (State == AIState.Flee || State == AIState.Escape) { return; }
|
||||
if (AIParams.CanOpenDoors && HasValidPath(requireNonDirty: true)) { return; }
|
||||
if (SelectedAiTarget == null) { return; }
|
||||
if (SelectedAiTarget.Entity == null) { return; }
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
if (SelectedAiTarget.Entity == null) { return; }
|
||||
wallHits.Clear();
|
||||
Structure wall = null;
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
|
||||
{
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
|
||||
{
|
||||
rayEnd -= Character.Submarine.SimPosition;
|
||||
}
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true, ignoreSensors: CanEnterSubmarine, ignoreDisabledWalls: CanEnterSubmarine);
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null)
|
||||
{
|
||||
if (closestBody.UserData is Structure wall && wall.Submarine != null && (Character.IsBot || wall.Submarine.Info.IsPlayer || wall.Submarine.Info.IsOutpost && TargetOutposts))
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
|
||||
float sectionDamage = wall.SectionDamage(sectionIndex);
|
||||
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
|
||||
{
|
||||
rayEnd -= Character.Submarine.SimPosition;
|
||||
}
|
||||
DoRayCast(rayStart, rayEnd);
|
||||
}
|
||||
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)
|
||||
{
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
rayEnd -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
|
||||
{
|
||||
rayStart -= Character.Submarine.SimPosition;
|
||||
rayEnd -= Character.Submarine.SimPosition;
|
||||
}
|
||||
DoRayCast(rayStart, rayEnd);
|
||||
}
|
||||
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering))
|
||||
{
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = rayStart + Steering * 5;
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
rayEnd -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
|
||||
{
|
||||
rayStart -= Character.Submarine.SimPosition;
|
||||
rayEnd -= Character.Submarine.SimPosition;
|
||||
}
|
||||
DoRayCast(rayStart, rayEnd);
|
||||
}
|
||||
if (wallHits.Any())
|
||||
{
|
||||
Body closestBody = null;
|
||||
float closestDistance = 0;
|
||||
int sectionIndex = -1;
|
||||
Vector2 sectionPos = Vector2.Zero;
|
||||
foreach ((Body body, int index, Vector2 sectionPosition) in wallHits)
|
||||
{
|
||||
float distance = Vector2.DistanceSquared(SimPosition, sectionPosition);
|
||||
if (closestBody == null || closestDistance == 0 || distance < closestDistance)
|
||||
{
|
||||
if (wall.SectionBodyDisabled(i))
|
||||
{
|
||||
if (Character.AnimController.CanEnterSubmarine && CanPassThroughHole(wall, i, requiredHoleCount))
|
||||
{
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore and keep breaking other sections
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (wall.SectionDamage(i) > sectionDamage)
|
||||
{
|
||||
sectionIndex = i;
|
||||
}
|
||||
}
|
||||
Vector2 sectionPos = wall.SectionPosition(sectionIndex);
|
||||
Vector2 attachTargetNormal;
|
||||
if (wall.IsHorizontal)
|
||||
{
|
||||
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y));
|
||||
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
|
||||
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
|
||||
}
|
||||
LatchOntoAI?.SetAttachTarget(wall, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner") || wall.Submarine != null && wall.Submarine == Character.Submarine)
|
||||
{
|
||||
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevents monsters from entering the the tail and the nose pieces.
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
}
|
||||
closestBody = body;
|
||||
closestDistance = distance;
|
||||
wall = closestBody.UserData as Structure;
|
||||
sectionPos = sectionPosition;
|
||||
sectionIndex = index;
|
||||
}
|
||||
}
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null && selectedTargetingParams?.AttackPattern == AttackPattern.Straight)
|
||||
if (closestBody == null || sectionIndex == -1) { return; }
|
||||
Vector2 attachTargetNormal;
|
||||
if (wall.IsHorizontal)
|
||||
{
|
||||
if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine ||
|
||||
closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine)
|
||||
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y));
|
||||
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
|
||||
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
|
||||
}
|
||||
LatchOntoAI?.SetAttachTarget(wall, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
// Cannot reach the target, because it's blocked by a disabled wall or a door
|
||||
// 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.
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blocked by a disabled wall.
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
|
||||
void DoRayCast(Vector2 rayStart, Vector2 rayEnd)
|
||||
{
|
||||
Body hitTarget = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true, ignoreSensors: CanEnterSubmarine, ignoreDisabledWalls: CanEnterSubmarine);
|
||||
if (hitTarget != null && IsValid(hitTarget, out wall))
|
||||
{
|
||||
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
|
||||
if (sectionIndex >= 0)
|
||||
{
|
||||
wallHits.Add((hitTarget, sectionIndex, GetSectionPosition(wall, sectionIndex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 GetSectionPosition(Structure wall, int sectionIndex)
|
||||
{
|
||||
float sectionDamage = wall.SectionDamage(sectionIndex);
|
||||
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
|
||||
{
|
||||
if (wall.SectionBodyDisabled(i))
|
||||
{
|
||||
if (Character.AnimController.CanEnterSubmarine && CanPassThroughHole(wall, i, requiredHoleCount))
|
||||
{
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore and keep breaking other sections
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (wall.SectionDamage(i) > sectionDamage)
|
||||
{
|
||||
sectionIndex = i;
|
||||
}
|
||||
}
|
||||
return wall.SectionPosition(sectionIndex, world: false);
|
||||
}
|
||||
|
||||
bool IsValid(Body hit, out Structure wall)
|
||||
{
|
||||
wall = null;
|
||||
if (Submarine.LastPickedFraction == 1.0f) { return false; }
|
||||
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; }
|
||||
wall = w;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2712,7 +2804,7 @@ namespace Barotrauma
|
||||
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex, requiredHoleCount))
|
||||
{
|
||||
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
|
||||
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
|
||||
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, world: true);
|
||||
return section?.gap != null && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime);
|
||||
}
|
||||
else if (SelectedAiTarget != null)
|
||||
|
||||
@@ -136,8 +136,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public override bool IsMentallyUnstable =>
|
||||
MentalStateManager?.CurrentMentalType != MentalStateManager.MentalType.Normal &&
|
||||
MentalStateManager?.CurrentMentalType != MentalStateManager.MentalType.Confused;
|
||||
MentalStateManager == null ? false :
|
||||
MentalStateManager.CurrentMentalType != MentalStateManager.MentalType.Normal &&
|
||||
MentalStateManager.CurrentMentalType != MentalStateManager.MentalType.Confused;
|
||||
|
||||
public ShipCommandManager ShipCommandManager { get; private set; }
|
||||
|
||||
@@ -740,9 +741,10 @@ namespace Barotrauma
|
||||
suitableContainer = null;
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredItems, positionalReference: containableItem, customPriorityFunction: i =>
|
||||
{
|
||||
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
|
||||
if (i.IsThisOrAnyContainerIgnoredByAI(character)) { return 0; }
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (!container.HasAccess(character)) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containableItem)) { return 0; }
|
||||
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
@@ -1032,11 +1034,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
float cumulativeDamage = GetDamageDoneByAttacker(attacker);
|
||||
if (!Character.IsSecurity && attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null)
|
||||
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null;
|
||||
if (isAccidental)
|
||||
{
|
||||
if (cumulativeDamage > 1)
|
||||
if (!Character.IsSecurity && cumulativeDamage > 1)
|
||||
{
|
||||
// Don't retaliate on damage done by friendly NPC, because we know it's accidental, unless if it's a berserking AI
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
|
||||
}
|
||||
}
|
||||
@@ -1109,7 +1111,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
if (otherCharacter == Character || otherCharacter.IsDead || otherCharacter.IsUnconscious || otherCharacter.Removed) { continue; }
|
||||
if (otherCharacter == Character || otherCharacter.IsUnconscious || otherCharacter.Removed) { continue; }
|
||||
if (otherCharacter.Submarine != Character.Submarine) { continue; }
|
||||
if (otherCharacter.Submarine != attacker.Submarine) { continue; }
|
||||
if (otherCharacter.Info?.Job == null || otherCharacter.IsInstigator) { continue; }
|
||||
@@ -1921,18 +1923,45 @@ namespace Barotrauma
|
||||
|
||||
private static bool FilterCrewMember(Character self, Character other) => other != null && !other.IsDead && !other.Removed && other.AIController is HumanAIController humanAi && humanAi.IsFriendly(self);
|
||||
|
||||
public static bool IsItemOperatedByAnother(Character character, ItemComponent target, out Character operatingCharacter)
|
||||
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
|
||||
{
|
||||
operatingCharacter = null;
|
||||
if (character == null) { return false; }
|
||||
if (target?.Item == null) { return false; }
|
||||
bool isOrder = IsOrderedToOperateThis(character.AIController);
|
||||
foreach (var c in Character.CharacterList)
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c == character) { continue; }
|
||||
if (c.IsDead || c.IsIncapacitated) { continue; }
|
||||
if (!IsFriendly(character, c, onlySameTeam: true)) { continue; }
|
||||
if (c.Removed) { continue; }
|
||||
if (c.TeamID != team) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
bool isOperated = c.SelectedConstruction == target.Item;
|
||||
if (!isOperated)
|
||||
{
|
||||
if (c.AIController is HumanAIController humanAI)
|
||||
{
|
||||
isOperated = humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item);
|
||||
}
|
||||
}
|
||||
operatingCharacter = c;
|
||||
if (isOperated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// There's some duplicate logic in the two methods below, but making them use the same code would require some changes in the target classes so that we could use exactly the same checks.
|
||||
// And even then there would be some differences that could end up being confusing (like the exception for steering).
|
||||
public bool IsItemOperatedByAnother(ItemComponent target, out Character other)
|
||||
{
|
||||
other = null;
|
||||
if (target?.Item == null) { return false; }
|
||||
bool isOrder = IsOrderedToOperateThis(Character.AIController);
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c == Character) { continue; }
|
||||
if (c.Removed) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
other = c;
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (c.SelectedConstruction == target.Item)
|
||||
@@ -1963,7 +1992,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isTargetOrdered && operatingAI.ObjectiveManager.CurrentOrder == operatingAI.ObjectiveManager.CurrentObjective)
|
||||
if (!isTargetOrdered && operatingAI.ObjectiveManager.CurrentOrder != operatingAI.ObjectiveManager.CurrentObjective)
|
||||
{
|
||||
// The other bot is ordered to do something else
|
||||
continue;
|
||||
@@ -1971,12 +2000,12 @@ namespace Barotrauma
|
||||
if (target is Steering)
|
||||
{
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
if (character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"))
|
||||
if (Character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c))
|
||||
else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1985,7 +2014,65 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
return false;
|
||||
bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item;
|
||||
bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item;
|
||||
}
|
||||
|
||||
public bool IsItemRepairedByAnother(Item target, out Character other)
|
||||
{
|
||||
other = null;
|
||||
if (Character == null) { return false; }
|
||||
if (target == null) { return false; }
|
||||
bool isOrder = IsOrderedToRepairThis(Character.AIController as HumanAIController);
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (c == Character) { continue; }
|
||||
if (c.TeamID != Character.TeamID) { continue; }
|
||||
if (c.IsIncapacitated) { continue; }
|
||||
other = c;
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
if (target.Repairables.Any(r => r.CurrentFixer == c))
|
||||
{
|
||||
// If the other character is player, don't try to repair
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is HumanAIController operatingAI)
|
||||
{
|
||||
var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
|
||||
if (repairItemsObjective == null) { continue; }
|
||||
if (repairItemsObjective.SubObjectives.None(o => o is AIObjectiveRepairItem repairObjective && repairObjective.Item == target))
|
||||
{
|
||||
// Not targeting the same item.
|
||||
continue;
|
||||
}
|
||||
bool isTargetOrdered = IsOrderedToRepairThis(operatingAI);
|
||||
if (!isOrder && isTargetOrdered)
|
||||
{
|
||||
// If the other bot is ordered to repair the item, let him do it, unless we are ordered too
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOrder && !isTargetOrdered)
|
||||
{
|
||||
// We are ordered and the target is not -> allow to repair
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isTargetOrdered && operatingAI.ObjectiveManager.CurrentOrder != operatingAI.ObjectiveManager.CurrentObjective)
|
||||
{
|
||||
// The other bot is ordered to do something else
|
||||
continue;
|
||||
}
|
||||
return target.Repairables.Max(r => r.DegreeOfSuccess(Character)) <= target.Repairables.Max(r => r.DegreeOfSuccess(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
bool IsOrderedToRepairThis(HumanAIController ai) => ai.ObjectiveManager.CurrentOrder is AIObjectiveRepairItems repairOrder && repairOrder.PrioritizedItem == target;
|
||||
}
|
||||
|
||||
#region Wrappers
|
||||
@@ -1994,7 +2081,6 @@ namespace Barotrauma
|
||||
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate);
|
||||
public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate);
|
||||
public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
|
||||
public bool IsItemOperatedByAnother(ItemComponent target, out Character operatingCharacter) => IsItemOperatedByAnother(Character, target, out operatingCharacter);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (item.IgnoreByAI)
|
||||
if (item.IgnoreByAI(character))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
|
||||
+3
-2
@@ -82,16 +82,17 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidContainer(Item container, Character character, bool allowUnloading = true) =>
|
||||
allowUnloading &&
|
||||
!container.IgnoreByAI &&
|
||||
!container.IgnoreByAI(character) &&
|
||||
container.IsInteractable(character) &&
|
||||
container.HasTag("allowcleanup") &&
|
||||
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
|
||||
container.GetComponent<ItemContainer>() is ItemContainer itemContainer && itemContainer.HasAccess(character) &&
|
||||
IsItemInsideValidSubmarine(container, character);
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.SpawnedInOutpost) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
|
||||
+4
-4
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI()))
|
||||
if (container == null || (container.Item != null && container.Item.IsThisOrAnyContainerIgnoredByAI(character)))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
@@ -87,11 +87,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && !i.IsThisOrAnyContainerIgnoredByAI();
|
||||
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && !i.IsThisOrAnyContainerIgnoredByAI(character);
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI())
|
||||
if (container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
@@ -147,7 +147,7 @@ namespace Barotrauma
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name,
|
||||
AbortCondition = obj =>
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI() ||
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
|
||||
|
||||
+5
-2
@@ -63,13 +63,16 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id) && !i.IgnoreByAI), recursive: false);
|
||||
Item itemToDecontain =
|
||||
targetItem ??
|
||||
sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id) && !i.IgnoreByAI(character)), recursive: false);
|
||||
|
||||
if (itemToDecontain == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (itemToDecontain.IgnoreByAI)
|
||||
if (itemToDecontain.IgnoreByAI(character))
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
|
||||
+5
-2
@@ -38,10 +38,13 @@ namespace Barotrauma
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(
|
||||
other => other != HumanAIController &&
|
||||
other.Character.IsBot &&
|
||||
other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks &&
|
||||
fixLeaks.SubObjectives.Any(so => so is AIObjectiveFixLeak fixObjective && fixObjective.Leak == Leak)))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
// Don't fix a leak on a wall section set to be ignored
|
||||
if (gap.ConnectedWall != null)
|
||||
{
|
||||
if (gap.ConnectedWall.Sections.Any(s => s.gap == gap && s.IgnoreByAI)) { return false; }
|
||||
if (gap.ConnectedWall.Sections.Any(s => s.gap == gap && s.IgnoreByAI(character))) { return false; }
|
||||
if (gap.ConnectedWall.MaxHealth <= 0.0f) { return false; }
|
||||
}
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
|
||||
+2
-1
@@ -305,6 +305,7 @@ namespace Barotrauma
|
||||
if (rootInventoryOwner is Item ownerItem)
|
||||
{
|
||||
if (!ownerItem.IsInteractable(character)) { continue; }
|
||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||
}
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
|
||||
@@ -403,7 +404,7 @@ namespace Barotrauma
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.IsThisOrAnyContainerIgnoredByAI()) { return false; }
|
||||
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ namespace Barotrauma
|
||||
targetItem.CurrentHull.FireSources.Any() ||
|
||||
HumanAIController.IsItemOperatedByAnother(target, out _) ||
|
||||
Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))
|
||||
|| component.Item.IgnoreByAI || (useController && controller.Item.IgnoreByAI))
|
||||
|| component.Item.IgnoreByAI(character) || useController && controller.Item.IgnoreByAI(character))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.IgnoreByAI) { return false; }
|
||||
if (pump.Item.IgnoreByAI(character)) { return false; }
|
||||
if (!pump.Item.IsInteractable(character)) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed || Item.IgnoreByAI)
|
||||
if (!IsAllowed || Item.IgnoreByAI(character))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
@@ -44,10 +44,10 @@ namespace Barotrauma
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
// Ignore items that are being repaired by someone else.
|
||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
|
||||
if (HumanAIController.IsItemRepairedByAnother(Item, out _))
|
||||
{
|
||||
Priority = 0;
|
||||
IsCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+5
-5
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public string RelevantSkill;
|
||||
|
||||
private readonly Item prioritizedItem;
|
||||
public Item PrioritizedItem { get; private set; }
|
||||
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
@@ -28,7 +28,7 @@ namespace Barotrauma
|
||||
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.prioritizedItem = prioritizedItem;
|
||||
PrioritizedItem = prioritizedItem;
|
||||
}
|
||||
|
||||
protected override void CreateObjectives()
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier.Equals(RelevantSkill, StringComparison.OrdinalIgnoreCase)))) { return false; }
|
||||
}
|
||||
return true;
|
||||
return !HumanAIController.IsItemRepairedByAnother(item, out _);
|
||||
}
|
||||
|
||||
public static bool ViableForRepair(Item item, Character character, HumanAIController humanAIController)
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
=> new AIObjectiveRepairItem(character, item, objectiveManager, priorityModifier: PriorityModifier, isPriority: item == prioritizedItem);
|
||||
=> new AIObjectiveRepairItem(character, item, objectiveManager, priorityModifier: PriorityModifier, isPriority: item == PrioritizedItem);
|
||||
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveRepairItems, Item>(character, target);
|
||||
@@ -147,7 +147,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
|
||||
@@ -495,27 +495,19 @@ namespace Barotrauma
|
||||
if (submarine == null) { return matchingItems; }
|
||||
if (ItemComponentType != null || TargetItems.Length > 0)
|
||||
{
|
||||
matchingItems = TargetItems.Length > 0 ?
|
||||
Item.ItemList.FindAll(it => TargetItems.Contains(it.Prefab.Identifier) || it.HasTag(TargetItems)) :
|
||||
Item.ItemList.FindAll(it => TryGetTargetItemComponent(it, out _));
|
||||
if (mustBelongToPlayerSub)
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
matchingItems.RemoveAll(it => it.Submarine?.Info != null && it.Submarine.Info.Type != SubmarineType.Player);
|
||||
}
|
||||
matchingItems.RemoveAll(it => it.Submarine != submarine && !submarine.DockedTo.Contains(it.Submarine));
|
||||
if (requiredTeam.HasValue)
|
||||
{
|
||||
matchingItems.RemoveAll(it => it.Submarine == null || it.Submarine.TeamID != requiredTeam.Value);
|
||||
}
|
||||
matchingItems.RemoveAll(it => it.NonInteractable);
|
||||
if (UseController)
|
||||
{
|
||||
matchingItems.RemoveAll(i => i.Components.None(c => c.GetType() == ItemComponentType) && !i.TryFindController(out _));
|
||||
}
|
||||
if (interactableFor != null)
|
||||
{
|
||||
matchingItems.RemoveAll(it => !it.IsInteractable(interactableFor) ||
|
||||
(UseController && it.FindController() is Controller c && !c.Item.IsInteractable(interactableFor)));
|
||||
if (TargetItems.Length > 0 && !TargetItems.Contains(item.Prefab.Identifier) && !item.HasTag(TargetItems)) { continue; }
|
||||
if (TargetItems.Length == 0 && !TryGetTargetItemComponent(item, out _)) { continue; }
|
||||
if (mustBelongToPlayerSub && item.Submarine?.Info != null && item.Submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (item.Submarine != submarine && !submarine.DockedTo.Contains(item.Submarine)) { continue; }
|
||||
if (requiredTeam.HasValue && (item.Submarine == null || item.Submarine.TeamID != requiredTeam.Value)) { continue; }
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; }
|
||||
Controller controller = null;
|
||||
if (UseController && !item.TryFindController(out controller)) { continue; }
|
||||
if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; }
|
||||
matchingItems.Add(item);
|
||||
}
|
||||
}
|
||||
return matchingItems;
|
||||
|
||||
-2
@@ -24,8 +24,6 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TargetItem.IgnoreByAI) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -270,14 +270,16 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
LimbJoint rightWrist = GetJointBetweenLimbs(LimbType.RightForearm, LimbType.RightHand);
|
||||
if (rightWrist != null)
|
||||
{
|
||||
forearmLength = Vector2.Distance(
|
||||
rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB,
|
||||
rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
|
||||
|
||||
forearmLength = Vector2.Distance(
|
||||
rightElbow.LimbA.type == LimbType.RightForearm ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB,
|
||||
rightWrist.LimbA.type == LimbType.RightForearm ? rightWrist.LocalAnchorA : rightWrist.LocalAnchorB);
|
||||
|
||||
forearmLength += Vector2.Distance(
|
||||
rightHand.PullJointLocalAnchorA,
|
||||
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
|
||||
forearmLength += Vector2.Distance(
|
||||
rightHand.PullJointLocalAnchorA,
|
||||
rightElbow.LimbA.type == LimbType.RightHand ? rightElbow.LocalAnchorA : rightElbow.LocalAnchorB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ namespace Barotrauma
|
||||
|
||||
protected Key[] keys;
|
||||
|
||||
public HumanPrefab Prefab;
|
||||
|
||||
private CharacterTeamType teamID;
|
||||
public CharacterTeamType TeamID
|
||||
{
|
||||
@@ -1365,11 +1367,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
private List<Item> wearableItems = new List<Item>();
|
||||
|
||||
public float GetSkillLevel(string skillIdentifier)
|
||||
{
|
||||
if (Info?.Job == null) { return 0.0f; }
|
||||
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
|
||||
|
||||
if (skillIdentifier != null)
|
||||
{
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
{
|
||||
if (Inventory.SlotTypes[i] != InvSlotType.Any && Inventory.GetItemAt(i)?.GetComponent<Wearable>() is Wearable wearable)
|
||||
{
|
||||
if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue))
|
||||
{
|
||||
skillLevel += skillValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
|
||||
{
|
||||
skillLevel *= affliction.GetSkillMultiplier();
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ namespace Barotrauma
|
||||
else if (Strength < ActiveThreshold)
|
||||
{
|
||||
DeactivateHusk();
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: false })
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
|
||||
{
|
||||
character.SpeechImpediment = 100;
|
||||
}
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
character.NeedsAir = false;
|
||||
}
|
||||
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: false })
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
|
||||
{
|
||||
character.SpeechImpediment = 100;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,20 @@ namespace Barotrauma
|
||||
private string rawAfflictionTypeString;
|
||||
private string[] parsedAfflictionIdentifiers;
|
||||
private string[] parsedAfflictionTypes;
|
||||
public string[] ParsedAfflictionIdentifiers
|
||||
{
|
||||
get
|
||||
{
|
||||
return parsedAfflictionIdentifiers;
|
||||
}
|
||||
}
|
||||
public string[] ParsedAfflictionTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return parsedAfflictionTypes;
|
||||
}
|
||||
}
|
||||
|
||||
public DamageModifier(XElement element, string parentDebugName)
|
||||
{
|
||||
|
||||
@@ -167,9 +167,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterInfo GetCharacterInfo()
|
||||
public CharacterInfo GetCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
{
|
||||
var characterElement = ToolBox.SelectWeightedRandom(CustomNPCSets.Keys.ToList(), CustomNPCSets.Values.ToList(), Rand.RandSync.Unsynced);
|
||||
var characterElement = ToolBox.SelectWeightedRandom(CustomNPCSets.Keys.ToList(), CustomNPCSets.Values.ToList(), randSync);
|
||||
return characterElement != null ? new CharacterInfo(characterElement) : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -526,7 +526,7 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "Does the character attack when provoked? When enabled, overrides the predefined targeting state with Attack and increases the priority of it."), Editable()]
|
||||
public bool AttackWhenProvoked { get; private set; }
|
||||
|
||||
[Serialize(true, true, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable]
|
||||
[Serialize(false, true, description: "The character will flee for a brief moment when being shot at if not performing an attack."), Editable]
|
||||
public bool AvoidGunfire { get; private set; }
|
||||
|
||||
[Serialize(3f, true, description: "How long the creature avoids gunfire. Also used when the creature is unlatched."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
@@ -565,6 +565,9 @@ namespace Barotrauma
|
||||
[Serialize(0f, true, description: ""), Editable]
|
||||
public float AggressionCumulation { get; private set; }
|
||||
|
||||
[Serialize(WallTargetingMethod.Target, true, description: ""), Editable]
|
||||
public WallTargetingMethod WallTargetingMethod { get; private set; }
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
protected readonly List<TargetParams> targets = new List<TargetParams>();
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace Barotrauma
|
||||
UpdaterUtil.SaveFileList("filelist.xml");
|
||||
}));
|
||||
|
||||
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
|
||||
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team (0-3)]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
|
||||
() =>
|
||||
{
|
||||
List<string> characterFiles = GameMain.Instance.GetFilesOfType(ContentType.Character).Select(f => f.Path).ToList();
|
||||
@@ -1904,9 +1904,19 @@ namespace Barotrauma
|
||||
spawnPoint = WayPoint.GetRandom(human ? SpawnType.Human : SpawnType.Enemy);
|
||||
}
|
||||
|
||||
CharacterTeamType teamType;
|
||||
teamType = args.Length > 2 ? (CharacterTeamType)int.Parse(args[2]) : Character.Controlled != null ? Character.Controlled.TeamID : CharacterTeamType.Team1;
|
||||
if (string.IsNullOrWhiteSpace(args[0])) { return; }
|
||||
CharacterTeamType teamType = Character.Controlled != null ? Character.Controlled.TeamID : CharacterTeamType.Team1;
|
||||
if (args.Length > 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
teamType = (CharacterTeamType)int.Parse(args[2]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError($"\"{args[2]}\" is not a valid team id.");
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnPoint != null) { spawnPosition = spawnPoint.WorldPosition; }
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (speaker == null) { return; }
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.ActiveConversation = this;
|
||||
speaker.ActiveConversation = null;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
|
||||
@@ -48,9 +48,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
foreach (var objective in humanAiController.ObjectiveManager.Objectives)
|
||||
{
|
||||
if (goToObjective.Target == target)
|
||||
if (objective is AIObjectiveGoTo goToObjective && goToObjective.Target == target)
|
||||
{
|
||||
goToObjective.Abandon = true;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Barotrauma
|
||||
bool hasValidTargets = false;
|
||||
foreach (Entity target in targets)
|
||||
{
|
||||
if (target is Character character && character.Inventory != null)
|
||||
if (target is Character character && character.Inventory != null || target is Item)
|
||||
{
|
||||
hasValidTargets = true;
|
||||
break;
|
||||
@@ -55,20 +55,31 @@ namespace Barotrauma
|
||||
}
|
||||
if (!hasValidTargets) { return; }
|
||||
|
||||
List<Item> usedItems = new List<Item>();
|
||||
HashSet<Item> removedItems = new HashSet<Item>();
|
||||
foreach (Entity target in targets)
|
||||
{
|
||||
Inventory inventory = (target as Character)?.Inventory;
|
||||
if (inventory == null) { continue; }
|
||||
while (usedItems.Count < Amount)
|
||||
if (inventory != null)
|
||||
{
|
||||
var item = inventory.FindItem(it =>
|
||||
it != null &&
|
||||
!usedItems.Contains(it) &&
|
||||
it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase), recursive: true);
|
||||
if (item == null) { break; }
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
usedItems.Add(item);
|
||||
while (removedItems.Count < Amount)
|
||||
{
|
||||
var item = inventory.FindItem(it =>
|
||||
it != null &&
|
||||
!removedItems.Contains(it) &&
|
||||
it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase), recursive: true);
|
||||
if (item == null) { break; }
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
removedItems.Add(item);
|
||||
}
|
||||
}
|
||||
else if (target is Item item)
|
||||
{
|
||||
if (item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
removedItems.Add(item);
|
||||
if (removedItems.Count >= Amount) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
|
||||
@@ -107,27 +107,32 @@ namespace Barotrauma
|
||||
if (!string.IsNullOrEmpty(NPCSetIdentifier) && !string.IsNullOrEmpty(NPCIdentifier))
|
||||
{
|
||||
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
if (humanPrefab != null)
|
||||
{
|
||||
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.Prefab = humanPrefab;
|
||||
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
item.SpawnedInOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
{
|
||||
item.SpawnedInOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(SpeciesName))
|
||||
{
|
||||
@@ -197,8 +202,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
spawned = true;
|
||||
|
||||
spawned = true;
|
||||
}
|
||||
|
||||
public static Vector2 OffsetSpawnPos(Vector2 pos, float offsetAmount)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Barotrauma
|
||||
{
|
||||
class TagAction : EventAction
|
||||
{
|
||||
public enum SubType { Any= 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
|
||||
public enum SubType { Any = 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Criteria { get; set; }
|
||||
@@ -67,6 +67,16 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private void TagHumansByIdentifier(string identifier)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Prefab?.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
{
|
||||
ParentEvent.AddTarget(Tag, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void TagStructuresByIdentifier(string identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
@@ -122,6 +132,9 @@ namespace Barotrauma
|
||||
case "crew":
|
||||
TagCrew();
|
||||
break;
|
||||
case "humanprefabidentifier":
|
||||
if (kvp.Length > 1) { TagHumansByIdentifier(kvp[1].Trim()); }
|
||||
break;
|
||||
case "structureidentifier":
|
||||
if (kvp.Length > 1) { TagStructuresByIdentifier(kvp[1].Trim()); }
|
||||
break;
|
||||
|
||||
@@ -122,6 +122,7 @@ namespace Barotrauma
|
||||
{
|
||||
npcOrItem = npc;
|
||||
npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
|
||||
npc.RequireConsciousnessForCustomInteract = false;
|
||||
#if CLIENT
|
||||
npc.SetCustomInteract(
|
||||
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
|
||||
@@ -132,7 +133,6 @@ namespace Barotrauma
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
#endif
|
||||
npc.RequireConsciousnessForCustomInteract = false;
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -176,6 +176,9 @@ namespace Barotrauma
|
||||
npc.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
npc.SetCustomInteract(null, null);
|
||||
npc.RequireConsciousnessForCustomInteract = true;
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
#endif
|
||||
}
|
||||
else if (npcOrItem.TryGet(out Item item))
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Barotrauma
|
||||
if (eventSet == null) { return; }
|
||||
if (eventSet.OncePerOutpost)
|
||||
{
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First))
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.prefab))
|
||||
{
|
||||
if (!level.LevelData.NonRepeatableEvents.Contains(ep))
|
||||
{
|
||||
@@ -374,11 +374,11 @@ namespace Barotrauma
|
||||
preloadedSprites.Clear();
|
||||
}
|
||||
|
||||
private float CalculateCommonness(Pair<EventPrefab, float> eventPrefab)
|
||||
private float CalculateCommonness(EventPrefab eventPrefab, float baseCommonness)
|
||||
{
|
||||
if (level.LevelData.NonRepeatableEvents.Contains(eventPrefab.First)) { return 0.0f; }
|
||||
float retVal = eventPrefab.Second;
|
||||
if (level.LevelData.EventHistory.Contains(eventPrefab.First)) { retVal *= 0.1f; }
|
||||
if (level.LevelData.NonRepeatableEvents.Contains(eventPrefab)) { return 0.0f; }
|
||||
float retVal = baseCommonness;
|
||||
if (level.LevelData.EventHistory.Contains(eventPrefab)) { retVal *= 0.1f; }
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -420,8 +420,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e =>
|
||||
string.IsNullOrEmpty(e.First.BiomeIdentifier) ||
|
||||
e.First.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
string.IsNullOrEmpty(e.prefab.BiomeIdentifier) ||
|
||||
e.prefab.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
@@ -429,14 +429,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (suitablePrefabs.Count > 0)
|
||||
{
|
||||
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(suitablePrefabs);
|
||||
var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(suitablePrefabs);
|
||||
for (int j = 0; j < eventSet.EventCount; j++)
|
||||
{
|
||||
if (unusedEvents.All(e => CalculateCommonness(e) <= 0.0f)) { break; }
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e)).ToList(), rand);
|
||||
if (eventPrefab != null)
|
||||
if (unusedEvents.All(e => CalculateCommonness(e.prefab, e.commonness) <= 0.0f)) { break; }
|
||||
(EventPrefab eventPrefab, float commonness, float probability) = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e.prefab, e.commonness)).ToList(), rand);
|
||||
if (eventPrefab != null && rand.NextDouble() <= probability)
|
||||
{
|
||||
var newEvent = eventPrefab.First.CreateInstance();
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||
@@ -450,7 +450,7 @@ namespace Barotrauma
|
||||
selectedEvents.Add(eventSet, new List<Event>());
|
||||
}
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
unusedEvents.Remove(eventPrefab);
|
||||
unusedEvents.Remove((eventPrefab, commonness, probability));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,9 +465,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Pair<EventPrefab, float> eventPrefab in suitablePrefabs)
|
||||
foreach ((EventPrefab eventPrefab, float commonness, float probability) in suitablePrefabs)
|
||||
{
|
||||
var newEvent = eventPrefab.First.CreateInstance();
|
||||
if (rand.NextDouble() > probability) { continue; }
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
#if DEBUG
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly XElement ConfigElement;
|
||||
public readonly Type EventType;
|
||||
public readonly float SpawnProbability;
|
||||
public readonly float Probability;
|
||||
public readonly bool TriggerEventCooldown;
|
||||
public float Commonness;
|
||||
public string Identifier;
|
||||
@@ -39,7 +39,7 @@ namespace Barotrauma
|
||||
Identifier = ConfigElement.GetAttributeString("identifier", string.Empty);
|
||||
BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty);
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1);
|
||||
Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
|
||||
UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false);
|
||||
|
||||
@@ -48,10 +48,10 @@ namespace Barotrauma
|
||||
List<EventPrefab> eventPrefabs = new List<EventPrefab>(PrefabList);
|
||||
foreach (var eventSet in List)
|
||||
{
|
||||
eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.First));
|
||||
eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.prefab));
|
||||
foreach (var childSet in eventSet.ChildSets)
|
||||
{
|
||||
eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.First));
|
||||
eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.prefab));
|
||||
}
|
||||
}
|
||||
return eventPrefabs;
|
||||
@@ -96,8 +96,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly Dictionary<string, float> Commonness;
|
||||
|
||||
//Pair.First: event prefab, Pair.Second: commonness
|
||||
public readonly List<Pair<EventPrefab, float>> EventPrefabs;
|
||||
public readonly List<(EventPrefab prefab, float commonness, float probability)> EventPrefabs;
|
||||
|
||||
public readonly List<EventSet> ChildSets;
|
||||
|
||||
@@ -111,7 +110,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier;
|
||||
Commonness = new Dictionary<string, float>();
|
||||
EventPrefabs = new List<Pair<EventPrefab, float>>();
|
||||
EventPrefabs = new List<(EventPrefab prefab, float commonness, float probability)>();
|
||||
ChildSets = new List<EventSet>();
|
||||
|
||||
BiomeIdentifier = element.GetAttributeString("biome", string.Empty);
|
||||
@@ -184,13 +183,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness);
|
||||
EventPrefabs.Add(new Pair<EventPrefab, float>( prefab, commonness));
|
||||
float probability = subElement.GetAttributeFloat("probability", prefab.Probability);
|
||||
EventPrefabs.Add((prefab, commonness, probability));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefab = new EventPrefab(subElement);
|
||||
EventPrefabs.Add(new Pair<EventPrefab, float>(prefab, prefab.Commonness));
|
||||
EventPrefabs.Add((prefab, prefab.Commonness, prefab.Probability));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -342,13 +342,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (thisSet.ChooseRandom)
|
||||
{
|
||||
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(thisSet.EventPrefabs);
|
||||
var unusedEvents = new List<(EventPrefab prefab, float commonness, float probability)>(thisSet.EventPrefabs);
|
||||
for (int i = 0; i < thisSet.EventCount; i++)
|
||||
{
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Second).ToList(), Rand.RandSync.Unsynced);
|
||||
if (eventPrefab != null)
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
if (eventPrefab.prefab != null)
|
||||
{
|
||||
AddEvent(stats, eventPrefab.First);
|
||||
AddEvent(stats, eventPrefab.prefab);
|
||||
unusedEvents.Remove(eventPrefab);
|
||||
}
|
||||
}
|
||||
@@ -357,7 +357,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var eventPrefab in thisSet.EventPrefabs)
|
||||
{
|
||||
AddEvent(stats, eventPrefab.First);
|
||||
AddEvent(stats, eventPrefab.prefab);
|
||||
}
|
||||
}
|
||||
foreach (var childSet in thisSet.ChildSets)
|
||||
|
||||
@@ -24,6 +24,19 @@ namespace Barotrauma
|
||||
|
||||
private Submarine sub;
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Submarine.MainSub != sub)
|
||||
{
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(Submarine.MainSub))}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public CargoMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
|
||||
@@ -14,12 +14,15 @@ namespace Barotrauma
|
||||
private readonly XElement itemConfig;
|
||||
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterDictionary = new Dictionary<Character, List<Item>>();
|
||||
private readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
|
||||
|
||||
private readonly int baseEscortedCharacters;
|
||||
private readonly float scalingEscortedCharacters;
|
||||
private readonly float terroristChance;
|
||||
|
||||
private int calculatedReward;
|
||||
private Submarine missionSub;
|
||||
|
||||
private Character vipCharacter;
|
||||
|
||||
private readonly List<Character> terroristCharacters = new List<Character>();
|
||||
@@ -30,24 +33,43 @@ namespace Barotrauma
|
||||
public EscortMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
missionSub = sub;
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
// Should reflect different escortables, prisoners, VIPs, passengers (where does this comment refer to?)
|
||||
|
||||
baseEscortedCharacters = prefab.ConfigElement.GetAttributeInt("baseescortedcharacters", 1);
|
||||
scalingEscortedCharacters = prefab.ConfigElement.GetAttributeFloat("scalingescortedcharacters", 0);
|
||||
terroristChance = prefab.ConfigElement.GetAttributeFloat("terroristchance", 0);
|
||||
itemConfig = prefab.ConfigElement.Element("TerroristItems");
|
||||
CalculateReward();
|
||||
}
|
||||
|
||||
private void CalculateReward()
|
||||
{
|
||||
if (missionSub == null)
|
||||
{
|
||||
calculatedReward = Prefab.Reward;
|
||||
return;
|
||||
}
|
||||
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
calculatedReward = Prefab.Reward * multiplier;
|
||||
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(missionSub))}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
}
|
||||
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
return Prefab.Reward * multiplier;
|
||||
if (sub != missionSub)
|
||||
{
|
||||
missionSub = sub;
|
||||
CalculateReward();
|
||||
}
|
||||
return calculatedReward;
|
||||
}
|
||||
|
||||
int CalculateScalingEscortedCharacterCount(bool inMission = false)
|
||||
{
|
||||
if (Submarine.MainSub == null || Submarine.MainSub.Info == null) // UI logic failing to get the correct value is not important, but the mission logic must succeed
|
||||
if (missionSub == null || missionSub.Info == null) // UI logic failing to get the correct value is not important, but the mission logic must succeed
|
||||
{
|
||||
if (inMission)
|
||||
{
|
||||
@@ -55,13 +77,13 @@ namespace Barotrauma
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * (Submarine.MainSub.Info.RecommendedCrewSizeMin + Submarine.MainSub.Info.RecommendedCrewSizeMax) / 2);
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * (missionSub.Info.RecommendedCrewSizeMin + missionSub.Info.RecommendedCrewSizeMax) / 2);
|
||||
}
|
||||
|
||||
private void InitEscort()
|
||||
{
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
characterItems.Clear();
|
||||
// VIP transport mission characters stay in the same location; other characters roam at will
|
||||
// could be replaced with a designated waypoint for VIPs, such as cargo or crew
|
||||
WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
|
||||
@@ -78,7 +100,7 @@ namespace Barotrauma
|
||||
int count = CalculateScalingEscortedCharacterCount(inMission: true);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterDictionary, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
|
||||
if (spawnedCharacter.AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.InitMentalStateManager();
|
||||
@@ -156,6 +178,13 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
// to ensure single missions run without issues, default to mainsub
|
||||
if (missionSub == null)
|
||||
{
|
||||
missionSub = Submarine.MainSub;
|
||||
CalculateReward();
|
||||
}
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
InitEscort();
|
||||
@@ -276,7 +305,7 @@ namespace Barotrauma
|
||||
// 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 characterDictionary)
|
||||
foreach (var characterItem in characterItems)
|
||||
{
|
||||
if (Survived(characterItem.Key) || !completed)
|
||||
{
|
||||
@@ -291,7 +320,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
characterItems.Clear();
|
||||
failed = !completed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,8 +400,6 @@ namespace Barotrauma
|
||||
// putting these here since both escort and pirate missions need them. could be tucked away into another class that they can inherit from (or use composition)
|
||||
protected HumanPrefab CreateHumanPrefabFromElement(XElement element)
|
||||
{
|
||||
HumanPrefab humanPrefab = null;
|
||||
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission \"" + Name + "\" - use character identifiers instead of names to configure the characters.");
|
||||
@@ -411,8 +409,7 @@ namespace Barotrauma
|
||||
|
||||
string characterIdentifier = element.GetAttributeString("identifier", "");
|
||||
string characterFrom = element.GetAttributeString("from", "");
|
||||
humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn character for mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
@@ -428,9 +425,11 @@ namespace Barotrauma
|
||||
{
|
||||
positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
||||
}
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
|
||||
var characterInfo = humanPrefab.GetCharacterInfo(Rand.RandSync.Server) ?? new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
characterInfo.TeamID = teamType;
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
spawnedCharacter.Prefab = humanPrefab;
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||
humanPrefab.GiveItems(spawnedCharacter, submarine, Rand.RandSync.Server, createNetworkEvents: false);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
|
||||
private Submarine enemySub;
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterDictionary = new Dictionary<Character, List<Item>>();
|
||||
private readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
|
||||
|
||||
// Update the last sighting periodically so that the players can find the pirate sub even if they have lost the track of it.
|
||||
private readonly float pirateSightingUpdateFrequency = 30;
|
||||
@@ -103,17 +103,20 @@ namespace Barotrauma
|
||||
|
||||
alternateReward = submarineConfig.GetAttributeInt("alternatereward", Reward);
|
||||
|
||||
string submarineIdentifier = submarineConfig.GetAttributeString("identifier", string.Empty);
|
||||
if (submarineIdentifier == string.Empty)
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", alternateReward)}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
|
||||
string submarinePath = submarineConfig.GetAttributeString("path", string.Empty);
|
||||
if (submarinePath == string.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError("No identifier used for submarine for pirate mission!");
|
||||
DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!");
|
||||
return;
|
||||
}
|
||||
// maybe a little redundant
|
||||
var contentFile = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.EnemySubmarine).FirstOrDefault(x => x.Path == submarineIdentifier);
|
||||
var contentFile = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.EnemySubmarine).FirstOrDefault(x => x.Path == submarinePath);
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No submarine file found with the identifier!");
|
||||
DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,14 +190,13 @@ namespace Barotrauma
|
||||
reactor.PowerUpImmediately();
|
||||
}
|
||||
enemySub.EnableMaintainPosition();
|
||||
enemySub.SetPosition(spawnPos);
|
||||
enemySub.TeamID = CharacterTeamType.None;
|
||||
}
|
||||
|
||||
private void InitPirates()
|
||||
{
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
characterItems.Clear();
|
||||
|
||||
if (characterConfig == null)
|
||||
{
|
||||
@@ -222,13 +224,13 @@ namespace Barotrauma
|
||||
|
||||
if (characterType == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No character types defined in CharacterTypes for a declared type identifier in mission file " + this);
|
||||
DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".");
|
||||
return;
|
||||
}
|
||||
|
||||
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterDictionary, enemySub, CharacterTeamType.None, null);
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
{
|
||||
bool isCommander = variantElement.GetAttributeBool("iscommander", false);
|
||||
@@ -242,6 +244,14 @@ namespace Barotrauma
|
||||
commanderAssigned = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in spawnedCharacter.Inventory.AllItems)
|
||||
{
|
||||
if (item?.Prefab.Identifier == "idcard")
|
||||
{
|
||||
item.AddTag("id_pirate");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,9 +307,10 @@ namespace Barotrauma
|
||||
{
|
||||
InitPirateShip(spawnPos);
|
||||
}
|
||||
enemySub.SetPosition(spawnPos);
|
||||
|
||||
// flipping the sub on the frame it is moved into place must be done after it's been moved, or it breaks item connections to the submarine
|
||||
// creating the pirates have to be done after the sub has been flipped, or it seems to break the AI pathing
|
||||
// flipping the sub on the frame it is moved into place must be done after it's been moved, or it breaks item connections in the submarine
|
||||
// creating the pirates has to be done after the sub has been flipped, or it seems to break the AI pathing
|
||||
enemySub.FlipX();
|
||||
enemySub.ShowSonarMarker = false;
|
||||
|
||||
@@ -375,7 +386,7 @@ namespace Barotrauma
|
||||
completed = true;
|
||||
}
|
||||
characters.Clear();
|
||||
characterDictionary.Clear();
|
||||
characterItems.Clear();
|
||||
failed = !completed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,13 +182,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (disallowed) { return; }
|
||||
|
||||
if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability)
|
||||
{
|
||||
spawnPos = null;
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
spawnPos = Vector2.Zero;
|
||||
var availablePositions = GetAvailableSpawnPositions();
|
||||
var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
|
||||
|
||||
@@ -207,7 +207,8 @@ namespace Barotrauma
|
||||
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
|
||||
AllowStealing = validContainer.Key.Item.AllowStealing,
|
||||
OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
|
||||
OriginalContainerID = validContainer.Key.Item.ID
|
||||
OriginalContainerIndex =
|
||||
Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item)
|
||||
};
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
|
||||
@@ -246,42 +246,21 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
availableContainers.Add(itemContainer);
|
||||
#if SERVER
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (itemContainer == null)
|
||||
{
|
||||
//no container, place at the waypoint
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine, onSpawned: itemSpawned);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemSpawned(item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//place in the container
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory, onSpawned: itemSpawned);
|
||||
}
|
||||
else
|
||||
{
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer.Inventory.TryPutItem(item, null);
|
||||
itemSpawned(item);
|
||||
}
|
||||
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
itemSpawned(item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(item, false);
|
||||
#endif
|
||||
static void itemSpawned(Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
|
||||
@@ -412,16 +412,16 @@ namespace Barotrauma
|
||||
|
||||
public static Character GetCharacterForQuickAssignment(Order order, Character controlledCharacter, IEnumerable<Character> characters, bool includeSelf = false)
|
||||
{
|
||||
var controllingCharacter = controlledCharacter != null;
|
||||
bool isControlledCharacterNull = controlledCharacter == null;
|
||||
#if !DEBUG
|
||||
if (!controllingCharacter) { return null; }
|
||||
if (isControlledCharacterNull) { return null; }
|
||||
#endif
|
||||
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter) &&
|
||||
(!controllingCharacter || operatingCharacter.CanHearCharacter(controlledCharacter)))
|
||||
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemTargetedBySomeone(order.TargetItemComponent, controlledCharacter != null ? controlledCharacter.TeamID : CharacterTeamType.Team1, out Character operatingCharacter) &&
|
||||
(isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter)))
|
||||
{
|
||||
return operatingCharacter;
|
||||
}
|
||||
return GetCharactersSortedForOrder(order, characters, controlledCharacter, includeSelf).FirstOrDefault(c => !controllingCharacter || c.CanHearCharacter(controlledCharacter)) ?? controlledCharacter;
|
||||
return GetCharactersSortedForOrder(order, characters, controlledCharacter, includeSelf).FirstOrDefault(c => isControlledCharacterNull || c.CanHearCharacter(controlledCharacter)) ?? controlledCharacter;
|
||||
}
|
||||
|
||||
public static IEnumerable<Character> GetCharactersSortedForOrder(Order order, IEnumerable<Character> characters, Character controlledCharacter, bool includeSelf, IEnumerable<Character> extraCharacters = null)
|
||||
|
||||
@@ -494,7 +494,7 @@ namespace Barotrauma
|
||||
if (Level.Loaded.StartOutpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = Level.Loaded.StartOutpost.DockedTo.FirstOrDefault();
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle) { return null; }
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != leavingPlayers.FirstOrDefault()?.TeamID) { return null; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
|
||||
@@ -522,7 +522,7 @@ namespace Barotrauma
|
||||
if (Level.Loaded.EndOutpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = Level.Loaded.EndOutpost.DockedTo.FirstOrDefault();
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle) { return null; }
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != leavingPlayers.FirstOrDefault()?.TeamID) { return null; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
|
||||
|
||||
@@ -549,7 +549,10 @@ namespace Barotrauma
|
||||
if (port.IsHorizontal || port.Docked) { continue; }
|
||||
if (port.Item.Submarine == level.StartOutpost)
|
||||
{
|
||||
outPostPort = port;
|
||||
if (port.DockingTarget == null)
|
||||
{
|
||||
outPostPort = port;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (port.Item.Submarine != Submarine) { continue; }
|
||||
|
||||
@@ -323,7 +323,11 @@ namespace Barotrauma
|
||||
Campaign.Money -= price;
|
||||
|
||||
itemToRemove.AvailableSwaps.Add(itemToRemove.Prefab);
|
||||
if (itemToInstall != null) { itemToRemove.AvailableSwaps.Add(itemToInstall); }
|
||||
if (itemToInstall != null && !itemToRemove.AvailableSwaps.Contains(itemToInstall))
|
||||
{
|
||||
itemToRemove.PurchasedNewSwap = true;
|
||||
itemToRemove.AvailableSwaps.Add(itemToInstall);
|
||||
}
|
||||
|
||||
if (itemToRemove.Prefab != itemToInstall && itemToInstall != null)
|
||||
{
|
||||
@@ -424,7 +428,12 @@ namespace Barotrauma
|
||||
|
||||
List<PurchasedUpgrade> pendingUpgrades = PendingUpgrades;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
if (Level.Loaded is { Type: LevelData.LevelType.Outpost })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember is { IsClient: true })
|
||||
{
|
||||
if (loadedUpgrades != null)
|
||||
{
|
||||
@@ -438,7 +447,7 @@ namespace Barotrauma
|
||||
{
|
||||
int newLevel = BuyUpgrade(prefab, category, Submarine.MainSub, level);
|
||||
DebugConsole.Log($" - {category.Identifier}.{prefab.Identifier} lvl. {level}, new: ({newLevel})");
|
||||
SetUpgradeLevel(prefab, category, Math.Clamp(level, 0, prefab.MaxLevel));
|
||||
SetUpgradeLevel(prefab, category, Math.Clamp(GetRealUpgradeLevel(prefab, category) + level, 0, prefab.MaxLevel));
|
||||
}
|
||||
|
||||
PendingUpgrades.Clear();
|
||||
@@ -703,7 +712,7 @@ namespace Barotrauma
|
||||
|
||||
private void LoadPendingUpgrades(XElement? element, bool isSingleplayer = true)
|
||||
{
|
||||
if (element == null || !element.HasElements) { return; }
|
||||
if (!(element is { HasElements: true })) { return; }
|
||||
|
||||
List<PurchasedUpgrade> pendingUpgrades = new List<PurchasedUpgrade>();
|
||||
|
||||
|
||||
@@ -538,16 +538,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (hull.Submarine != subs[i]) continue;
|
||||
if (hull.WorldRect.Y < hullRects[i].Y - hullRects[i].Height) continue;
|
||||
if (hull.WorldRect.Y - hull.WorldRect.Height > hullRects[i].Y) continue;
|
||||
if (hull.Submarine != subs[i]) { continue; }
|
||||
if (hull.WorldRect.Y - 5 < hullRects[i].Y - hullRects[i].Height) { continue; }
|
||||
if (hull.WorldRect.Y - hull.WorldRect.Height + 5 > hullRects[i].Y) { continue; }
|
||||
|
||||
if (i == 0) //left hull
|
||||
{
|
||||
if (hull.WorldPosition.X > hullRects[0].Center.X) { continue; }
|
||||
leftSubRightSide = Math.Max(hull.WorldRect.Right, leftSubRightSide);
|
||||
}
|
||||
else //upper hull
|
||||
{
|
||||
if (hull.WorldPosition.X < hullRects[1].Center.X) { continue; }
|
||||
rightSubLeftSide = Math.Min(hull.WorldRect.X, rightSubLeftSide);
|
||||
}
|
||||
}
|
||||
@@ -591,8 +593,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
int expand = 5;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].X -= expand;
|
||||
hullRects[i].Width += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
@@ -636,16 +641,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (hull.Submarine != subs[i]) continue;
|
||||
if (hull.WorldRect.Right < hullRects[i].X) continue;
|
||||
if (hull.WorldRect.X > hullRects[i].Right) continue;
|
||||
if (hull.Submarine != subs[i]) { continue; }
|
||||
if (hull.WorldRect.Right - 5 < hullRects[i].X) { continue; }
|
||||
if (hull.WorldRect.X + 5 > hullRects[i].Right) { continue; }
|
||||
|
||||
if (i == 0) //lower hull
|
||||
{
|
||||
if (hull.WorldPosition.Y > hullRects[i].Y - hullRects[i].Height / 2) { continue; }
|
||||
lowerSubTop = Math.Max(hull.WorldRect.Y, lowerSubTop);
|
||||
}
|
||||
else //upper hull
|
||||
{
|
||||
if (hull.WorldPosition.Y < hullRects[i].Y - hullRects[i].Height / 2) { continue; }
|
||||
upperSubBottom = Math.Min(hull.WorldRect.Y - hull.WorldRect.Height, upperSubBottom);
|
||||
}
|
||||
}
|
||||
@@ -705,8 +712,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
|
||||
int expand = 5;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].Y += expand;
|
||||
hullRects[i].Height += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
|
||||
@@ -663,7 +663,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
return Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character));
|
||||
return base.HasAccess(character) && Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace Barotrauma.Items.Components
|
||||
item.IsShootable = true;
|
||||
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
|
||||
item.RequireAimToUse = true;
|
||||
characterUsable = true;
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
|
||||
@@ -660,7 +660,7 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public virtual bool HasAccess(Character character)
|
||||
{
|
||||
if (item.IgnoreByAI) { return false; }
|
||||
if (character.IsBot && item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (requiredItems.None()) { return true; }
|
||||
if (character.Inventory != null)
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool alwaysContainedItemsSpawned;
|
||||
|
||||
public ItemInventory Inventory;
|
||||
|
||||
private readonly List<ActiveContainedItem> activeContainedItems = new List<ActiveContainedItem>();
|
||||
@@ -208,6 +210,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
|
||||
{
|
||||
SpawnAlwaysContainedItems();
|
||||
}
|
||||
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
@@ -418,11 +425,13 @@ namespace Barotrauma.Items.Components
|
||||
if (!isEditor && (Entity.Spawner == null || Entity.Spawner.Removed) && GameMain.NetworkMember == null)
|
||||
{
|
||||
var spawnedItem = new Item(prefab, Vector2.Zero, null);
|
||||
Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots, createNetworkEvent: false);
|
||||
Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots, createNetworkEvent: false);
|
||||
alwaysContainedItemsSpawned = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false);
|
||||
IsActive = true;
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false, onSpawned: (Item item) => { alwaysContainedItemsSpawned = true; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,19 +326,24 @@ namespace Barotrauma.Items.Components
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
DebugConsole.Log($"Degree of success: {degreeOfSuccess}");
|
||||
DebugConsole.Log($"Current load: {currentLoad}");
|
||||
DebugConsole.Log($"Max power output: {MaxPowerOutput}");
|
||||
DebugConsole.Log($"Available fuel: {AvailableFuel}");
|
||||
|
||||
float desiredTurbineOutput = MathHelper.Clamp(correctTurbineOutput, 0.0f, 100.0f);
|
||||
DebugConsole.Log($"Turbine output reset: {targetTurbineOutput}, {turbineOutput} -> {desiredTurbineOutput}");
|
||||
targetTurbineOutput = desiredTurbineOutput;
|
||||
turbineOutput = desiredTurbineOutput;
|
||||
|
||||
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
|
||||
DebugConsole.Log($"Fission rate reset: {targetFissionRate}, {fissionRate} -> {desiredFissionRate}");
|
||||
targetFissionRate = desiredFissionRate;
|
||||
fissionRate = desiredFissionRate;
|
||||
|
||||
float desiredTemperature = (optimalTemperature.X + optimalTemperature.Y) / 2.0f;
|
||||
DebugConsole.Log($"Temperature reset: {temperature} -> {desiredTemperature}");
|
||||
temperature = desiredTemperature;
|
||||
|
||||
float desiredFissionRate = GetFissionRateForTargetTemperatureAndTurbineOutput(desiredTemperature, desiredTurbineOutput);
|
||||
DebugConsole.Log($"Fission rate reset: {targetFissionRate}, {fissionRate} -> {desiredFissionRate}");
|
||||
targetFissionRate = desiredFissionRate;
|
||||
fissionRate = desiredFissionRate;
|
||||
}
|
||||
|
||||
loadQueue.Enqueue(currentLoad);
|
||||
@@ -420,6 +425,12 @@ namespace Barotrauma.Items.Components
|
||||
return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f;
|
||||
}
|
||||
|
||||
private float GetFissionRateForTargetTemperatureAndTurbineOutput(float temperature, float turbineOutput)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(AvailableFuel, 0f)) { return 0f; }
|
||||
return (temperature + turbineOutput) / (AvailableFuel / 100f) / 2f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do we need more fuel to generate enough power to match the current load.
|
||||
/// </summary>
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
private bool removePending;
|
||||
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
@@ -274,10 +276,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character != null && !characterUsable) { return false; }
|
||||
|
||||
|
||||
for (int i = 0; i < HitScanCount; i++)
|
||||
{
|
||||
float launchAngle = 0f;
|
||||
float launchAngle;
|
||||
|
||||
if (StaticSpread)
|
||||
{
|
||||
@@ -305,6 +306,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.SetTransform(item.body.SimPosition, launchAngle);
|
||||
float modifiedLaunchImpulse = LaunchImpulse * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
|
||||
DoLaunch(launchDir * modifiedLaunchImpulse * item.body.Mass);
|
||||
}
|
||||
@@ -562,14 +564,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
|
||||
}
|
||||
|
||||
if (!removePending)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
}
|
||||
|
||||
if (item.body != null && item.body.FarseerBody.IsBullet)
|
||||
{
|
||||
if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold)
|
||||
@@ -668,6 +673,10 @@ namespace Barotrauma.Items.Components
|
||||
hits.Add(target.Body);
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
|
||||
IsActive = true;
|
||||
if (RemoveOnHit)
|
||||
{
|
||||
item.body.FarseerBody.ResetDynamics();
|
||||
}
|
||||
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
{
|
||||
Deactivate();
|
||||
@@ -867,15 +876,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (RemoveOnHit)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
//clients aren't allowed to remove items by themselves, so lets hide the projectile until the server tells us to remove it
|
||||
item.HiddenInGame = Hitscan;
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
}
|
||||
removePending = true;
|
||||
item.HiddenInGame = true;
|
||||
item.body.FarseerBody.Enabled = false;
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private string[] labels;
|
||||
[Serialize("", true, description: "The texts displayed on the buttons/tickboxes, separated by commas.")]
|
||||
[Serialize("", true, description: "The texts displayed on the buttons/tickboxes, separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string Labels
|
||||
{
|
||||
get { return string.Join(",", labels); }
|
||||
@@ -111,7 +111,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private string[] signals;
|
||||
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.")]
|
||||
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string Signals
|
||||
{
|
||||
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
|
||||
|
||||
@@ -20,9 +20,10 @@ namespace Barotrauma.Items.Components
|
||||
private bool castShadows;
|
||||
private bool drawBehindSubs;
|
||||
|
||||
|
||||
private double lastToggleSignalTime;
|
||||
|
||||
private string prevColorSignal;
|
||||
|
||||
public PhysicsBody ParentBody;
|
||||
|
||||
private Turret turret;
|
||||
@@ -326,7 +327,11 @@ namespace Barotrauma.Items.Components
|
||||
IsOn = signal.value != "0";
|
||||
break;
|
||||
case "set_color":
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
if (signal.value != prevColorSignal)
|
||||
{
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ namespace Barotrauma.Items.Components
|
||||
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,67 +174,93 @@ namespace Barotrauma.Items.Components
|
||||
float broadRangeY = Math.Max(rangeY * 2, 500);
|
||||
|
||||
if (item.CurrentHull == null && item.Submarine != null && Level.Loaded != null &&
|
||||
(Target == TargetType.Wall || Target == TargetType.Any) &&
|
||||
(Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity))
|
||||
(Target == TargetType.Wall || Target == TargetType.Any))
|
||||
{
|
||||
var cells = Level.Loaded.GetCells(item.WorldPosition, 1);
|
||||
foreach (var cell in cells)
|
||||
if (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity)
|
||||
{
|
||||
if (cell.IsPointInside(item.WorldPosition))
|
||||
var cells = Level.Loaded.GetCells(item.WorldPosition, 1);
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
var closestPoint = MathUtils.GetClosestPointOnLineSegment(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, item.WorldPosition);
|
||||
if (Math.Abs(closestPoint.X - item.WorldPosition.X) < rangeX && Math.Abs(closestPoint.Y - item.WorldPosition.Y) < rangeY)
|
||||
if (cell.IsPointInside(item.WorldPosition))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
Vector2 e1 = edge.Point1 + cell.Translation;
|
||||
Vector2 e2 = edge.Point2 + cell.Translation;
|
||||
if (MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
|
||||
//ignore characters that have spawned a second or less ago
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
switch (Target)
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
case TargetType.Human:
|
||||
if (!c.IsHuman) { continue; }
|
||||
break;
|
||||
case TargetType.Monster:
|
||||
if (c.IsHuman || c.IsPet) { continue; }
|
||||
break;
|
||||
case TargetType.Wall:
|
||||
break;
|
||||
}
|
||||
if (sub == item.Submarine) { continue; }
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Vector2 relativeVelocity = item.Submarine.Velocity - sub.Velocity;
|
||||
if (Math.Abs(relativeVelocity.X) < MinimumVelocity && Math.Abs(relativeVelocity.Y) < MinimumVelocity) { continue; }
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) { continue; }
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
Rectangle worldBorders = new Rectangle(
|
||||
sub.Borders.X + (int)sub.WorldPosition.X,
|
||||
sub.Borders.Y + (int)sub.WorldPosition.Y - sub.Borders.Height,
|
||||
sub.Borders.Width,
|
||||
sub.Borders.Height);
|
||||
|
||||
if (worldBorders.Intersects(detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Target != TargetType.Wall)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
|
||||
//ignore characters that have spawned a second or less ago
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
switch (Target)
|
||||
{
|
||||
case TargetType.Human:
|
||||
if (!c.IsHuman) { continue; }
|
||||
break;
|
||||
case TargetType.Monster:
|
||||
if (c.IsHuman || c.IsPet) { continue; }
|
||||
break;
|
||||
}
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) { continue; }
|
||||
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace Barotrauma.Items.Components
|
||||
partial class Turret : Powered, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private Sprite barrelSprite, railSprite;
|
||||
private List<Tuple<Sprite, Vector2>> chargeSprites = new List<Tuple<Sprite, Vector2>>();
|
||||
private List<Sprite> spinningBarrelSprites = new List<Sprite>();
|
||||
private readonly List<(Sprite sprite, Vector2 position)> chargeSprites = new List<(Sprite sprite, Vector2 position)>();
|
||||
private readonly List<Sprite> spinningBarrelSprites = new List<Sprite>();
|
||||
|
||||
private Vector2 barrelPos;
|
||||
private Vector2 transformedBarrelPos;
|
||||
@@ -290,7 +290,7 @@ namespace Barotrauma.Items.Components
|
||||
railSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "chargesprite":
|
||||
chargeSprites.Add(new Tuple<Sprite, Vector2>(new Sprite(subElement), subElement.GetAttributeVector2("chargetarget", Vector2.Zero)));
|
||||
chargeSprites.Add((new Sprite(subElement), subElement.GetAttributeVector2("chargetarget", Vector2.Zero)));
|
||||
break;
|
||||
case "spinningbarrelsprite":
|
||||
int spriteCount = subElement.GetAttributeInt("spriteamount", 1);
|
||||
@@ -1283,7 +1283,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Vector2 GetRelativeFiringPosition(bool useOffset = true)
|
||||
{
|
||||
// i don't feel great about this method, should be evaluated again
|
||||
Vector2 transformedFiringOffset = Vector2.Zero;
|
||||
if (useOffset)
|
||||
{
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace Barotrauma
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Wearable : Pickable, IServerSerializable
|
||||
partial class Wearable : Pickable, IServerSerializable
|
||||
{
|
||||
private readonly XElement[] wearableElements;
|
||||
private readonly WearableSprite[] wearableSprites;
|
||||
@@ -210,6 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
private readonly Limb[] limb;
|
||||
|
||||
private readonly List<DamageModifier> damageModifiers;
|
||||
public readonly Dictionary<string, float> SkillModifiers;
|
||||
|
||||
public IEnumerable<DamageModifier> DamageModifiers
|
||||
{
|
||||
@@ -265,7 +266,8 @@ namespace Barotrauma.Items.Components
|
||||
this.item = item;
|
||||
|
||||
damageModifiers = new List<DamageModifier>();
|
||||
|
||||
SkillModifiers = new Dictionary<string, float>();
|
||||
|
||||
int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
|
||||
Variants = element.GetAttributeInt("variants", 0);
|
||||
variant = Rand.Range(1, Variants + 1, Rand.RandSync.Server);
|
||||
@@ -308,6 +310,18 @@ namespace Barotrauma.Items.Components
|
||||
case "damagemodifier":
|
||||
damageModifiers.Add(new DamageModifier(subElement, item.Name + ", Wearable"));
|
||||
break;
|
||||
case "skillmodifier":
|
||||
string skillIdentifier = subElement.GetAttributeString("skillidentifier", string.Empty);
|
||||
float skillValue = subElement.GetAttributeFloat("skillvalue", 0f);
|
||||
if (SkillModifiers.ContainsKey(skillIdentifier))
|
||||
{
|
||||
SkillModifiers[skillIdentifier] += skillValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
SkillModifiers.TryAdd(skillIdentifier, skillValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,7 +338,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var wearableSprite = wearableSprites[i];
|
||||
if (!wearableSprite.IsInitialized) { wearableSprite.Init(picker.Info?.Gender ?? Gender.None); }
|
||||
if (picker.Info?.Gender != Gender.None && (wearableSprite.Gender != Gender.None))
|
||||
if (picker.Info != null && picker.Info?.Gender != Gender.None && (wearableSprite.Gender != Gender.None))
|
||||
{
|
||||
// If the item is gender specific (it has a different textures for male and female), we have to change the gender here so that the texture is updated.
|
||||
wearableSprite.Gender = picker.Info.Gender;
|
||||
@@ -386,6 +400,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character == null || character.Removed) { return; }
|
||||
if (picker == null) { return; }
|
||||
|
||||
for (int i = 0; i < wearableSprites.Length; i++)
|
||||
{
|
||||
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
|
||||
|
||||
@@ -204,6 +204,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool PurchasedNewSwap
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks both <see cref="NonInteractable"/> and <see cref="NonPlayerTeamInteractable"/>
|
||||
/// </summary>
|
||||
@@ -705,7 +712,7 @@ namespace Barotrauma
|
||||
get { return allPropertyObjects; }
|
||||
}
|
||||
|
||||
public bool IgnoreByAI => OrderedToBeIgnored || HasTag("ignorebyai");
|
||||
public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam;
|
||||
public bool OrderedToBeIgnored { get; set; }
|
||||
|
||||
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID)
|
||||
@@ -1280,16 +1287,16 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Should this item or any of its containers be ignored by the AI?
|
||||
/// </summary>
|
||||
public bool IsThisOrAnyContainerIgnoredByAI()
|
||||
public bool IsThisOrAnyContainerIgnoredByAI(Character character)
|
||||
{
|
||||
if (IgnoreByAI) { return true; }
|
||||
if (IgnoreByAI(character)) { return true; }
|
||||
if (Container == null) { return false; }
|
||||
if (Container.IgnoreByAI) { return true; }
|
||||
if (Container.IgnoreByAI(character)) { return true; }
|
||||
var container = Container;
|
||||
while (container.Container != null)
|
||||
{
|
||||
container = container.Container;
|
||||
if (container.IgnoreByAI) { return true; }
|
||||
if (container.IgnoreByAI(character)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2779,6 +2786,7 @@ namespace Barotrauma
|
||||
item.SpriteDepth = element.GetAttributeFloat("spritedepth", item.SpriteDepth);
|
||||
item.SpriteColor = element.GetAttributeColor("spritecolor", item.SpriteColor);
|
||||
item.Rotation = element.GetAttributeFloat("rotation", item.Rotation);
|
||||
item.PurchasedNewSwap = element.GetAttributeBool("purchasednewswap", false);
|
||||
|
||||
float scaleRelativeToPrefab = element.GetAttributeFloat(item.scale, "scale", "Scale") / oldPrefab.Scale;
|
||||
item.Scale *= scaleRelativeToPrefab;
|
||||
@@ -2787,16 +2795,41 @@ namespace Barotrauma
|
||||
{
|
||||
Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale");
|
||||
oldRelativeOrigin.Y = -oldRelativeOrigin.Y;
|
||||
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, item.rotationRad);
|
||||
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, -item.rotationRad);
|
||||
Vector2 oldOrigin = centerPos + oldRelativeOrigin;
|
||||
|
||||
Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale;
|
||||
relativeOrigin.Y = -relativeOrigin.Y;
|
||||
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, item.rotationRad);
|
||||
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.rotationRad);
|
||||
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
|
||||
|
||||
item.rect.Location -= (origin - oldOrigin).ToPoint();
|
||||
}
|
||||
|
||||
if (item.PurchasedNewSwap && !string.IsNullOrEmpty(appliedSwap.SwappableItem?.SpawnWithId))
|
||||
{
|
||||
var container = item.GetComponent<ItemContainer>();
|
||||
if (container != null)
|
||||
{
|
||||
container.SpawnWithId = appliedSwap.SwappableItem.SpawnWithId;
|
||||
}
|
||||
/*string[] splitIdentifier = appliedSwap.SwappableItem.SpawnWithId.Split(',');
|
||||
foreach (string id in splitIdentifier)
|
||||
{
|
||||
ItemPrefab itemToSpawn = ItemPrefab.Find(name: null, identifier: id.Trim());
|
||||
if (itemToSpawn == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to spawn an item inside the purchased {item.Name} (could not find an item with the identifier \"{id}\").");
|
||||
}
|
||||
else
|
||||
{
|
||||
var spawnedItem = new Item(itemToSpawn, Vector2.Zero, null);
|
||||
item.OwnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots, createNetworkEvent: false);
|
||||
Spawner?.AddToSpawnQueue(itemToSpawn, item.OwnInventory, spawnIfInventoryFull: false);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
item.PurchasedNewSwap = false;
|
||||
}
|
||||
|
||||
float condition = element.GetAttributeFloat("condition", item.MaxCondition);
|
||||
|
||||
@@ -210,6 +210,10 @@ namespace Barotrauma
|
||||
|
||||
public readonly string ReplacementOnUninstall;
|
||||
|
||||
public string SpawnWithId;
|
||||
|
||||
public string SwapIdentifier;
|
||||
|
||||
public readonly Vector2 SwapOrigin;
|
||||
|
||||
public List<(string requiredTag, string swapTo)> ConnectedItemsToSwap = new List<(string requiredTag, string swapTo)>();
|
||||
@@ -225,9 +229,11 @@ namespace Barotrauma
|
||||
public SwappableItem(XElement element)
|
||||
{
|
||||
BasePrice = Math.Max(element.GetAttributeInt("price", 0), 0);
|
||||
SwapIdentifier = element.GetAttributeString("swapidentifier", string.Empty);
|
||||
CanBeBought = element.GetAttributeBool("canbebought", BasePrice != 0);
|
||||
ReplacementOnUninstall = element.GetAttributeString("replacementonuninstall", "");
|
||||
SwapOrigin = element.GetAttributeVector2("origin", Vector2.One);
|
||||
SpawnWithId = element.GetAttributeString("spawnwithid", string.Empty);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -357,6 +363,14 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
//if true then players can only highlight the item if its targeted for interaction by a campaign event
|
||||
[Serialize(false, false)]
|
||||
public bool RequireCampaignInteract
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//should the camera focus on the item when selected
|
||||
[Serialize(false, false)]
|
||||
public bool FocusOnSelected
|
||||
@@ -730,6 +744,9 @@ namespace Barotrauma
|
||||
//nameidentifier can be used to make multiple items use the same names and descriptions
|
||||
string nameIdentifier = element.GetAttributeString("nameidentifier", "");
|
||||
|
||||
//only used if the item doesn't have a name/description defined in the currently selected language
|
||||
string fallbackNameIdentifier = element.GetAttributeString("fallbacknameidentifier", "");
|
||||
|
||||
//works the same as nameIdentifier, but just replaces the description
|
||||
string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", "");
|
||||
|
||||
@@ -737,11 +754,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameIdentifier))
|
||||
{
|
||||
name = TextManager.Get("EntityName." + identifier, true) ?? string.Empty;
|
||||
name = TextManager.Get("EntityName." + identifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = TextManager.Get("EntityName." + nameIdentifier, true) ?? string.Empty;
|
||||
name = TextManager.Get("EntityName." + nameIdentifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
else if (Category.HasFlag(MapEntityCategory.Legacy))
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Barotrauma
|
||||
|
||||
float displayRange = Attack.Range;
|
||||
|
||||
Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position;
|
||||
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f);
|
||||
#if CLIENT
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Barotrauma
|
||||
|
||||
interface IIgnorable : ISpatialEntity
|
||||
{
|
||||
bool IgnoreByAI { get; }
|
||||
bool IgnoreByAI(Character character);
|
||||
bool OrderedToBeIgnored { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Barotrauma
|
||||
|
||||
public Rectangle Bounds;
|
||||
|
||||
public ItemAssemblyPrefab(string filePath)
|
||||
public ItemAssemblyPrefab(string filePath, bool allowOverwrite = false)
|
||||
{
|
||||
FilePath = filePath;
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
@@ -113,6 +113,10 @@ namespace Barotrauma
|
||||
new Rectangle(0, 0, 1, 1) :
|
||||
new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
|
||||
if (allowOverwrite && Prefabs.ContainsKey(identifier))
|
||||
{
|
||||
Prefabs.Remove(Prefabs[identifier]);
|
||||
}
|
||||
Prefabs.Add(this, doc.Root.IsOverride());
|
||||
}
|
||||
|
||||
|
||||
@@ -1894,26 +1894,45 @@ namespace Barotrauma
|
||||
private void CalculateTunnelDistanceField(int density)
|
||||
{
|
||||
distanceField = new List<(Point point, double distance)>();
|
||||
for (int x = 0; x < Size.X; x += density)
|
||||
|
||||
if (Mirrored)
|
||||
{
|
||||
for (int y = 0; y < Size.Y; y += density)
|
||||
for (int x = Size.X - 1; x >= 0; x -= density)
|
||||
{
|
||||
Point point = new Point(x, y);
|
||||
double shortestDistSqr = double.PositiveInfinity;
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
for (int y = 0; y < Size.Y; y += density)
|
||||
{
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
|
||||
}
|
||||
addPoint(x, y);
|
||||
}
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endExitPosition.X, (double)borders.Bottom));
|
||||
distanceField.Add((point, Math.Sqrt(shortestDistSqr)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int x = 0; x < Size.X; x += density)
|
||||
{
|
||||
for (int y = 0; y < Size.Y; y += density)
|
||||
{
|
||||
addPoint(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addPoint(int x, int y)
|
||||
{
|
||||
Point point = new Point(x, y);
|
||||
double shortestDistSqr = double.PositiveInfinity;
|
||||
foreach (Tunnel tunnel in Tunnels)
|
||||
{
|
||||
for (int i = 1; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.LineSegmentToPointDistanceSquared(tunnel.Nodes[i - 1], tunnel.Nodes[i], point));
|
||||
}
|
||||
}
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startPosition.X, (double)startPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)startExitPosition.X, (double)borders.Bottom));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endPosition.X, (double)endPosition.Y));
|
||||
shortestDistSqr = Math.Min(shortestDistSqr, MathUtils.DistanceSquared((double)point.X, (double)point.Y, (double)endExitPosition.X, (double)borders.Bottom));
|
||||
distanceField.Add((point, Math.Sqrt(shortestDistSqr)));
|
||||
}
|
||||
}
|
||||
|
||||
private double GetDistToTunnel(Vector2 position, Tunnel tunnel)
|
||||
@@ -3554,7 +3573,7 @@ namespace Barotrauma
|
||||
{
|
||||
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
|
||||
}
|
||||
outpost.SetPosition(spawnPos);
|
||||
outpost.SetPosition(spawnPos, forceUndockFromStaticSubmarines: false);
|
||||
if ((i == 0) == !Mirrored)
|
||||
{
|
||||
StartOutpost = outpost;
|
||||
|
||||
@@ -354,7 +354,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition);
|
||||
sub.SetPosition(sub.WorldPosition - Submarine.WorldPosition, forceUndockFromStaticSubmarines: false);
|
||||
sub.Submarine = Submarine;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ namespace Barotrauma
|
||||
public class TakenItem
|
||||
{
|
||||
public readonly ushort OriginalID;
|
||||
public readonly ushort OriginalContainerID;
|
||||
public readonly ushort ModuleIndex;
|
||||
public readonly string Identifier;
|
||||
public readonly int OriginalContainerIndex;
|
||||
|
||||
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerID, ushort moduleIndex)
|
||||
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerIndex, ushort moduleIndex)
|
||||
{
|
||||
OriginalID = originalID;
|
||||
OriginalContainerID = originalContainerID;
|
||||
OriginalContainerIndex = originalContainerIndex;
|
||||
ModuleIndex = moduleIndex;
|
||||
Identifier = identifier;
|
||||
}
|
||||
@@ -29,11 +29,7 @@ namespace Barotrauma
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(item.OriginalModuleIndex >= 0, "Trying to add a non-outpost item to a location's taken items");
|
||||
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
{
|
||||
OriginalContainerID = item.OriginalContainerID;
|
||||
}
|
||||
|
||||
OriginalContainerIndex = item.OriginalContainerIndex;
|
||||
OriginalID = item.ID;
|
||||
ModuleIndex = (ushort) item.OriginalModuleIndex;
|
||||
Identifier = item.prefab.Identifier;
|
||||
@@ -41,14 +37,14 @@ namespace Barotrauma
|
||||
|
||||
public bool IsEqual(TakenItem obj)
|
||||
{
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerIndex == OriginalContainerIndex && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
}
|
||||
|
||||
public bool Matches(Item item)
|
||||
{
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
if (item.OriginalContainerIndex != Entity.NullEntityID)
|
||||
{
|
||||
return item.OriginalContainerID == OriginalContainerID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
return item.OriginalContainerIndex == OriginalContainerIndex && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -184,7 +180,11 @@ namespace Barotrauma
|
||||
private readonly List<Mission> selectedMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> SelectedMissions
|
||||
{
|
||||
get { return selectedMissions; }
|
||||
get
|
||||
{
|
||||
selectedMissions.RemoveAll(m => !availableMissions.Contains(m));
|
||||
return selectedMissions;
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectMission(Mission mission)
|
||||
@@ -345,9 +345,9 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[2], out ushort containerId))
|
||||
if (!ushort.TryParse(takenItemSplit[2], out ushort containerIndex))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken container id \"{takenItemSplit[2]}\"");
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[3], out ushort moduleIndex))
|
||||
@@ -355,7 +355,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item module index \"{takenItemSplit[3]}\"");
|
||||
continue;
|
||||
}
|
||||
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerId, moduleIndex));
|
||||
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerIndex, moduleIndex));
|
||||
}
|
||||
|
||||
killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", new int[0]).ToHashSet();
|
||||
@@ -1153,7 +1153,7 @@ namespace Barotrauma
|
||||
{
|
||||
locationElement.Add(new XAttribute(
|
||||
"takenitems",
|
||||
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerID + ";" + it.ModuleIndex))));
|
||||
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerIndex + ";" + it.ModuleIndex))));
|
||||
}
|
||||
if (killedCharacterIdentifiers.Any())
|
||||
{
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int OriginalModuleIndex = -1;
|
||||
|
||||
public UInt16 OriginalContainerID;
|
||||
public int OriginalContainerIndex = -1;
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
@@ -509,9 +509,9 @@ namespace Barotrauma
|
||||
mapEntityList.Remove(this);
|
||||
|
||||
#if CLIENT
|
||||
if (selectedList.Contains(this))
|
||||
if (SelectedList.Contains(this))
|
||||
{
|
||||
selectedList = selectedList.FindAll(e => e != this);
|
||||
SelectedList = SelectedList.Where(e => e != this).ToHashSet();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -186,7 +186,8 @@ namespace Barotrauma
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine != sub) { continue; }
|
||||
if (hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.IsNullOrEmpty(hull.RoomName) ||
|
||||
hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hull.RoomName = hull.CreateRoomName();
|
||||
}
|
||||
@@ -1436,6 +1437,7 @@ namespace Barotrauma
|
||||
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
npc.AnimController.FindHull(gotoTarget.WorldPosition, true);
|
||||
npc.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
npc.Prefab = humanPrefab;
|
||||
if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier))
|
||||
{
|
||||
outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List<Character>());
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
public Submarine Submarine => Wall.Submarine;
|
||||
public Rectangle WorldRect => Submarine == null ? rect :
|
||||
new Rectangle((int)(rect.X + Submarine.Position.X), (int)(rect.Y + Submarine.Position.Y), rect.Width, rect.Height);
|
||||
public bool IgnoreByAI => OrderedToBeIgnored;
|
||||
public bool IgnoreByAI(Character character) => OrderedToBeIgnored && character.IsOnPlayerTeam;
|
||||
public bool OrderedToBeIgnored { get; set; }
|
||||
|
||||
public WallSection(Rectangle rect, Structure wall, float damage = 0.0f)
|
||||
@@ -872,13 +872,17 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 SectionPosition(int sectionIndex, bool world = false)
|
||||
{
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return Vector2.Zero;
|
||||
if (sectionIndex < 0 || sectionIndex >= Sections.Length)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
if (Prefab.BodyRotation == 0.0f)
|
||||
{
|
||||
Vector2 sectionPos = new Vector2(
|
||||
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
|
||||
Sections[sectionIndex].rect.Y - Sections[sectionIndex].rect.Height / 2.0f);
|
||||
|
||||
if (world && Submarine != null)
|
||||
{
|
||||
sectionPos += Submarine.Position;
|
||||
@@ -897,8 +901,11 @@ namespace Barotrauma
|
||||
{
|
||||
diffFromCenter = ((sectionRect.Y - sectionRect.Height / 2) - (rect.Y - rect.Height / 2)) / (float)rect.Height * BodyHeight;
|
||||
}
|
||||
if (FlippedX) diffFromCenter = -diffFromCenter;
|
||||
|
||||
if (FlippedX)
|
||||
{
|
||||
diffFromCenter = -diffFromCenter;
|
||||
}
|
||||
|
||||
Vector2 sectionPos = Position + new Vector2(
|
||||
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
|
||||
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
|
||||
|
||||
@@ -250,17 +250,21 @@ namespace Barotrauma
|
||||
var parentType = element.Parent?.GetAttributeString("prefabtype", "") ?? string.Empty;
|
||||
|
||||
string nameIdentifier = element.GetAttributeString("nameidentifier", "");
|
||||
|
||||
//only used if the item doesn't have a name/description defined in the currently selected language
|
||||
string fallbackNameIdentifier = element.GetAttributeString("fallbacknameidentifier", "");
|
||||
|
||||
string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", "");
|
||||
|
||||
if (string.IsNullOrEmpty(sp.originalName))
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameIdentifier))
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + sp.identifier, true) ?? string.Empty;
|
||||
sp.name = TextManager.Get("EntityName." + sp.identifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.name = TextManager.Get("EntityName." + nameIdentifier, true) ?? string.Empty;
|
||||
sp.name = TextManager.Get("EntityName." + nameIdentifier, true, "EntityName." + fallbackNameIdentifier) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1174,7 +1174,7 @@ namespace Barotrauma
|
||||
prevPosition = position;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position, List<Submarine> checkd = null)
|
||||
public void SetPosition(Vector2 position, List<Submarine> checkd = null, bool forceUndockFromStaticSubmarines = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(position)) { return; }
|
||||
|
||||
@@ -1188,7 +1188,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Submarine dockedSub in DockedTo)
|
||||
{
|
||||
if (dockedSub.PhysicsBody.BodyType == BodyType.Static)
|
||||
if (dockedSub.PhysicsBody.BodyType == BodyType.Static && forceUndockFromStaticSubmarines)
|
||||
{
|
||||
if (ConnectedDockingPorts.TryGetValue(dockedSub, out DockingPort port))
|
||||
{
|
||||
@@ -1198,7 +1198,7 @@ namespace Barotrauma
|
||||
}
|
||||
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
|
||||
if (expectedLocation == null) { continue; }
|
||||
dockedSub.SetPosition(position + expectedLocation.Value, checkd);
|
||||
dockedSub.SetPosition(position + expectedLocation.Value, checkd, forceUndockFromStaticSubmarines);
|
||||
dockedSub.UpdateTransform(interpolate: false);
|
||||
}
|
||||
}
|
||||
@@ -1610,6 +1610,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (Item itemToSwap in itemsToSwap)
|
||||
{
|
||||
itemToSwap.PurchasedNewSwap = item.PurchasedNewSwap;
|
||||
if (itemPrefab != itemToSwap.Prefab) { itemToSwap.PendingItemSwap = itemPrefab; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,19 +322,30 @@ namespace Barotrauma
|
||||
Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height))));
|
||||
}
|
||||
|
||||
if (Position.X < 0)
|
||||
//hard limit for how far outside the level the sub can go
|
||||
float maxDist = 200000.0f;
|
||||
//the force of the current starts to increase exponentially after this point
|
||||
float exponentialForceIncreaseDist = 150000.0f;
|
||||
float distance = Position.X < 0 ? Math.Abs(Position.X) : Position.X - Level.Loaded.Size.X;
|
||||
if (distance > 0)
|
||||
{
|
||||
float force = Math.Abs(Position.X * 0.5f);
|
||||
totalForce += Vector2.UnitX * force;
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
if (distance > maxDist)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f));
|
||||
if (Position.X < 0)
|
||||
{
|
||||
Body.LinearVelocity = new Vector2(Math.Max(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
Body.LinearVelocity = new Vector2(Math.Min(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float force = (Position.X - Level.Loaded.Size.X) * 0.5f;
|
||||
totalForce -= Vector2.UnitX * force;
|
||||
if (distance > exponentialForceIncreaseDist)
|
||||
{
|
||||
distance += (float)Math.Pow((distance - exponentialForceIncreaseDist) * 0.01f, 2.0f);
|
||||
}
|
||||
float force = distance * 0.5f;
|
||||
totalForce += (Position.X < 0 ? Vector2.UnitX : -Vector2.UnitX) * force;
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(force * 0.0001f, 5.0f));
|
||||
@@ -828,9 +839,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine && Character.Controlled.KnockbackCooldownTimer <= 0.0f)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = impact * 10.0f;
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake);
|
||||
if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player))
|
||||
{
|
||||
float angularVelocity =
|
||||
|
||||
@@ -535,7 +535,6 @@ namespace Barotrauma
|
||||
XDocument doc = new XDocument(newElement);
|
||||
|
||||
doc.Root.Add(new XAttribute("name", Name));
|
||||
|
||||
if (previewImage != null)
|
||||
{
|
||||
doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray())));
|
||||
@@ -692,8 +691,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string TempFolder = Path.Combine("Submarine", "Temp");
|
||||
|
||||
public static XDocument OpenFile(string file)
|
||||
{
|
||||
return OpenFile(file, out _);
|
||||
@@ -723,7 +720,7 @@ namespace Barotrauma
|
||||
|
||||
if (extension == ".sub")
|
||||
{
|
||||
System.IO.Stream stream = null;
|
||||
System.IO.Stream stream;
|
||||
try
|
||||
{
|
||||
stream = SaveUtil.DecompressFiletoStream(file);
|
||||
|
||||
@@ -829,7 +829,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (assignedWayPoints[i] == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a waypoint for " + crew[i].Name + "!");
|
||||
DebugConsole.AddWarning("Couldn't find a waypoint for " + crew[i].Name + "!");
|
||||
assignedWayPoints[i] = WayPointList[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ namespace Barotrauma.Networking
|
||||
if (hull.Submarine != RespawnShuttle) { continue; }
|
||||
hull.OxygenPercentage = 100.0f;
|
||||
hull.WaterVolume = 0.0f;
|
||||
hull.BallastFlora?.Kill();
|
||||
}
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the active prefab with identifier k.
|
||||
/// Returns the active prefab with the identifier.
|
||||
/// </summary>
|
||||
/// <param name="k">Prefab identifier</param>
|
||||
/// <returns>Active prefab with identifier k</returns>
|
||||
public T this[string k]
|
||||
/// <param name="identifier">Prefab identifier</param>
|
||||
/// <returns>Active prefab with the identifier</returns>
|
||||
public T this[string identifier]
|
||||
{
|
||||
get { return prefabs[k].Last(); }
|
||||
get { return prefabs[identifier].Last(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,13 +63,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a prefab with identifier k exists, false otherwise.
|
||||
/// Returns true if a prefab with the identifier exists, false otherwise.
|
||||
/// </summary>
|
||||
/// <param name="k">Prefab identifier</param>
|
||||
/// <returns>Whether a prefab with identifier k exists or not</returns>
|
||||
public bool ContainsKey(string k)
|
||||
/// <param name="identifier">Prefab identifier</param>
|
||||
/// <returns>Whether a prefab with the identifier exists or not</returns>
|
||||
public bool ContainsKey(string identifier)
|
||||
{
|
||||
return prefabs.ContainsKey(k);
|
||||
return prefabs.ContainsKey(identifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,11 +93,10 @@ namespace Barotrauma
|
||||
//Handle bad overrides and duplicates
|
||||
if (basePrefabExists && !isOverride)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error registering \"{prefab.OriginalName}\", \"{prefab.Identifier}\" ({typeof(T).ToString()}): base already exists; try overriding\n{Environment.StackTrace}");
|
||||
DebugConsole.ThrowError($"Failed to add the prefab \"{prefab.OriginalName}\", \"{prefab.Identifier}\" ({typeof(T)}): a prefab with the same identifier already exists; try overriding\n{Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Add to list
|
||||
if (!basePrefabExists)
|
||||
{
|
||||
|
||||
@@ -650,7 +650,7 @@ namespace Barotrauma
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault = false)
|
||||
public static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault = false, bool ignoreEditable = false)
|
||||
{
|
||||
var saveProperties = GetProperties<Serialize>(obj);
|
||||
foreach (var property in saveProperties)
|
||||
@@ -667,7 +667,7 @@ namespace Barotrauma
|
||||
foreach (var attribute in property.Attributes.OfType<Serialize>())
|
||||
{
|
||||
if ((attribute.isSaveable && !attribute.defaultValue.Equals(value)) ||
|
||||
property.Attributes.OfType<Editable>().Any())
|
||||
(!ignoreEditable && property.Attributes.OfType<Editable>().Any()))
|
||||
{
|
||||
save = true;
|
||||
break;
|
||||
|
||||
@@ -748,7 +748,18 @@ namespace Barotrauma
|
||||
{
|
||||
owner = ownerItem.ParentInventory?.Owner;
|
||||
}
|
||||
if (owner is Item container && HasRequiredConditions(container.AllPropertyObjects, pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
if (owner is Item container)
|
||||
{
|
||||
if (pc.Type == PropertyConditional.ConditionType.HasTag)
|
||||
{
|
||||
//if we're checking for tags, just check the Item object, not the ItemComponents
|
||||
if (HasRequiredConditions((container as ISerializableEntity).ToEnumerable(), pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasRequiredConditions(container.AllPropertyObjects, pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
}
|
||||
}
|
||||
if (owner is Character character && HasRequiredConditions(character.ToEnumerable(), pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user