(7ee8dbc11) v0.9.8.0
This commit is contained in:
@@ -25,7 +25,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// How long does it take for the ai target to fade out if not kept alive.
|
||||
/// </summary>
|
||||
public float FadeOutTime { get; private set; } = 1;
|
||||
public float FadeOutTime { get; private set; } = 2;
|
||||
|
||||
public bool Static { get; private set; }
|
||||
public bool StaticSound { get; private set; }
|
||||
@@ -92,7 +92,7 @@ namespace Barotrauma
|
||||
public string SonarLabel;
|
||||
public string SonarIconIdentifier;
|
||||
|
||||
public bool Enabled = true;
|
||||
public bool Enabled => SoundRange > 0 || SightRange > 0;
|
||||
|
||||
public float MinSoundRange, MinSightRange;
|
||||
public float MaxSoundRange = 100000, MaxSightRange = 100000;
|
||||
@@ -195,7 +195,7 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (!Static && FadeOutTime > 0)
|
||||
if (Enabled && !Static && FadeOutTime > 0)
|
||||
{
|
||||
// The aitarget goes silent/invisible if the components don't keep it active
|
||||
if (!StaticSight)
|
||||
|
||||
@@ -89,9 +89,6 @@ namespace Barotrauma
|
||||
private readonly float memoryFadeTime = 0.5f;
|
||||
private readonly float avoidTime = 3;
|
||||
|
||||
//Has the character been attacked since the last Update.
|
||||
private bool wasAttacked;
|
||||
|
||||
private float avoidTimer;
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
@@ -234,13 +231,6 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (wasAttacked)
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
wasAttacked = false;
|
||||
}
|
||||
|
||||
if (DisableEnemyAI) { return; }
|
||||
|
||||
base.Update(deltaTime);
|
||||
@@ -305,7 +295,8 @@ namespace Barotrauma
|
||||
FadeMemories(updateMemoriesInverval);
|
||||
updateMemoriesTimer = updateMemoriesInverval;
|
||||
}
|
||||
if (Character.HealthPercentage <= FleeHealthThreshold)
|
||||
if (Character.HealthPercentage <= FleeHealthThreshold && SelectedAiTarget != null &&
|
||||
SelectedAiTarget.Entity is Character target && (target.IsPlayer || IsBeingChasedBy(target)))
|
||||
{
|
||||
State = AIState.Flee;
|
||||
wallTarget = null;
|
||||
@@ -384,9 +375,9 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
var attackLimb = GetAttackLimb(SelectedAiTarget.WorldPosition);
|
||||
if (attackLimb != null && distance <= Math.Pow(attackLimb.attack.Range, 2))
|
||||
if (attackLimb != null && squaredDistance <= Math.Pow(attackLimb.attack.Range, 2))
|
||||
{
|
||||
run = true;
|
||||
if (State == AIState.Avoid)
|
||||
@@ -402,17 +393,18 @@ namespace Barotrauma
|
||||
{
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (distance <= Math.Pow(reactDistance + escapeMargin, 2))
|
||||
if (squaredDistance <= Math.Pow(reactDistance + escapeMargin, 2))
|
||||
{
|
||||
float halfReactDistance = reactDistance / 2;
|
||||
if (State == AIState.Aggressive || State == AIState.PassiveAggressive && distance < Math.Pow(halfReactDistance, 2))
|
||||
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
|
||||
if (State == AIState.Aggressive || State == AIState.PassiveAggressive && squaredDistance < Math.Pow(attackDistance, 2))
|
||||
{
|
||||
run = true;
|
||||
UpdateAttack(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
run = isBeingChased ? true : distance < Math.Pow(halfReactDistance, 2);
|
||||
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
|
||||
if (escapeMargin <= 0)
|
||||
{
|
||||
escapeMargin = halfReactDistance;
|
||||
@@ -440,13 +432,14 @@ namespace Barotrauma
|
||||
SwarmBehavior.Refresh();
|
||||
SwarmBehavior.UpdateSteering(deltaTime);
|
||||
}
|
||||
float speed = Character.AnimController.GetCurrentSpeed(run);
|
||||
float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
|
||||
steeringManager.Update(speed);
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, State == AIState.Idle && Character.AnimController.InWater ? Steering.Length() : speed);
|
||||
if (Character.CurrentHull != null && Character.AnimController.InWater)
|
||||
{
|
||||
// Halve the swimming speed inside the sub
|
||||
speed /= 2;
|
||||
Character.AnimController.TargetMovement *= 0.5f;
|
||||
}
|
||||
steeringManager.Update(speed);
|
||||
}
|
||||
|
||||
#region Idle
|
||||
@@ -577,7 +570,7 @@ namespace Barotrauma
|
||||
else if (SelectedAiTarget?.Entity is Character targetCharacter && targetCharacter.CurrentHull == Character.CurrentHull)
|
||||
{
|
||||
// Steer away from the target if in the same room
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.GetTargetMovement());
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
}
|
||||
@@ -615,7 +608,7 @@ namespace Barotrauma
|
||||
{
|
||||
escapeTarget = null;
|
||||
allGapsSearched = false;
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.GetTargetMovement());
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
if (Character.CurrentHull == null)
|
||||
@@ -758,6 +751,10 @@ namespace Barotrauma
|
||||
bool pursue = false;
|
||||
if (IsCoolDownRunning)
|
||||
{
|
||||
if (AttackingLimb.attack.CoolDownTimer >= AttackingLimb.attack.CoolDown + AttackingLimb.attack.CurrentRandomCoolDown - AttackingLimb.attack.AfterAttackDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (AttackingLimb.attack.AfterAttack)
|
||||
{
|
||||
case AIBehaviorAfterAttack.Pursue:
|
||||
@@ -1017,58 +1014,63 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 steerPos = attackSimPos + offset;
|
||||
|
||||
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
if (AttackingLimb != null && AttackingLimb.attack.Retreat)
|
||||
{
|
||||
if (pathSteering.CurrentPath != null)
|
||||
UpdateFallBack(attackWorldPos, deltaTime, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 steerPos = attackSimPos + offset;
|
||||
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
// Attack doors
|
||||
if (canAttackSub)
|
||||
if (pathSteering.CurrentPath != null)
|
||||
{
|
||||
// If the target is in the same hull, there shouldn't be any doors blocking the path
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
// Attack doors
|
||||
if (canAttackSub)
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
// If the target is in the same hull, there shouldn't be any doors blocking the path
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
|
||||
return;
|
||||
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Steer towards the target if in the same room and swimming
|
||||
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
|
||||
// Steer towards the target if in the same room and swimming
|
||||
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 2);
|
||||
// Switch to Idle when cannot reach the target and if cannot damage the walls
|
||||
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 2);
|
||||
// Switch to Idle when cannot reach the target and if cannot damage the walls
|
||||
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
SteeringManager.SteeringSeek(steerPos, 5);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 5);
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
|
||||
if (canAttack)
|
||||
{
|
||||
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
@@ -1259,9 +1261,16 @@ namespace Barotrauma
|
||||
float reactionTime = Rand.Range(0.1f, 0.3f);
|
||||
updateTargetsTimer = Math.Min(updateTargetsTimer, reactionTime);
|
||||
|
||||
wasAttacked = true;
|
||||
|
||||
bool wasLatched = IsLatchedOnSub;
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
if (attacker == null || attacker.AiTarget == null) { return; }
|
||||
if (wasLatched)
|
||||
{
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
if (State == AIState.Flee)
|
||||
{
|
||||
@@ -1272,22 +1281,30 @@ namespace Barotrauma
|
||||
if (attackResult.Damage > 0.0f)
|
||||
{
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
|
||||
if (Character.Params.AI.AttackWhenProvoked)
|
||||
if (Character.Params.AI.AttackWhenProvoked && canAttack)
|
||||
{
|
||||
if (canAttack)
|
||||
if (attacker.IsHusk)
|
||||
{
|
||||
ChangeTargetState("husk", AIState.Attack, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeTargetState(attacker, AIState.Attack, 100);
|
||||
}
|
||||
}
|
||||
else if (!AIParams.HasTag(attacker.SpeciesName))
|
||||
{
|
||||
if (attacker.AIController is EnemyAIController enemyAI)
|
||||
if (attacker.IsHusk)
|
||||
{
|
||||
ChangeTargetState("husk", canAttack ? AIState.Attack : AIState.Escape, 100);
|
||||
}
|
||||
else if (attacker.AIController is EnemyAIController enemyAI)
|
||||
{
|
||||
if (enemyAI.CombatStrength > CombatStrength)
|
||||
{
|
||||
if (!AIParams.HasTag("stronger"))
|
||||
{
|
||||
ChangeTargetState(attacker, AIState.Escape, 100);
|
||||
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
|
||||
}
|
||||
}
|
||||
else if (enemyAI.CombatStrength < CombatStrength)
|
||||
@@ -1305,7 +1322,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeTargetState(attacker, AIState.Escape, 100);
|
||||
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1515,7 +1532,8 @@ namespace Barotrauma
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
// In a different sub or the target is outside when we are inside or vice versa.
|
||||
if (State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
|
||||
// Make an exception for humans so that creatures cannot avoid/flee from humans that are inside a sub.
|
||||
if (!targetCharacter.IsHuman && State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
|
||||
{
|
||||
// If we are escaping, let's not ignore the target entirely, because there can be a gaps where we or they can go freely
|
||||
if (targetCharacter.Submarine != null)
|
||||
@@ -1550,29 +1568,36 @@ namespace Barotrauma
|
||||
// Ignore targets that are in the same group (treat them like they were of the same species)
|
||||
continue;
|
||||
}
|
||||
if (enemy.CombatStrength > CombatStrength)
|
||||
if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
{
|
||||
targetingTag = "stronger";
|
||||
targetingTag = "husk";
|
||||
}
|
||||
else if (enemy.CombatStrength < CombatStrength)
|
||||
else
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
|
||||
{
|
||||
if (SelectedAiTarget == aiTarget)
|
||||
if (enemy.CombatStrength > CombatStrength)
|
||||
{
|
||||
// Freightened -> hold on to the target
|
||||
valueModifier *= 2;
|
||||
targetingTag = "stronger";
|
||||
}
|
||||
if (IsBeingChasedBy(targetCharacter))
|
||||
else if (enemy.CombatStrength < CombatStrength)
|
||||
{
|
||||
valueModifier *= 2;
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
|
||||
{
|
||||
// Inside but in a different room
|
||||
valueModifier /= 2;
|
||||
if (SelectedAiTarget == aiTarget)
|
||||
{
|
||||
// Freightened -> hold on to the target
|
||||
valueModifier *= 2;
|
||||
}
|
||||
if (IsBeingChasedBy(targetCharacter))
|
||||
{
|
||||
valueModifier *= 2;
|
||||
}
|
||||
if (Character.CurrentHull != null && !VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
{
|
||||
// Inside but in a different room
|
||||
valueModifier /= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1746,6 +1771,9 @@ namespace Barotrauma
|
||||
|
||||
if (valueModifier > targetValue)
|
||||
{
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
|
||||
newTarget = aiTarget;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = valueModifier;
|
||||
@@ -1867,6 +1895,39 @@ namespace Barotrauma
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
|
||||
private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
|
||||
{
|
||||
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
|
||||
{
|
||||
if (!onlyExisting && !tempParams.ContainsKey(tag))
|
||||
{
|
||||
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
|
||||
{
|
||||
tempParams.Add(tag, targetParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetParams != null)
|
||||
{
|
||||
if (priority.HasValue)
|
||||
{
|
||||
targetParams.Priority = priority.Value;
|
||||
}
|
||||
targetParams.State = state;
|
||||
if (!modifiedParams.ContainsKey(tag))
|
||||
{
|
||||
modifiedParams.Add(tag, targetParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeTargetState(string tag, AIState state, float? priority = null)
|
||||
{
|
||||
isStateChanged = true;
|
||||
SetStateResetTimer();
|
||||
ChangeParams(tag, state, priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporarily changes the predefined state for a target. Eg. Idle -> Attack.
|
||||
/// </summary>
|
||||
@@ -1874,50 +1935,27 @@ namespace Barotrauma
|
||||
{
|
||||
isStateChanged = true;
|
||||
SetStateResetTimer();
|
||||
ChangeParams(target.SpeciesName);
|
||||
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
|
||||
if (state == AIState.Attack || state == AIState.Escape)
|
||||
ChangeParams(target.SpeciesName, state, priority);
|
||||
if (target.IsHuman)
|
||||
{
|
||||
ChangeParams("weapon");
|
||||
ChangeParams("tool");
|
||||
}
|
||||
if (state == AIState.Attack)
|
||||
{
|
||||
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
|
||||
// --> Target the submarine too.
|
||||
if (target.Submarine != null && canAttackSub)
|
||||
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
|
||||
if (state == AIState.Attack || state == AIState.Escape)
|
||||
{
|
||||
ChangeParams("room");
|
||||
ChangeParams("wall");
|
||||
ChangeParams("door");
|
||||
ChangeParams("weapon", state, priority);
|
||||
ChangeParams("tool", state, priority);
|
||||
}
|
||||
ChangeParams("provocative", onlyExisting: true);
|
||||
ChangeParams("light", onlyExisting: true);
|
||||
}
|
||||
|
||||
void ChangeParams(string tag, bool onlyExisting = false)
|
||||
{
|
||||
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
|
||||
if (state == AIState.Attack)
|
||||
{
|
||||
if (!onlyExisting && !tempParams.ContainsKey(tag))
|
||||
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
|
||||
// --> Target the submarine too.
|
||||
if (target.Submarine != null && canAttackSub)
|
||||
{
|
||||
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams))
|
||||
{
|
||||
tempParams.Add(tag, targetParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetParams != null)
|
||||
{
|
||||
if (priority.HasValue)
|
||||
{
|
||||
targetParams.Priority = priority.Value;
|
||||
}
|
||||
targetParams.State = state;
|
||||
if (!modifiedParams.ContainsKey(tag))
|
||||
{
|
||||
modifiedParams.Add(tag, targetParams);
|
||||
ChangeParams("room", state, priority);
|
||||
ChangeParams("wall", state, priority);
|
||||
ChangeParams("door", state, priority);
|
||||
}
|
||||
ChangeParams("provocative", state, priority, onlyExisting: true);
|
||||
ChangeParams("light", state, priority, onlyExisting: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,12 +170,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (run)
|
||||
{
|
||||
run = !AnimController.Crouching && !AnimController.IsMovingBackwards;
|
||||
}
|
||||
float currentSpeed = Character.AnimController.GetCurrentSpeed(run);
|
||||
steeringManager.Update(currentSpeed);
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
|
||||
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
@@ -209,17 +204,6 @@ namespace Barotrauma
|
||||
targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed);
|
||||
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
|
||||
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
|
||||
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
float speedMultiplier = Character.SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
|
||||
if (Character.AnimController.InWater && targetMovement.LengthSquared() < 0.000001f)
|
||||
{
|
||||
bool isAiming = false;
|
||||
@@ -243,7 +227,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.TargetMovement = targetMovement;
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
|
||||
|
||||
flipTimer -= deltaTime;
|
||||
if (flipTimer <= 0.0f)
|
||||
@@ -323,7 +307,7 @@ namespace Barotrauma
|
||||
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|
||||
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
|
||||
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
AIObjectiveGoTo gotoObjective = ObjectiveManager.CurrentOrder as AIObjectiveGoTo;
|
||||
AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
|
||||
if (!removeDivingSuit)
|
||||
{
|
||||
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
|
||||
@@ -536,14 +520,16 @@ namespace Barotrauma
|
||||
Hull targetHull = null;
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
|
||||
bool isFleeing = ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>();
|
||||
foreach (var hull in VisibleHulls)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
foreach (Character target in Character.CharacterList)
|
||||
{
|
||||
if (c.CurrentHull != hull || !c.Enabled) { continue; }
|
||||
if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
|
||||
if (target.CurrentHull != hull || !target.Enabled) { continue; }
|
||||
if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
|
||||
{
|
||||
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c) && newOrder == null)
|
||||
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportintruders");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
@@ -560,42 +546,48 @@ namespace Barotrauma
|
||||
targetHull = hull;
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
if (!isFighting)
|
||||
{
|
||||
if (c.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRescueAll.IsValidTarget(c, Character))
|
||||
foreach (var gap in hull.ConnectedGaps)
|
||||
{
|
||||
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("requestfirstaid");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
targetHull = hull;
|
||||
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbreach");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
targetHull = hull;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var gap in hull.ConnectedGaps)
|
||||
{
|
||||
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
|
||||
if (!isFleeing)
|
||||
{
|
||||
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
|
||||
foreach (Character target in Character.CharacterList)
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbreach");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
targetHull = hull;
|
||||
if (target.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRescueAll.IsValidTarget(target, Character))
|
||||
{
|
||||
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("requestfirstaid");
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
targetHull = hull;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
|
||||
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbrokendevices");
|
||||
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
|
||||
targetHull = hull;
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
|
||||
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbrokendevices");
|
||||
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
|
||||
targetHull = hull;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,7 +642,7 @@ namespace Barotrauma
|
||||
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
|
||||
return;
|
||||
}
|
||||
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
|
||||
if (attacker.IsBot)
|
||||
{
|
||||
// Don't retaliate on damage done by friendly ai, because we know that it's accidental
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
|
||||
@@ -664,9 +656,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float currentVitality = Character.CharacterHealth.Vitality;
|
||||
float dmgPercentage = damage / currentVitality * 100;
|
||||
if (dmgPercentage < currentVitality / 10)
|
||||
float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality);
|
||||
if (dmgPercentage < 10)
|
||||
{
|
||||
// Don't retaliate on minor (accidental) dmg done by characters that are in the same team
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
|
||||
@@ -711,7 +702,6 @@ namespace Barotrauma
|
||||
|
||||
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
|
||||
{
|
||||
SetOrderProjSpecific(order, option);
|
||||
CurrentOrderOption = option;
|
||||
CurrentOrder = order;
|
||||
objectiveManager.SetOrder(order, option, orderGiver);
|
||||
@@ -752,8 +742,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void SetOrderProjSpecific(Order order, string option);
|
||||
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
@@ -806,11 +794,11 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage);
|
||||
|
||||
public static bool HasItem(Character character, string identifier, string containedTag, float conditionPercentage = 0)
|
||||
public static bool HasItem(Character character, string tagOrIdentifier, string containedTag = null, float conditionPercentage = 0)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
if (character.Inventory == null) { return false; }
|
||||
var item = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
|
||||
var item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier);
|
||||
return item != null &&
|
||||
item.ConditionPercentage > conditionPercentage &&
|
||||
character.HasEquippedItem(item) &&
|
||||
@@ -934,7 +922,7 @@ namespace Barotrauma
|
||||
visibleHulls = VisibleHulls;
|
||||
}
|
||||
// TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy.
|
||||
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreFire = objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = HasDivingSuit(character);
|
||||
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
@@ -1022,15 +1010,23 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null)
|
||||
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false)
|
||||
{
|
||||
if (character == null) { return 0; }
|
||||
int count = 0;
|
||||
foreach (var c in Character.CharacterList)
|
||||
foreach (var other in Character.CharacterList)
|
||||
{
|
||||
if (FilterCrewMember(character, c))
|
||||
if (onlyActive && !IsActive(other))
|
||||
{
|
||||
if (predicate == null || predicate(c.AIController as HumanAIController))
|
||||
continue;
|
||||
}
|
||||
if (onlyBots && other.IsPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (FilterCrewMember(character, other))
|
||||
{
|
||||
if (predicate == null || predicate(other.AIController as HumanAIController))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
@@ -1058,7 +1054,7 @@ namespace Barotrauma
|
||||
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
|
||||
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) => CountCrew(Character, predicate);
|
||||
public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null)
|
||||
{
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.NextNode == null || Vector2.DistanceSquared(target, currentTarget) > 1);
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || Vector2.DistanceSquared(target, currentTarget) > 1);
|
||||
//find a new path if one hasn't been found yet or the target is different from the current target
|
||||
if (needsNewPath || findPathTimer < -1.0f)
|
||||
{
|
||||
@@ -308,7 +308,7 @@ namespace Barotrauma
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!IsNextLadderSameAsCurrent)
|
||||
{
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
@@ -530,7 +530,6 @@ namespace Barotrauma
|
||||
if (node.Waypoint != null && node.Waypoint.CurrentHull != null)
|
||||
{
|
||||
var hull = node.Waypoint.CurrentHull;
|
||||
|
||||
if (hull.FireSources.Count > 0)
|
||||
{
|
||||
foreach (FireSource fs in hull.FireSources)
|
||||
@@ -538,9 +537,14 @@ namespace Barotrauma
|
||||
penalty += fs.Size.X * 10.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f) penalty += 500.0f;
|
||||
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume) penalty += 1000.0f;
|
||||
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f)
|
||||
{
|
||||
penalty += 500.0f;
|
||||
}
|
||||
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
|
||||
{
|
||||
penalty += 1000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return penalty;
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace Barotrauma
|
||||
|
||||
jointDir = attachLimb.Dir;
|
||||
|
||||
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.character.AnimController.RagdollParams.LimbScale;
|
||||
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.Scale * attachLimb.Params.Ragdoll.LimbScale;
|
||||
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
||||
|
||||
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
|
||||
|
||||
@@ -32,6 +32,18 @@ namespace Barotrauma
|
||||
public virtual bool UnequipItems => false;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
private float _cumulatedDevotion;
|
||||
protected float CumulatedDevotion
|
||||
{
|
||||
get { return _cumulatedDevotion; }
|
||||
set { _cumulatedDevotion = MathHelper.Clamp(value, 0, MaxDevotion); }
|
||||
}
|
||||
|
||||
protected virtual float MaxDevotion => 10;
|
||||
|
||||
/// <summary>
|
||||
/// Final priority value after all calculations.
|
||||
/// </summary>
|
||||
public float Priority { get; set; }
|
||||
public float PriorityModifier { get; private set; } = 1;
|
||||
public readonly Character character;
|
||||
@@ -59,6 +71,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public virtual bool IsLoop { get; set; }
|
||||
public IEnumerable<AIObjective> SubObjectives => subObjectives;
|
||||
public AIObjective CurrentSubObjective => subObjectives.FirstOrDefault();
|
||||
|
||||
private readonly List<AIObjective> all = new List<AIObjective>();
|
||||
public IEnumerable<AIObjective> GetSubObjectivesRecursive(bool includingSelf = false)
|
||||
@@ -86,7 +99,7 @@ namespace Barotrauma
|
||||
|
||||
public AIObjective GetActiveObjective()
|
||||
{
|
||||
var subObjective = SubObjectives.FirstOrDefault();
|
||||
var subObjective = CurrentSubObjective;
|
||||
return subObjective == null ? this : subObjective.GetActiveObjective();
|
||||
}
|
||||
|
||||
@@ -157,7 +170,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (!AllowSubObjectiveSorting) { return; }
|
||||
if (subObjectives.None()) { return; }
|
||||
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
||||
subObjectives.ForEach(so => so.GetPriority());
|
||||
subObjectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
if (ConcurrentObjectives)
|
||||
{
|
||||
subObjectives.ForEach(so => so.SortSubObjectives());
|
||||
@@ -168,26 +182,38 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual float GetPriority() => Priority * PriorityModifier;
|
||||
|
||||
public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
/// <summary>
|
||||
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
|
||||
/// </summary>
|
||||
public virtual float GetPriority()
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
else if (objectiveManager.WaitTimer <= 0)
|
||||
else
|
||||
{
|
||||
if (objectiveManager.CurrentObjective != null)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
|
||||
{
|
||||
Priority += Devotion * PriorityModifier * deltaTime;
|
||||
}
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
Priority = CumulatedDevotion * PriorityModifier;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
private void UpdateDevotion(float deltaTime)
|
||||
{
|
||||
var currentObjective = objectiveManager.CurrentObjective;
|
||||
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
|
||||
{
|
||||
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsDuplicate<T>(T otherObjective) where T : AIObjective => otherObjective.Option == Option;
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder != this && objectiveManager.WaitTimer <= 0)
|
||||
{
|
||||
UpdateDevotion(deltaTime);
|
||||
}
|
||||
subObjectives.ForEach(so => so.Update(deltaTime));
|
||||
}
|
||||
@@ -264,6 +290,7 @@ namespace Barotrauma
|
||||
|
||||
public virtual void OnDeselected()
|
||||
{
|
||||
CumulatedDevotion = 0;
|
||||
Deselected?.Invoke();
|
||||
}
|
||||
|
||||
@@ -282,6 +309,7 @@ namespace Barotrauma
|
||||
isCompleted = false;
|
||||
hasBeenChecked = false;
|
||||
_abandon = false;
|
||||
CumulatedDevotion = 0;
|
||||
}
|
||||
|
||||
protected abstract void Act(float deltaTime);
|
||||
|
||||
+6
-2
@@ -100,7 +100,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
|
||||
public override float GetPriority()
|
||||
{
|
||||
Priority = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -139,7 +143,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (seekAmmunition == null)
|
||||
{
|
||||
if (TryArm() && Enemy != null && !Enemy.Removed)
|
||||
if (Mode != CombatMode.Retreat && TryArm() && Enemy != null && !Enemy.Removed)
|
||||
{
|
||||
OperateWeapon(deltaTime);
|
||||
}
|
||||
|
||||
-9
@@ -74,15 +74,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage > ConditionLevel;
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
|
||||
-9
@@ -54,15 +54,6 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Check() => IsCompleted;
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
Item itemToDecontain = targetItem ?? sourceContainer.Inventory.FindItem(i => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)), recursive: false);
|
||||
|
||||
+33
-17
@@ -1,5 +1,4 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -29,32 +28,44 @@ namespace Barotrauma
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
|
||||
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (targetHull == character.CurrentHull)
|
||||
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
distanceFactor = 1;
|
||||
Priority = 0;
|
||||
}
|
||||
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
||||
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
|
||||
float devotion = Math.Min(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
|
||||
else
|
||||
{
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (targetHull == character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
||||
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override bool Check() => targetHull.FireSources.None();
|
||||
|
||||
private float sinTime;
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
|
||||
var extinguisherItem = character.Inventory.FindItemByIdentifier("fireextinguisher") ?? character.Inventory.FindItemByTag("fireextinguisher");
|
||||
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
|
||||
{
|
||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
|
||||
return new AIObjectiveGetItem(character, "fireextinguisher", objectiveManager, equip: true)
|
||||
{
|
||||
// If the item is inside an unsafe hull, decrease the priority
|
||||
GetItemPriority = i => HumanAIController.UnsafeHulls.Contains(i.CurrentHull) ? 0.1f : 1
|
||||
};
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -79,8 +90,12 @@ namespace Barotrauma
|
||||
{
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
// Aim
|
||||
character.CursorPosition = fs.Position;
|
||||
if (extinguisher.Item.RequireAimToUse)
|
||||
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
|
||||
float dist = fromCharacterToFireSource.Length();
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
|
||||
if (extinguisherItem.RequireAimToUse)
|
||||
{
|
||||
bool isOperatingButtons = false;
|
||||
if (SteeringManager == PathSteering)
|
||||
@@ -95,8 +110,9 @@ namespace Barotrauma
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
sinTime += deltaTime * 10;
|
||||
}
|
||||
character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
|
||||
character.SetInput(extinguisherItem.IsShootable ? InputType.Shoot : InputType.Use, false, true);
|
||||
extinguisher.Use(deltaTime, character);
|
||||
if (!targetHull.FireSources.Contains(fs))
|
||||
{
|
||||
@@ -110,7 +126,7 @@ namespace Barotrauma
|
||||
if (move)
|
||||
{
|
||||
//go to the first firesource
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager)
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire",
|
||||
TargetName = fs.Hull.DisplayName
|
||||
|
||||
-1
@@ -9,7 +9,6 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "extinguish fires";
|
||||
public override bool ForceRun => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
|
||||
-1
@@ -9,7 +9,6 @@ namespace Barotrauma
|
||||
public override string DebugTag => $"find diving gear ({gearTag})";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
private readonly string gearTag;
|
||||
private readonly string fallbackTag;
|
||||
|
||||
+33
-20
@@ -26,13 +26,35 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveFindDivingGear divingGearObjective;
|
||||
|
||||
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Check() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
private bool resetPriority;
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
|
||||
{
|
||||
Priority = 100;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
// Boost the priority while seeking the diving gear
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (resetPriority)
|
||||
@@ -44,28 +66,19 @@ namespace Barotrauma
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
currenthullSafety = 0;
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
|
||||
return;
|
||||
}
|
||||
if (HumanAIController.NeedsDivingGear(character, character.CurrentHull, out _) && !HumanAIController.HasDivingGear(character))
|
||||
{
|
||||
Priority = 100;
|
||||
}
|
||||
currenthullSafety = HumanAIController.CurrentHullSafety;
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
// Boost the priority while seeking the diving gear
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
|
||||
currenthullSafety = HumanAIController.CurrentHullSafety;
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-10
@@ -29,16 +29,23 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (Leak.Removed || Leak.Open <= 0) { return 0; }
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
|
||||
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float devotion = Math.Min(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
|
||||
if (Leak.Removed || Leak.Open <= 0)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
|
||||
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
|
||||
+3
-4
@@ -11,7 +11,6 @@ namespace Barotrauma
|
||||
public override string DebugTag => "fix leaks";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
@@ -36,7 +35,7 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>());
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
|
||||
int totalLeaks = Targets.Count();
|
||||
if (totalLeaks == 0) { return 0; }
|
||||
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
|
||||
@@ -44,13 +43,13 @@ namespace Barotrauma
|
||||
bool anyFixers = otherFixers > 0;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
float ratio = anyFixers ? totalLeaks / otherFixers : 1;
|
||||
float ratio = anyFixers ? totalLeaks / (float)otherFixers : 1;
|
||||
return Targets.Sum(t => GetLeakSeverity(t)) * ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / otherFixers : 1;
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
|
||||
{
|
||||
// Enough fixers
|
||||
return 0;
|
||||
|
||||
-9
@@ -31,15 +31,6 @@ namespace Barotrauma
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
|
||||
+15
-6
@@ -51,14 +51,23 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (followControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
||||
if (Target is Entity e && e.Removed) { return 0.0f; }
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (followControlledCharacter && Character.Controlled == null)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
Priority = 0;
|
||||
}
|
||||
return 1.0f;
|
||||
if (Target is Entity e && e.Removed)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.GetPriority();
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
|
||||
|
||||
+5
-8
@@ -45,20 +45,17 @@ namespace Barotrauma
|
||||
private float randomUpdateInterval = 5;
|
||||
public float Random { get; private set; }
|
||||
|
||||
public void SetRandom()
|
||||
public void CalculatePriority()
|
||||
{
|
||||
Random = Rand.Range(0.5f, 1.5f);
|
||||
randomTimer = randomUpdateInterval;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
float initiative = character.GetSkillLevel("initiative");
|
||||
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public override float GetPriority() => Priority;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
@@ -69,7 +66,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRandom();
|
||||
CalculatePriority();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +179,7 @@ namespace Barotrauma
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
|
||||
(PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
|
||||
(PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
|
||||
{
|
||||
Wander(deltaTime);
|
||||
return;
|
||||
|
||||
+39
-13
@@ -45,6 +45,7 @@ namespace Barotrauma
|
||||
public override bool CanBeCompleted => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
@@ -107,21 +108,46 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (character.LockHands) { return 0; }
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (Targets.None()) { return 0; }
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (character.LockHands || character.Submarine == null || Targets.None())
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
Priority = 0;
|
||||
}
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
float devotion = MathHelper.Min(10, Priority);
|
||||
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1);
|
||||
return MathHelper.Lerp(0, max, value);
|
||||
else
|
||||
{
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
if (InverseTargetEvaluation)
|
||||
{
|
||||
targetValue = 100 - targetValue;
|
||||
}
|
||||
var currentSubObjective = CurrentSubObjective;
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
||||
{
|
||||
// If the priority is higher than the target value, let's just use it.
|
||||
// The priority calculation is more precise, but it takes into account things like distances,
|
||||
// so it's better not to use it if it's lower than the rougher targetValue.
|
||||
targetValue = Priority;
|
||||
}
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected void UpdateTargets()
|
||||
|
||||
+17
-6
@@ -41,6 +41,15 @@ namespace Barotrauma
|
||||
public bool IsActiveObjective<T>() where T : AIObjective => GetActiveObjective() is T;
|
||||
|
||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||
/// <summary>
|
||||
/// Returns the last active objective of the specific type.
|
||||
/// </summary>
|
||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||
/// </summary>
|
||||
public IEnumerable<T> GetActiveObjectives<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).Where(so => so is T).Select(so => so as T);
|
||||
|
||||
public bool HasActiveObjective<T>() where T : AIObjective => CurrentObjective is T || CurrentObjective != null && CurrentObjective.GetSubObjectivesRecursive().Any(so => so is T);
|
||||
|
||||
@@ -146,7 +155,7 @@ namespace Barotrauma
|
||||
{
|
||||
var previousObjective = CurrentObjective;
|
||||
var firstObjective = Objectives.FirstOrDefault();
|
||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
|
||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.Priority > firstObjective.Priority)
|
||||
{
|
||||
CurrentObjective = CurrentOrder;
|
||||
}
|
||||
@@ -158,14 +167,14 @@ namespace Barotrauma
|
||||
{
|
||||
previousObjective?.OnDeselected();
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>().SetRandom();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority();
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
|
||||
public float GetCurrentPriority()
|
||||
{
|
||||
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
|
||||
return CurrentObjective == null ? 0.0f : CurrentObjective.Priority;
|
||||
}
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
@@ -203,9 +212,11 @@ namespace Barotrauma
|
||||
|
||||
public void SortObjectives()
|
||||
{
|
||||
CurrentOrder?.GetPriority();
|
||||
Objectives.ForEach(o => o.GetPriority());
|
||||
if (Objectives.Any())
|
||||
{
|
||||
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
||||
Objectives.Sort((x, y) => y.Priority.CompareTo(x.Priority));
|
||||
}
|
||||
GetCurrentObjective()?.SortSubObjectives();
|
||||
}
|
||||
@@ -297,7 +308,7 @@ namespace Barotrauma
|
||||
{
|
||||
IsLoop = true,
|
||||
// Don't override unless it's an order by a player
|
||||
Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
break;
|
||||
default:
|
||||
@@ -306,7 +317,7 @@ namespace Barotrauma
|
||||
{
|
||||
IsLoop = true,
|
||||
// Don't override unless it's an order by a player
|
||||
Override = orderGiver != null && (orderGiver == Character.Controlled || orderGiver.IsRemotePlayer)
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
+38
-18
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -31,19 +32,32 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (component.Item.ConditionPercentage <= 0) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (component.Item.ConditionPercentage <= 0)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
Priority = 0;
|
||||
}
|
||||
if (component.Item.CurrentHull == null) { return 0; }
|
||||
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
|
||||
if (IsOperatedByAnother(GetTarget())) { return 0; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
|
||||
float devotion = MathHelper.Min(10, Priority);
|
||||
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
|
||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
return MathHelper.Clamp(value, 0, max);
|
||||
else
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
if (component.Item.CurrentHull == null || component.Item.CurrentHull.FireSources.Any() || IsOperatedByAnother(character, GetTarget(), out _))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
|
||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
@@ -62,25 +76,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsOperatedByAnother(ItemComponent target)
|
||||
public static bool IsOperatedByAnother(Character character, ItemComponent target, out Character operatingCharacter)
|
||||
{
|
||||
operatingCharacter = null;
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (c == character) { continue; }
|
||||
if (!HumanAIController.IsFriendly(c)) { continue; }
|
||||
if (character != null && c == character) { continue; }
|
||||
if (character?.AIController is HumanAIController humanAi && !humanAi.IsFriendly(c)) { continue; }
|
||||
if (c.SelectedConstruction != target.Item) { continue; }
|
||||
operatingCharacter = c;
|
||||
// If the other character is player, don't try to operate
|
||||
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
|
||||
if (c.AIController is HumanAIController humanAi)
|
||||
if (c.AIController is HumanAIController controllingHumanAi)
|
||||
{
|
||||
// If the other character is ordered to operate the item, let him do it
|
||||
if (humanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
|
||||
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target is Steering)
|
||||
if (character == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (target is Steering)
|
||||
{
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
|
||||
@@ -116,7 +136,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
|
||||
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(target))
|
||||
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(character, target, out _))
|
||||
{
|
||||
// Don't abandon
|
||||
return;
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ namespace Barotrauma
|
||||
public override string DebugTag => "pump water";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool UnequipItems => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
private IEnumerable<Pump> pumpList;
|
||||
|
||||
|
||||
+20
-13
@@ -30,21 +30,28 @@ namespace Barotrauma
|
||||
{
|
||||
// TODO: priority list?
|
||||
// Ignore items that are being repaired by someone else.
|
||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; }
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (Item.CurrentHull == character.CurrentHull)
|
||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
|
||||
{
|
||||
distanceFactor = 1;
|
||||
Priority = 0;
|
||||
}
|
||||
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float isSelected = IsRepairing ? 50 : 0;
|
||||
float devotion = (Math.Min(Priority, 10) + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
|
||||
else
|
||||
{
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (Item.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float isSelected = IsRepairing ? 50 : 0;
|
||||
float devotion = (CumulatedDevotion + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override bool Check()
|
||||
|
||||
+3
-4
@@ -81,18 +81,17 @@ namespace Barotrauma
|
||||
// Don't stop fixing until done
|
||||
return 100;
|
||||
}
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>());
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>(), onlyBots: true);
|
||||
int items = Targets.Count;
|
||||
bool anyFixers = otherFixers > 0;
|
||||
float ratio = anyFixers ? items / otherFixers : 1;
|
||||
var result = ratio;
|
||||
float ratio = anyFixers ? items / (float)otherFixers : 1;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / HumanAIController.CountCrew() > 0.75f))
|
||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
|
||||
{
|
||||
// Enough fixers
|
||||
return 0;
|
||||
|
||||
+50
-19
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
const float CloseEnoughToTreat = 150.0f;
|
||||
const float CloseEnoughToTreat = 100.0f;
|
||||
|
||||
private readonly Character targetCharacter;
|
||||
|
||||
@@ -44,6 +44,15 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
|
||||
{
|
||||
var otherCharacter = character.SelectedBy;
|
||||
if (otherCharacter != null)
|
||||
{
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
|
||||
}
|
||||
}
|
||||
|
||||
if (targetCharacter != character)
|
||||
{
|
||||
@@ -67,7 +76,11 @@ namespace Barotrauma
|
||||
TargetName = targetCharacter.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () => RemoveSubObjective(ref goToObjective));
|
||||
onAbandon: () =>
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
Abandon = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -86,7 +99,11 @@ namespace Barotrauma
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () => RemoveSubObjective(ref goToObjective));
|
||||
onAbandon: () =>
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
safeHull = character.CurrentHull;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +122,11 @@ namespace Barotrauma
|
||||
TargetName = targetCharacter.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () => RemoveSubObjective(ref goToObjective));
|
||||
onAbandon: () =>
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
Abandon = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -127,6 +148,11 @@ namespace Barotrauma
|
||||
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (!targetCharacter.IsPlayer)
|
||||
{
|
||||
// If the target is a bot, don't let it move
|
||||
targetCharacter.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
treatmentTimer -= deltaTime;
|
||||
@@ -137,9 +163,8 @@ namespace Barotrauma
|
||||
//find which treatments are the most suitable to treat the character's current condition
|
||||
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, normalize: false);
|
||||
|
||||
var allAfflictions = GetVitalityReducingAfflictions(targetCharacter).OrderByDescending(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth));
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
|
||||
{
|
||||
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
@@ -200,10 +225,12 @@ namespace Barotrauma
|
||||
onAbandon: () => RemoveSubObjective(ref getItemObjective));
|
||||
}
|
||||
}
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
if (character != targetCharacter)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||
@@ -240,7 +267,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) > AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager);
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
if (isCompleted && targetCharacter != character)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
|
||||
@@ -253,20 +280,24 @@ namespace Barotrauma
|
||||
{
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
{
|
||||
return 0;
|
||||
Priority = 0;
|
||||
}
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (targetCharacter.CurrentHull == character.CurrentHull)
|
||||
else
|
||||
{
|
||||
distanceFactor = 1;
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (targetCharacter.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter);
|
||||
float devotion = Math.Min(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1));
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public static IEnumerable<Affliction> GetVitalityReducingAfflictions(Character character) => character.CharacterHealth.GetAllAfflictions(a => a.GetVitalityDecrease(character.CharacterHealth) > 0);
|
||||
public static IEnumerable<Affliction> GetSortedAfflictions(Character character) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions());
|
||||
}
|
||||
}
|
||||
|
||||
+67
-8
@@ -8,11 +8,11 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "rescue all";
|
||||
public override bool ForceRun => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool InverseTargetEvaluation => true;
|
||||
|
||||
private const float vitalityThreshold = 80;
|
||||
private const float vitalityThresholdForOrders = 95;
|
||||
public static float GetVitalityThreshold(AIObjectiveManager manager)
|
||||
private const float vitalityThresholdForOrders = 100;
|
||||
public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
|
||||
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? vitalityThresholdForOrders : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,50 @@ namespace Barotrauma
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
|
||||
protected override float TargetEvaluation() => Targets.Max(t => GetVitalityFactor(t));
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int otherRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRescueAll>(), onlyBots: true);
|
||||
int targetCount = Targets.Count;
|
||||
bool anyRescuers = otherRescuers > 0;
|
||||
float ratio = anyRescuers ? targetCount / (float)otherRescuers : 1;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return Targets.Min(t => GetVitalityFactor(t)) / ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
float multiplier = 1;
|
||||
if (anyRescuers)
|
||||
{
|
||||
float mySkill = character.GetSkillLevel("medical");
|
||||
int betterRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.Character.Info.Job.GetSkillLevel("medical") >= mySkill, onlyBots: true);
|
||||
if (targetCount / (float)betterRescuers <= 1)
|
||||
{
|
||||
// Enough rescuers
|
||||
return 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundOtherMedics = HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.Info.Job.Prefab.Identifier == "medicaldoctor");
|
||||
if (foundOtherMedics)
|
||||
{
|
||||
if (character.Info.Job.Prefab.Identifier != "medicaldoctor")
|
||||
{
|
||||
// Double the vitality factor -> less likely to take action
|
||||
multiplier = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Targets.Min(t => GetVitalityFactor(t)) / ratio * multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetVitalityFactor(Character character) => Math.Min(character.HealthPercentage - character.Bleeding - character.Bloodloss - Math.Min(character.Oxygen, 0), 100);
|
||||
public static float GetVitalityFactor(Character character)
|
||||
{
|
||||
float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Character target)
|
||||
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
|
||||
@@ -47,16 +88,34 @@ namespace Barotrauma
|
||||
if (!HumanAIController.IsFriendly(character, target)) { return false; }
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) > GetVitalityThreshold(humanAI.ObjectiveManager)) { return false; }
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetVitalityFactor(target) > vitalityThreshold) { return false; }
|
||||
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
|
||||
}
|
||||
if (target.Submarine == null || character.Submarine == null) { return false; }
|
||||
if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; }
|
||||
if (target.CurrentHull == null) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
{
|
||||
// Ignore all concious targets that are currently fighting, fleeing or treating characters
|
||||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Don't go into rooms that have enemies
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c))) { return false; }
|
||||
return true;
|
||||
|
||||
+38
-26
@@ -231,25 +231,12 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Limb refLimb = GetLimb(LimbType.Head);
|
||||
float refAngle;
|
||||
if (refLimb == null)
|
||||
float rotation = MathHelper.WrapAngle(Collider.Rotation);
|
||||
rotation = MathHelper.ToDegrees(rotation);
|
||||
if (rotation < 0.0f)
|
||||
{
|
||||
refAngle = CurrentAnimationParams.TorsoAngleInRadians;
|
||||
refLimb = GetLimb(LimbType.Torso);
|
||||
rotation += 360;
|
||||
}
|
||||
else
|
||||
{
|
||||
refAngle = CurrentAnimationParams.HeadAngleInRadians;
|
||||
}
|
||||
|
||||
float rotation = refLimb.Rotation;
|
||||
if (!float.IsNaN(refAngle)) { rotation -= refAngle * Dir; }
|
||||
|
||||
rotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(rotation));
|
||||
|
||||
if (rotation < 0.0f) rotation += 360;
|
||||
|
||||
if (rotation > 20 && rotation < 160)
|
||||
{
|
||||
TargetDir = Direction.Left;
|
||||
@@ -347,9 +334,23 @@ namespace Barotrauma
|
||||
target.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + dragForce));
|
||||
}
|
||||
|
||||
//pull the character's mouth to the target character (again with a fluctuating force)
|
||||
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
|
||||
mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
if (InWater)
|
||||
{
|
||||
//pull the character's mouth to the target character (again with a fluctuating force)
|
||||
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
|
||||
mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
float force = (float)Math.Sin(eatTimer * 100) * mouthLimb.Mass;
|
||||
mouthLimb.body.ApplyLinearImpulse(Vector2.UnitY * force * 2, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
mouthLimb.body.ApplyTorque(-force * 50);
|
||||
}
|
||||
var jaw = GetLimb(LimbType.Jaw);
|
||||
if (jaw != null)
|
||||
{
|
||||
jaw.body.ApplyTorque(-(float)Math.Sin(eatTimer * 150) * jaw.Mass * 25);
|
||||
}
|
||||
|
||||
character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
|
||||
@@ -439,7 +440,7 @@ namespace Barotrauma
|
||||
|
||||
if (CurrentSwimParams.RotateTowardsMovement)
|
||||
{
|
||||
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque);
|
||||
Collider.SmoothRotate(movementAngle, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
@@ -491,11 +492,11 @@ namespace Barotrauma
|
||||
}
|
||||
if (mainLimb.type == LimbType.Head && HeadAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque);
|
||||
Collider.SmoothRotate(HeadAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
else if (mainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
|
||||
{
|
||||
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque);
|
||||
Collider.SmoothRotate(TorsoAngle.Value * Dir, CurrentSwimParams.SteerTorque * character.SpeedMultiplier);
|
||||
}
|
||||
if (TorsoAngle.HasValue)
|
||||
{
|
||||
@@ -515,7 +516,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var waveLength = Math.Abs(CurrentSwimParams.WaveLength * RagdollParams.JointScale);
|
||||
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude);
|
||||
var waveAmplitude = Math.Abs(CurrentSwimParams.WaveAmplitude * character.SpeedMultiplier);
|
||||
if (waveLength > 0 && waveAmplitude > 0)
|
||||
{
|
||||
WalkPos -= transformedMovement.Length() / Math.Abs(waveLength);
|
||||
@@ -524,6 +525,10 @@ namespace Barotrauma
|
||||
|
||||
foreach (var limb in Limbs)
|
||||
{
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
|
||||
}
|
||||
switch (limb.type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
@@ -548,7 +553,7 @@ namespace Barotrauma
|
||||
if (Limbs[i].SteerForce <= 0.0f) { continue; }
|
||||
if (!Collider.PhysEnabled) { continue; }
|
||||
Vector2 pullPos = Limbs[i].PullJointWorldAnchorA;
|
||||
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos);
|
||||
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass * Math.Max(character.SpeedMultiplier, 1), pullPos);
|
||||
}
|
||||
|
||||
Vector2 mainLimbDiff = mainLimb.PullJointWorldAnchorB - mainLimb.SimPosition;
|
||||
@@ -665,6 +670,10 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (Math.Abs(limb.Params.ConstantTorque) > 0)
|
||||
{
|
||||
limb.body.SmoothRotate(movementAngle + MathHelper.ToRadians(limb.Params.ConstantAngle) * Dir, limb.Params.ConstantTorque, wrapAngle: true);
|
||||
}
|
||||
switch (limb.type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
@@ -729,7 +738,10 @@ namespace Barotrauma
|
||||
break;
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightLeg:
|
||||
if (Math.Abs(CurrentGroundedParams.LegTorque) > 0.001f) limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
|
||||
if (Math.Abs(CurrentGroundedParams.LegTorque) > 0)
|
||||
{
|
||||
limb.body.ApplyTorque(limb.Mass * CurrentGroundedParams.LegTorque * Dir);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -900,7 +900,7 @@ namespace Barotrauma
|
||||
|
||||
surfaceLimiter = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 0.4f) - surfacePos;
|
||||
surfaceLimiter = Math.Max(1.0f, surfaceLimiter);
|
||||
if (surfaceLimiter > 50.0f) return;
|
||||
if (surfaceLimiter > 50.0f) { return; }
|
||||
}
|
||||
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
@@ -928,8 +928,7 @@ namespace Barotrauma
|
||||
if (!aiming)
|
||||
{
|
||||
float newRotation = MathUtils.VectorToAngle(TargetMovement) - MathHelper.PiOver2;
|
||||
Collider.SmoothRotate(newRotation, 5.0f);
|
||||
//torso.body.SmoothRotate(newRotation);
|
||||
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -942,13 +941,13 @@ namespace Barotrauma
|
||||
TargetMovement = new Vector2(0.0f, -0.1f);
|
||||
|
||||
float newRotation = MathUtils.VectorToAngle(diff);
|
||||
Collider.SmoothRotate(newRotation, 5.0f);
|
||||
Collider.SmoothRotate(newRotation, 5.0f * character.SpeedMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
torso.body.MoveToPos(Collider.SimPosition + new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * 0.4f, 5.0f);
|
||||
|
||||
if (TargetMovement == Vector2.Zero) return;
|
||||
if (TargetMovement == Vector2.Zero) { return; }
|
||||
|
||||
movement = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
|
||||
|
||||
@@ -1007,7 +1006,7 @@ namespace Barotrauma
|
||||
|
||||
var waist = GetLimb(LimbType.Waist);
|
||||
footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength);
|
||||
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * CurrentAnimationParams.CycleSpeed, 0.0f);
|
||||
Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength / character.SpeedMultiplier) * CurrentSwimParams.LegMoveAmount, 0.0f);
|
||||
transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation));
|
||||
|
||||
if (rightFoot != null && !rightFoot.Disabled)
|
||||
@@ -1061,7 +1060,7 @@ namespace Barotrauma
|
||||
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, rightHandPos.X) : Math.Min(-0.3f, rightHandPos.X);
|
||||
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
|
||||
|
||||
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength);
|
||||
HandIK(rightHand, handPos + rightHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
|
||||
}
|
||||
|
||||
if (leftHand != null && !leftHand.Disabled)
|
||||
@@ -1070,7 +1069,7 @@ namespace Barotrauma
|
||||
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.3f, leftHandPos.X) : Math.Min(-0.3f, leftHandPos.X);
|
||||
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
|
||||
|
||||
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength);
|
||||
HandIK(leftHand, handPos + leftHandPos, CurrentSwimParams.HandMoveStrength * character.SpeedMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1929,7 +1928,7 @@ namespace Barotrauma
|
||||
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
|
||||
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
|
||||
{
|
||||
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false);
|
||||
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (character.IsHusk)
|
||||
if (character.IsHusk && character.Params.UseHuskAppendage)
|
||||
{
|
||||
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
|
||||
if (characterPrefab?.XDocument != null)
|
||||
@@ -1626,7 +1626,7 @@ namespace Barotrauma
|
||||
float sin = (float)Math.Sin(mouthLimb.Rotation);
|
||||
Vector2 bodySize = mouthLimb.body.GetSize();
|
||||
Vector2 offset = new Vector2(mouthLimb.MouthPos.X * bodySize.X / 2, mouthLimb.MouthPos.Y * bodySize.Y / 2);
|
||||
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * RagdollParams.LimbScale;
|
||||
return mouthLimb.SimPosition + new Vector2(offset.X * cos - offset.Y * sin, offset.X * sin + offset.Y * cos) * mouthLimb.Scale * RagdollParams.LimbScale;
|
||||
}
|
||||
|
||||
public Vector2 GetColliderBottom()
|
||||
|
||||
@@ -13,11 +13,12 @@ namespace Barotrauma
|
||||
|
||||
public enum AttackContext
|
||||
{
|
||||
NotDefined,
|
||||
Any,
|
||||
Water,
|
||||
Ground,
|
||||
Inside,
|
||||
Outside
|
||||
Outside,
|
||||
NotDefined
|
||||
}
|
||||
|
||||
public enum AttackTarget
|
||||
@@ -72,13 +73,13 @@ namespace Barotrauma
|
||||
|
||||
partial class Attack : ISerializableEntity
|
||||
{
|
||||
[Serialize(AttackContext.NotDefined, true, description: "The attack will be used only in this context."), Editable]
|
||||
[Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
|
||||
public AttackContext Context { get; private set; }
|
||||
|
||||
[Serialize(AttackTarget.Any, true, description: "Does the attack target only specific targets?"), Editable]
|
||||
public AttackTarget TargetType { get; private set; }
|
||||
|
||||
[Serialize(LimbType.None, true, description: "If not defined or set to none, the closest limb is used (default)."), Editable]
|
||||
[Serialize(LimbType.None, true, description: "To which limb is the attack aimed at? If not defined or set to none, the closest limb is used (default)."), Editable]
|
||||
public LimbType TargetLimbType { get; private set; }
|
||||
|
||||
[Serialize(HitDetection.Distance, true, description: "Collision detection is more accurate, but it only affects targets that are in contact with the limb."), Editable]
|
||||
@@ -87,9 +88,15 @@ namespace Barotrauma
|
||||
[Serialize(AIBehaviorAfterAttack.FallBack, true, description: "The preferred AI behavior after the attack."), Editable]
|
||||
public AIBehaviorAfterAttack AfterAttack { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the AI try to reverse when aiming with this attack?"), Editable]
|
||||
[Serialize(0f, true, description: "A delay before reacting after performing an attack."), Editable]
|
||||
public float AfterAttackDelay { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the AI try to turn around when aiming with this attack?"), Editable]
|
||||
public bool Reverse { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the AI try to steer away from the target when aiming with this attack? Best combined with PassiveAggressive behavior."), Editable]
|
||||
public bool Retreat { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "The min distance from the attack limb to the target before the AI tries to attack."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 2000.0f)]
|
||||
public float Range { get; set; }
|
||||
|
||||
@@ -147,7 +154,19 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs). The direction of the force is towards the target that's being attacked."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float Force { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
|
||||
public Vector2 RootForceWorldStart { get; private set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
|
||||
public Vector2 RootForceWorldMiddle { get; private set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the main limb. In world space coordinates(i.e. 0, 1 pushes the character upwards a bit). The attacker's facing direction is taken into account."), Editable]
|
||||
public Vector2 RootForceWorldEnd { get; private set; }
|
||||
|
||||
[Serialize(TransitionMode.Linear, true, description:""), Editable]
|
||||
public TransitionMode RootTransitionEasing { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "Applied to the attacking limb (or limbs defined using ApplyForceOnLimbs)"), Editable(MinValueFloat = -10000.0f, MaxValueFloat = 10000.0f)]
|
||||
public float Torque { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
@@ -156,13 +175,13 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the impulse is from this limb towards the target (use negative values to pull the target closer)."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float TargetImpulse { get; private set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
|
||||
public Vector2 TargetImpulseWorld { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "Applied to the target the attack hits. The direction of the force is from this limb towards the target (use negative values to pull the target closer)."), Editable(-1000.0f, 1000.0f)]
|
||||
public float TargetForce { get; private set; }
|
||||
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards)."), Editable]
|
||||
[Serialize("0.0, 0.0", true, description: "Applied to the target, in world space coordinates(i.e. 0, -1 pushes the target downwards). The attacker's facing direction is taken into account."), Editable]
|
||||
public Vector2 TargetForceWorld { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "How likely the attack causes target limbs to be severed when the target is dead."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
@@ -419,7 +438,10 @@ namespace Barotrauma
|
||||
|
||||
public AttackResult DoDamageToLimb(Character attacker, Limb targetLimb, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
{
|
||||
if (targetLimb == null) return new AttackResult();
|
||||
if (targetLimb == null)
|
||||
{
|
||||
return new AttackResult();
|
||||
}
|
||||
|
||||
if (OnlyHumans)
|
||||
{
|
||||
@@ -461,6 +483,7 @@ namespace Barotrauma
|
||||
|
||||
public float AttackTimer { get; private set; }
|
||||
public float CoolDownTimer { get; set; }
|
||||
public float CurrentRandomCoolDown { get; private set; }
|
||||
public float SecondaryCoolDownTimer { get; set; }
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
@@ -492,7 +515,8 @@ namespace Barotrauma
|
||||
public void SetCoolDown()
|
||||
{
|
||||
float randomFraction = CoolDown * CoolDownRandomFactor;
|
||||
CoolDownTimer = CoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
|
||||
CurrentRandomCoolDown = MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
|
||||
CoolDownTimer = CoolDown + CurrentRandomCoolDown;
|
||||
randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
|
||||
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
|
||||
}
|
||||
@@ -501,11 +525,12 @@ namespace Barotrauma
|
||||
{
|
||||
CoolDownTimer = 0;
|
||||
SecondaryCoolDownTimer = 0;
|
||||
CurrentRandomCoolDown = 0;
|
||||
}
|
||||
|
||||
partial void DamageParticles(float deltaTime, Vector2 worldPosition);
|
||||
|
||||
public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.NotDefined;
|
||||
public bool IsValidContext(AttackContext context) => Context == context || Context == AttackContext.Any || Context == AttackContext.NotDefined;
|
||||
|
||||
public bool IsValidContext(IEnumerable<AttackContext> contexts)
|
||||
{
|
||||
@@ -559,5 +584,11 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 CalculateAttackPhase(TransitionMode easing = TransitionMode.Linear)
|
||||
{
|
||||
float t = AttackTimer / Duration;
|
||||
return MathUtils.Bezier(RootForceWorldStart, RootForceWorldMiddle, RootForceWorldEnd, ToolBox.GetEasing(easing, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ namespace Barotrauma
|
||||
public Hull CurrentHull = null;
|
||||
|
||||
public bool IsRemotePlayer;
|
||||
|
||||
public bool IsPlayer => Controlled == this || IsRemotePlayer;
|
||||
public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled;
|
||||
|
||||
public readonly Dictionary<string, SerializableProperty> Properties;
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
@@ -128,6 +132,12 @@ namespace Barotrauma
|
||||
set => Params.Noise = value;
|
||||
}
|
||||
|
||||
public float Visibility
|
||||
{
|
||||
get => Params.Visibility;
|
||||
set => Params.Visibility = value;
|
||||
}
|
||||
|
||||
public bool IsTraitor;
|
||||
public string TraitorCurrentObjective = "";
|
||||
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
|
||||
@@ -201,7 +211,7 @@ namespace Barotrauma
|
||||
{
|
||||
displayName = TextManager.Get($"Character.{SpeciesName}", returnNull: true);
|
||||
}
|
||||
return displayName ?? Name;
|
||||
return string.IsNullOrWhiteSpace(displayName) ? Name : displayName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,10 +780,14 @@ namespace Barotrauma
|
||||
{
|
||||
nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
|
||||
}
|
||||
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(nonHuskedSpeciesName) as RagdollParams;
|
||||
if (info == null)
|
||||
if (ragdollParams == null)
|
||||
{
|
||||
info = new CharacterInfo(nonHuskedSpeciesName, ragdollParams.FileName);
|
||||
string name = Params.UseHuskAppendage ? nonHuskedSpeciesName : speciesName;
|
||||
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(name) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(name) as RagdollParams;
|
||||
}
|
||||
if (Params.HasInfo && info == null)
|
||||
{
|
||||
info = new CharacterInfo(nonHuskedSpeciesName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,7 +858,7 @@ namespace Barotrauma
|
||||
Info.HairElement?.Elements("sprite").ForEach(s => head.OtherWearables.Add(new WearableSprite(s, WearableType.Hair)));
|
||||
|
||||
#if CLIENT
|
||||
head.LoadHuskSprite();
|
||||
head.EnableHuskSprite = Params.Husk;
|
||||
head.LoadHerpesSprite();
|
||||
head.UpdateWearableTypesToHide();
|
||||
#endif
|
||||
@@ -1010,59 +1024,55 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsKeyDown(InputType.Left)) targetMovement.X -= 1.0f;
|
||||
if (IsKeyDown(InputType.Right)) targetMovement.X += 1.0f;
|
||||
if (IsKeyDown(InputType.Up)) targetMovement.Y += 1.0f;
|
||||
if (IsKeyDown(InputType.Down)) targetMovement.Y -= 1.0f;
|
||||
if (IsKeyDown(InputType.Left)) { targetMovement.X -= 1.0f; }
|
||||
if (IsKeyDown(InputType.Right)) { targetMovement.X += 1.0f; }
|
||||
if (IsKeyDown(InputType.Up)) { targetMovement.Y += 1.0f; }
|
||||
if (IsKeyDown(InputType.Down)) { targetMovement.Y -= 1.0f; }
|
||||
}
|
||||
bool run = false;
|
||||
if ((IsKeyDown(InputType.Run) && AnimController.ForceSelectAnimationType == AnimationType.NotDefined) || ForceRun)
|
||||
{
|
||||
|
||||
run = CanRun;
|
||||
}
|
||||
return ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
|
||||
}
|
||||
|
||||
//can't run if
|
||||
// - dragging someone
|
||||
// - crouching
|
||||
// - moving backwards
|
||||
public bool CanRun => (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
|
||||
(!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
|
||||
!AnimController.IsMovingBackwards;
|
||||
|
||||
public Vector2 ApplyMovementLimits(Vector2 targetMovement, float currentSpeed)
|
||||
{
|
||||
//the vertical component is only used for falling through platforms and climbing ladders when not in water,
|
||||
//so the movement can't be normalized or the Character would walk slower when pressing down/up
|
||||
if (AnimController.InWater)
|
||||
{
|
||||
float length = targetMovement.Length();
|
||||
if (length > 0.0f) targetMovement /= length;
|
||||
if (length > 0.0f)
|
||||
{
|
||||
targetMovement /= length;
|
||||
}
|
||||
}
|
||||
|
||||
bool run = false;
|
||||
if ((IsKeyDown(InputType.Run) && AnimController.ForceSelectAnimationType == AnimationType.NotDefined) || ForceRun)
|
||||
{
|
||||
//can't run if
|
||||
// - dragging someone
|
||||
// - crouching
|
||||
// - moving backwards
|
||||
run = (SelectedCharacter == null || !SelectedCharacter.CanBeDragged) &&
|
||||
(!(AnimController is HumanoidAnimController) || !((HumanoidAnimController)AnimController).Crouching) &&
|
||||
!AnimController.IsMovingBackwards;
|
||||
}
|
||||
|
||||
float currentSpeed = AnimController.GetCurrentSpeed(run);
|
||||
targetMovement *= currentSpeed;
|
||||
float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed);
|
||||
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
|
||||
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
|
||||
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
float speedMultiplier = SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
|
||||
ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
|
||||
SpeedMultiplier = greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
|
||||
targetMovement *= SpeedMultiplier;
|
||||
// Reset, status effects will set the value before the next update
|
||||
ResetSpeedMultiplier();
|
||||
return targetMovement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify the character's speed via StatusEffects
|
||||
/// </summary>
|
||||
public float SpeedMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
return greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
|
||||
}
|
||||
}
|
||||
public float SpeedMultiplier { get; private set; }
|
||||
|
||||
public void StackSpeedMultiplier(float val)
|
||||
{
|
||||
@@ -1915,7 +1925,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
|
||||
if (c == Controlled || c.IsRemotePlayer)
|
||||
if (c.IsPlayer || c.IsBot)
|
||||
{
|
||||
c.Enabled = true;
|
||||
}
|
||||
@@ -2012,8 +2022,8 @@ namespace Barotrauma
|
||||
HideFace = false;
|
||||
|
||||
|
||||
UpdateSightRange();
|
||||
UpdateSoundRange();
|
||||
UpdateSightRange(deltaTime);
|
||||
UpdateSoundRange(deltaTime);
|
||||
|
||||
if (IsDead) { return; }
|
||||
|
||||
@@ -2290,18 +2300,39 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSightRange()
|
||||
private readonly float maxAIRange = 10000;
|
||||
private readonly float aiTargetChangeSpeed = 5;
|
||||
|
||||
private void UpdateSightRange(float deltaTime)
|
||||
{
|
||||
if (aiTarget == null) { return; }
|
||||
float range = (float)Math.Sqrt(Mass) * 250 + AnimController.Collider.LinearVelocity.Length() * 500;
|
||||
aiTarget.SightRange = MathHelper.Clamp(range, 0, 10000);
|
||||
float minRange = Math.Clamp((float)Math.Sqrt(Mass) * Visibility, 250, 1000);
|
||||
float massFactor = (float)Math.Sqrt(Mass / 20);
|
||||
float targetRange = Math.Min(minRange + massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Visibility, maxAIRange);
|
||||
float newRange = MathHelper.SmoothStep(aiTarget.SightRange, targetRange, deltaTime * aiTargetChangeSpeed);
|
||||
if (!float.IsNaN(newRange))
|
||||
{
|
||||
aiTarget.SightRange = newRange;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSoundRange()
|
||||
private void UpdateSoundRange(float deltaTime)
|
||||
{
|
||||
if (aiTarget == null) { return; }
|
||||
float range = ((float)Math.Sqrt(Mass) / 3) * (AnimController.TargetMovement.Length() * 2) * Noise;
|
||||
aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000);
|
||||
if (IsDead)
|
||||
{
|
||||
aiTarget.SoundRange = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float massFactor = (float)Math.Sqrt(Mass / 10);
|
||||
float targetRange = Math.Min(massFactor * AnimController.Collider.LinearVelocity.Length() * 2 * Noise, maxAIRange);
|
||||
float newRange = MathHelper.SmoothStep(aiTarget.SoundRange, targetRange, deltaTime * aiTargetChangeSpeed);
|
||||
if (!float.IsNaN(newRange))
|
||||
{
|
||||
aiTarget.SoundRange = newRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanHearCharacter(Character speaker)
|
||||
@@ -2316,21 +2347,16 @@ namespace Barotrauma
|
||||
|
||||
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true)
|
||||
{
|
||||
if (orderGiver != null)
|
||||
{
|
||||
//set the character order only if the character is close enough to hear the message
|
||||
if (!CanHearCharacter(orderGiver)) { return; }
|
||||
}
|
||||
//set the character order only if the character is close enough to hear the message
|
||||
if (orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
|
||||
|
||||
if (AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.SetOrder(order, orderOption, orderGiver, speak);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
else
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
|
||||
}
|
||||
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
|
||||
#endif
|
||||
|
||||
CurrentOrder = order;
|
||||
@@ -2469,13 +2495,17 @@ namespace Barotrauma
|
||||
DamageLimb(worldPosition, targetLimb, attack.Afflictions.Keys, attack.Stun, playSound, attackImpulse, attacker);
|
||||
|
||||
if (limbHit == null) { return new AttackResult(); }
|
||||
|
||||
limbHit.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
Vector2 forceWorld = attack.TargetImpulseWorld + attack.TargetForceWorld;
|
||||
if (attacker != null)
|
||||
{
|
||||
forceWorld.X *= attacker.AnimController.Dir;
|
||||
}
|
||||
limbHit.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
var mainLimb = limbHit.character.AnimController.MainLimb;
|
||||
if (limbHit != mainLimb)
|
||||
{
|
||||
// Always add force to mainlimb
|
||||
mainLimb.body?.ApplyLinearImpulse(attack.TargetImpulseWorld + attack.TargetForceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
mainLimb.body?.ApplyLinearImpulse(forceWorld * deltaTime, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
#if SERVER
|
||||
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)
|
||||
|
||||
+9
-4
@@ -14,8 +14,13 @@ namespace Barotrauma
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
|
||||
|
||||
protected float _strength;
|
||||
[Serialize(0f, true), Editable]
|
||||
public float Strength { get; set; }
|
||||
public virtual float Strength
|
||||
{
|
||||
get { return _strength; }
|
||||
set { _strength = value; }
|
||||
}
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
public string Identifier { get; private set; }
|
||||
@@ -38,7 +43,7 @@ namespace Barotrauma
|
||||
public Affliction(AfflictionPrefab prefab, float strength)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Strength = strength;
|
||||
_strength = strength;
|
||||
Identifier = prefab?.Identifier;
|
||||
}
|
||||
|
||||
@@ -173,11 +178,11 @@ namespace Barotrauma
|
||||
|
||||
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
|
||||
{
|
||||
Strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
|
||||
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier;
|
||||
}
|
||||
else // Reduce strengthening of afflictions if resistant
|
||||
{
|
||||
Strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
|
||||
_strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
|
||||
}
|
||||
|
||||
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
|
||||
|
||||
+83
-75
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
{
|
||||
public enum InfectionState
|
||||
{
|
||||
Dormant, Transition, Active
|
||||
Initial, Dormant, Transition, Active, Final
|
||||
}
|
||||
|
||||
private bool subscribedToDeathEvent;
|
||||
@@ -17,154 +17,157 @@ namespace Barotrauma
|
||||
private InfectionState state;
|
||||
|
||||
private List<Limb> huskAppendage;
|
||||
|
||||
|
||||
private Character character;
|
||||
|
||||
private readonly List<Affliction> huskInfection = new List<Affliction>();
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public override float Strength
|
||||
{
|
||||
get { return _strength; }
|
||||
set
|
||||
{
|
||||
// Don't allow to set the strength too high (from outside) to avoid rapid transformation into husk when taking lots of damage from husks.
|
||||
// If the strength is more than the value, this will effectively reset the current strength to the max. That's why we use two steps.
|
||||
float max = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
|
||||
_strength = Math.Clamp(value, 0, max);
|
||||
}
|
||||
}
|
||||
|
||||
public InfectionState State
|
||||
{
|
||||
get { return state; }
|
||||
private set
|
||||
{
|
||||
if (state == value) { return; }
|
||||
state = value;
|
||||
if (character != null && character == Character.Controlled)
|
||||
{
|
||||
UpdateMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AfflictionHusk(AfflictionPrefab prefab, float strength) :
|
||||
base(prefab, strength)
|
||||
{
|
||||
}
|
||||
private float DormantThreshold => Prefab.MaxStrength * 0.5f;
|
||||
private float ActiveThreshold => Prefab.MaxStrength * 0.75f;
|
||||
|
||||
public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { }
|
||||
|
||||
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
float prevStrength = Strength;
|
||||
base.Update(characterHealth, targetLimb, deltaTime);
|
||||
|
||||
character = characterHealth.Character;
|
||||
if (character == null) { return; }
|
||||
if (!subscribedToDeathEvent)
|
||||
{
|
||||
characterHealth.Character.OnDeath += CharacterDead;
|
||||
character.OnDeath += CharacterDead;
|
||||
subscribedToDeathEvent = true;
|
||||
}
|
||||
|
||||
if (characterHealth.Character == Character.Controlled) UpdateMessages(prevStrength, characterHealth.Character);
|
||||
if (Strength < Prefab.MaxStrength * 0.5f)
|
||||
if (Strength < DormantThreshold)
|
||||
{
|
||||
UpdateDormantState(deltaTime, characterHealth.Character);
|
||||
DeactivateHusk();
|
||||
State = InfectionState.Dormant;
|
||||
}
|
||||
else if (Strength < ActiveThreshold)
|
||||
{
|
||||
DeactivateHusk();
|
||||
character.SpeechImpediment = 100;
|
||||
State = InfectionState.Transition;
|
||||
}
|
||||
else if (Strength < Prefab.MaxStrength)
|
||||
{
|
||||
characterHealth.Character.SpeechImpediment = 100.0f;
|
||||
UpdateTransitionState(deltaTime, characterHealth.Character);
|
||||
if (State != InfectionState.Active)
|
||||
{
|
||||
character.SetStun(Rand.Range(2, 4, Rand.RandSync.Server));
|
||||
}
|
||||
State = InfectionState.Active;
|
||||
ActivateHusk();
|
||||
}
|
||||
else
|
||||
{
|
||||
characterHealth.Character.SpeechImpediment = 100.0f;
|
||||
UpdateActiveState(deltaTime, characterHealth.Character);
|
||||
State = InfectionState.Final;
|
||||
ActivateHusk();
|
||||
ApplyDamage(deltaTime, applyForce: true);
|
||||
character.SetStun(1);
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateMessages(float prevStrength, Character character);
|
||||
partial void UpdateMessages();
|
||||
|
||||
private void UpdateDormantState(float deltaTime, Character character)
|
||||
private void ApplyDamage(float deltaTime, bool applyForce)
|
||||
{
|
||||
if (state != InfectionState.Dormant)
|
||||
{
|
||||
DeactivateHusk(character);
|
||||
}
|
||||
|
||||
state = InfectionState.Dormant;
|
||||
}
|
||||
|
||||
private void UpdateTransitionState(float deltaTime, Character character)
|
||||
{
|
||||
if (state != InfectionState.Transition)
|
||||
{
|
||||
DeactivateHusk(character);
|
||||
}
|
||||
|
||||
state = InfectionState.Transition;
|
||||
}
|
||||
|
||||
private void UpdateActiveState(float deltaTime, Character character)
|
||||
{
|
||||
if (state != InfectionState.Active)
|
||||
{
|
||||
ActivateHusk(character);
|
||||
state = InfectionState.Active;
|
||||
}
|
||||
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
float random = Rand.Value(Rand.RandSync.Server);
|
||||
huskInfection.Clear();
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * deltaTime / character.AnimController.Limbs.Length));
|
||||
character.LastDamageSource = null;
|
||||
character.DamageLimb(
|
||||
limb.WorldPosition, limb,
|
||||
new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(0.5f * deltaTime / character.AnimController.Limbs.Length) },
|
||||
0.0f, false, 0.0f);
|
||||
float force = applyForce ? random * 0.1f * limb.Mass : 0;
|
||||
character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateHusk(Character character)
|
||||
public void ActivateHusk()
|
||||
{
|
||||
if (huskAppendage == null)
|
||||
if (huskAppendage == null && character.Params.UseHuskAppendage)
|
||||
{
|
||||
huskAppendage = AttachHuskAppendage(character, Prefab.Identifier);
|
||||
if (huskAppendage != null)
|
||||
{
|
||||
character.NeedsAir = false;
|
||||
character.SetStun(0.5f);
|
||||
}
|
||||
#if CLIENT
|
||||
character.AnimController.GetLimb(LimbType.Head).EnableHuskSprite = true;
|
||||
#endif
|
||||
}
|
||||
character.NeedsAir = false;
|
||||
character.SpeechImpediment = 100;
|
||||
}
|
||||
|
||||
private void DeactivateHusk(Character character)
|
||||
private void DeactivateHusk()
|
||||
{
|
||||
character.NeedsAir = character.Params.MainElement.GetAttributeBool("needsair", false);
|
||||
if (huskAppendage != null)
|
||||
{
|
||||
huskAppendage.ForEach(l => character.AnimController.RemoveLimb(l));
|
||||
huskAppendage = null;
|
||||
#if CLIENT
|
||||
character.AnimController.GetLimb(LimbType.Head).EnableHuskSprite = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Character character)
|
||||
public void Remove()
|
||||
{
|
||||
DeactivateHusk(character);
|
||||
if (character != null) character.OnDeath -= CharacterDead;
|
||||
if (character == null) { return; }
|
||||
DeactivateHusk();
|
||||
character.OnDeath -= CharacterDead;
|
||||
subscribedToDeathEvent = false;
|
||||
}
|
||||
|
||||
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (Strength < Prefab.MaxStrength * 0.5f || character.Removed) { return; }
|
||||
if (Strength < ActiveThreshold || character.Removed) { return; }
|
||||
|
||||
//don't turn the character into a husk if any of its limbs are severed
|
||||
if (character.AnimController?.LimbJoints != null)
|
||||
{
|
||||
foreach (var limbJoint in character.AnimController.LimbJoints)
|
||||
{
|
||||
if (limbJoint.IsSevered) return;
|
||||
if (limbJoint.IsSevered) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
//create the AI husk in a coroutine to ensure that we don't modify the character list while enumerating it
|
||||
CoroutineManager.StartCoroutine(CreateAIHusk(character));
|
||||
CoroutineManager.StartCoroutine(CreateAIHusk());
|
||||
}
|
||||
|
||||
private IEnumerable<object> CreateAIHusk(Character character)
|
||||
private IEnumerable<object> CreateAIHusk()
|
||||
{
|
||||
character.Enabled = false;
|
||||
Entity.Spawner.AddToRemoveQueue(character);
|
||||
|
||||
string speciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
|
||||
CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
string huskedSpeciesName = GetHuskedSpeciesName(character.SpeciesName, Prefab as AfflictionPrefabHusk);
|
||||
CharacterPrefab prefab = CharacterPrefab.FindBySpeciesName(huskedSpeciesName);
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to turn character \"" + character.Name + "\" into a husk - husk config file not found.");
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
var husk = Character.Create(speciesName, character.WorldPosition, character.Info.Name, character.Info, isRemotePlayer: false, hasAi: true, ragdoll: character.AnimController.RagdollParams);
|
||||
var husk = Character.Create(huskedSpeciesName, character.WorldPosition, ToolBox.RandomSeed(8), character.Info, isRemotePlayer: false, hasAi: true);
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
@@ -197,6 +200,11 @@ namespace Barotrauma
|
||||
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true, false, null);
|
||||
}
|
||||
|
||||
husk.SetStun(5);
|
||||
yield return new WaitForSeconds(5, false);
|
||||
#if CLIENT
|
||||
husk.PlaySound(CharacterSound.SoundType.Idle);
|
||||
#endif
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -791,8 +791,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
|
||||
/// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param>
|
||||
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
|
||||
|
||||
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
|
||||
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f)
|
||||
{
|
||||
//key = item identifier
|
||||
@@ -873,5 +872,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
|
||||
/// <summary>
|
||||
/// Automatically filters out buffs.
|
||||
/// </summary>
|
||||
public static IEnumerable<Affliction> SortAfflictionsBySeverity(IEnumerable<Affliction> afflictions) =>
|
||||
afflictions.Where(a => !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public static JobPrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(sync);
|
||||
public static JobPrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(p => p.Identifier != "watchman", sync);
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Barotrauma
|
||||
public enum LimbType
|
||||
{
|
||||
None, LeftHand, RightHand, LeftArm, RightArm, LeftForearm, RightForearm,
|
||||
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist
|
||||
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist, Jaw
|
||||
};
|
||||
|
||||
partial class LimbJoint : RevoluteJoint
|
||||
@@ -28,6 +28,8 @@ namespace Barotrauma
|
||||
public readonly Ragdoll ragdoll;
|
||||
public readonly Limb LimbA, LimbB;
|
||||
|
||||
public float Scale => Params.Scale * ragdoll.RagdollParams.JointScale;
|
||||
|
||||
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One)
|
||||
{
|
||||
Params = jointParams;
|
||||
@@ -59,15 +61,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (ragdoll.IsFlipped)
|
||||
{
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Params.Ragdoll.JointScale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Params.Ragdoll.JointScale);
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb1Anchor.X, Params.Limb1Anchor.Y) * Scale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-Params.Limb2Anchor.X, Params.Limb2Anchor.Y) * Scale);
|
||||
UpperLimit = MathHelper.ToRadians(-Params.LowerLimit);
|
||||
LowerLimit = MathHelper.ToRadians(-Params.UpperLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Params.Ragdoll.JointScale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Params.Ragdoll.JointScale);
|
||||
LocalAnchorA = ConvertUnits.ToSimUnits(Params.Limb1Anchor * Scale);
|
||||
LocalAnchorB = ConvertUnits.ToSimUnits(Params.Limb2Anchor * Scale);
|
||||
UpperLimit = MathHelper.ToRadians(Params.UpperLimit);
|
||||
LowerLimit = MathHelper.ToRadians(Params.LowerLimit);
|
||||
}
|
||||
@@ -125,9 +127,29 @@ namespace Barotrauma
|
||||
private Direction dir;
|
||||
|
||||
public int HealthIndex => Params.HealthIndex;
|
||||
public float Scale => Params.Ragdoll.LimbScale;
|
||||
public float Scale => Params.Scale * Params.Ragdoll.LimbScale;
|
||||
public float AttackPriority => Params.AttackPriority;
|
||||
public bool DoesFlip => Params.Flip;
|
||||
public bool DoesFlip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (character.AnimController.CurrentAnimationParams is GroundedMovementParams)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.LeftThigh:
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.RightLeg:
|
||||
case LimbType.RightThigh:
|
||||
// Legs always has to flip
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return Params.Flip;
|
||||
}
|
||||
}
|
||||
|
||||
public float SteerForce => Params.SteerForce;
|
||||
|
||||
@@ -654,33 +676,37 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 diff = attackSimPos - SimPosition;
|
||||
bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
|
||||
bool applyForces = !attack.ApplyForcesOnlyOnce || !wasRunning;
|
||||
if (applyForces)
|
||||
{
|
||||
|
||||
if (attack.ForceOnLimbIndices != null && attack.ForceOnLimbIndices.Count > 0)
|
||||
{
|
||||
foreach (int limbIndex in attack.ForceOnLimbIndices)
|
||||
{
|
||||
if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) continue;
|
||||
|
||||
if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) { continue; }
|
||||
Limb limb = character.AnimController.Limbs[limbIndex];
|
||||
limb.body.ApplyTorque(limb.Mass * character.AnimController.Dir * attack.Torque);
|
||||
diff = attackSimPos - limb.SimPosition;
|
||||
if (diff == Vector2.Zero) { continue; }
|
||||
limb.body.ApplyTorque(limb.Mass * character.AnimController.Dir * attack.Torque * limb.Params.AttackForceMultiplier);
|
||||
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * limb.Params.AttackForceMultiplier * Vector2.Normalize(diff), forcePos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (diff != Vector2.Zero)
|
||||
{
|
||||
body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque);
|
||||
body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque * Params.AttackForceMultiplier);
|
||||
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
|
||||
body.ApplyLinearImpulse(
|
||||
Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition),
|
||||
forcePos,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
body.ApplyLinearImpulse(Mass * attack.Force * Params.AttackForceMultiplier * Vector2.Normalize(diff), forcePos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
}
|
||||
Vector2 forceWorld = attack.CalculateAttackPhase(attack.RootTransitionEasing);
|
||||
forceWorld.X *= character.AnimController.Dir;
|
||||
character.AnimController.MainLimb.body.ApplyLinearImpulse(character.Mass * forceWorld, character.SimPosition, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
if (!attack.IsRunning)
|
||||
{
|
||||
// Set the main collider where the body lands after the attack
|
||||
character.AnimController.Collider.SetTransform(character.AnimController.MainLimb.body.SimPosition, rotation: 0);
|
||||
}
|
||||
return wasHit;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,15 @@ namespace Barotrauma
|
||||
[Serialize(false, true), Editable]
|
||||
public bool Humanoid { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HasInfo { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool Husk { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool UseHuskAppendage { get; private set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool NeedsAir { get; set; }
|
||||
|
||||
@@ -40,6 +46,9 @@ namespace Barotrauma
|
||||
[Serialize(100f, true, description: "How much noise the character makes when moving?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float Noise { get; set; }
|
||||
|
||||
[Serialize(100f, true, description: "How visible the character is?"), Editable(minValue: 0f, maxValue: 1000f)]
|
||||
public float Visibility { get; set; }
|
||||
|
||||
[Serialize("blood", true), Editable]
|
||||
public string BloodDecal { get; private set; }
|
||||
|
||||
@@ -450,6 +459,9 @@ namespace Barotrauma
|
||||
[Serialize(false, true, description: "Does the character try to break inside the sub?"), Editable()]
|
||||
public bool AggressiveBoarding { get; private set; }
|
||||
|
||||
[Serialize(true, true, description: "Enforce aggressive behavior if the creature is spawned as a target of a monster mission."), Editable()]
|
||||
public bool EnforceAggressiveBehaviorForMissions { get; private set; }
|
||||
|
||||
// TODO: latchonto, swarming
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
@@ -538,6 +550,9 @@ namespace Barotrauma
|
||||
[Serialize(0f, true, description: "Generic distance that can be used for different purposes depending on the state. Eg. in Avoid state this defines the distance that the character tries to keep to the target. If the distance is 0, it's not used."), Editable(MinValueFloat = 0, ValueStep = 10, DecimalCount = 0)]
|
||||
public float ReactDistance { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "Used for defining the attack distance for PassiveAggressive and Aggressive states. If the distance is 0, it's not used."), Editable(MinValueFloat = 0, ValueStep = 10, DecimalCount = 0)]
|
||||
public float AttackDistance { get; set; }
|
||||
|
||||
public TargetParams(XElement element, CharacterParams character) : base(element, character) { }
|
||||
|
||||
public TargetParams(string tag, AIState state, float priority, CharacterParams character) : base(CreateNewElement(tag, state, priority), character) { }
|
||||
|
||||
@@ -489,6 +489,9 @@ namespace Barotrauma
|
||||
[Serialize(0.25f, true), Editable]
|
||||
public float Stiffness { get; set; }
|
||||
|
||||
[Serialize(1f, true, description: "CAUTION: Not fully implemented. Only use for limb joints that connect non-animated limbs!"), Editable]
|
||||
public float Scale { get; set; }
|
||||
|
||||
public JointParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
}
|
||||
|
||||
@@ -591,6 +594,18 @@ namespace Barotrauma
|
||||
[Serialize("", true), Editable]
|
||||
public string Notes { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float ConstantTorque { get; set; }
|
||||
|
||||
[Serialize(0f, true), Editable]
|
||||
public float ConstantAngle { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable]
|
||||
public float Scale { get; set; }
|
||||
|
||||
[Serialize(1f, true), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float AttackForceMultiplier { get; set; }
|
||||
|
||||
// Non-editable ->
|
||||
[Serialize(0, true)]
|
||||
public int HealthIndex { get; set; }
|
||||
|
||||
@@ -460,12 +460,12 @@ namespace Barotrauma
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
var rootElement = doc.Root;
|
||||
var element = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;
|
||||
var ragdollFolder = RagdollParams.GetFolder(doc, file.Path);
|
||||
var ragdollFolder = RagdollParams.GetFolder(doc, file.Path).CleanUpPathCrossPlatform(true);
|
||||
if (Directory.Exists(ragdollFolder))
|
||||
{
|
||||
Directory.GetFiles(ragdollFolder, "*.xml").ForEach(f => filePaths.Add(f));
|
||||
}
|
||||
var animationFolder = AnimationParams.GetFolder(doc, file.Path);
|
||||
var animationFolder = AnimationParams.GetFolder(doc, file.Path).CleanUpPathCrossPlatform(true);
|
||||
if (Directory.Exists(animationFolder))
|
||||
{
|
||||
Directory.GetFiles(animationFolder, "*.xml").ForEach(f => filePaths.Add(f));
|
||||
|
||||
@@ -186,6 +186,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
if (handle.Thread == null)
|
||||
{
|
||||
if (handle.AbortRequested) { return true; }
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
|
||||
@@ -1249,6 +1249,13 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if !DEBUG
|
||||
if (!IsCommandPermitted(splitCommand[0].ToLowerInvariant(), GameMain.Client))
|
||||
{
|
||||
ThrowError("You're not permitted to use the command \"" + splitCommand[0].ToLowerInvariant() + "\"!");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1610,8 +1617,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.WriteLine(error);
|
||||
NewMessage(error, Color.Red);
|
||||
#if CLIENT
|
||||
if (listBox == null) { NewMessage(error, Color.Red); return; }
|
||||
|
||||
var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
|
||||
style: "InnerFrame", color: Color.White)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
|
||||
error, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true)
|
||||
{
|
||||
CanBeFocused = false,
|
||||
TextColor = Color.Red
|
||||
};
|
||||
textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
|
||||
textBlock.SetTextPos();
|
||||
|
||||
listBox.UpdateScrollBarSize();
|
||||
listBox.BarScroll = 1.0f;
|
||||
|
||||
if (createMessageBox)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(CreateMessageBox(error));
|
||||
@@ -1620,6 +1645,8 @@ namespace Barotrauma
|
||||
{
|
||||
isOpen = true;
|
||||
}
|
||||
#else
|
||||
NewMessage(error, Color.Red);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum TransitionMode
|
||||
{
|
||||
Linear,
|
||||
Smooth,
|
||||
Smoother,
|
||||
EaseIn,
|
||||
EaseOut,
|
||||
Exponential
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override int TeamCount
|
||||
{
|
||||
get { return 2; }
|
||||
}
|
||||
|
||||
public CombatMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
@@ -113,7 +118,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (wifiComponent.Item.Submarine == subs[i] || subs[i].DockedTo.Contains(wifiComponent.Item.Submarine))
|
||||
if (wifiComponent.Item.Submarine == subs[i] || subs[i].ConnectedDockingPorts.ContainsKey(wifiComponent.Item.Submarine))
|
||||
{
|
||||
wifiComponent.TeamID = subs[i].TeamID;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ namespace Barotrauma
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual int TeamCount
|
||||
{
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get { return Enumerable.Empty<Vector2>(); }
|
||||
@@ -136,7 +141,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.type)) != 0));
|
||||
allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.Type)) != 0));
|
||||
}
|
||||
|
||||
allowedMissions.RemoveAll(m => isSinglePlayer ? m.MultiplayerOnly : m.SingleplayerOnly);
|
||||
@@ -168,10 +173,9 @@ namespace Barotrauma
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual bool AssignTeamIDs(List<Networking.Client> clients)
|
||||
public virtual void AssignTeamIDs(List<Networking.Client> clients)
|
||||
{
|
||||
clients.ForEach(c => c.TeamID = Character.TeamType.Team1);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void ShowMessage(int missionState)
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
public readonly MissionType type;
|
||||
public readonly MissionType Type;
|
||||
|
||||
public readonly bool MultiplayerOnly, SingleplayerOnly;
|
||||
|
||||
@@ -154,18 +154,18 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
string missionTypeName = element.GetAttributeString("type", "");
|
||||
if (!Enum.TryParse(missionTypeName, out type))
|
||||
if (!Enum.TryParse(missionTypeName, out Type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
|
||||
return;
|
||||
}
|
||||
if (type == MissionType.None)
|
||||
if (Type == MissionType.None)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - mission type cannot be none.");
|
||||
return;
|
||||
}
|
||||
|
||||
constructor = missionClasses[type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
constructor = missionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace Barotrauma
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public Mission Instantiate(Location[] locations)
|
||||
{
|
||||
return constructor?.Invoke(new object[] { this, locations }) as Mission;
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -113,7 +110,25 @@ namespace Barotrauma
|
||||
|
||||
private void InitializeMonsters(IEnumerable<Character> monsters)
|
||||
{
|
||||
monsters.ForEach(m => m.Enabled = false);
|
||||
foreach (var monster in monsters)
|
||||
{
|
||||
monster.Enabled = false;
|
||||
if (monster.Params.AI.EnforceAggressiveBehaviorForMissions)
|
||||
{
|
||||
foreach (var targetParam in monster.Params.AI.Targets)
|
||||
{
|
||||
switch (targetParam.State)
|
||||
{
|
||||
case AIState.Avoid:
|
||||
case AIState.Escape:
|
||||
case AIState.Flee:
|
||||
case AIState.PassiveAggressive:
|
||||
targetParam.State = AIState.Attack;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
|
||||
foreach (Character monster in monsters)
|
||||
{
|
||||
@@ -191,7 +206,10 @@ namespace Barotrauma
|
||||
completed = true;
|
||||
}
|
||||
|
||||
public bool IsEliminated(Character enemy) => enemy.Removed || enemy.IsDead || enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
|
||||
|
||||
public bool IsEliminated(Character enemy) =>
|
||||
enemy == null ||
|
||||
enemy.Removed ||
|
||||
enemy.IsDead ||
|
||||
enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,11 @@ namespace Barotrauma.Extensions
|
||||
return source.Count(predicate) > 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<T> ToEnumerable<T>(this T item)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
// source: https://stackoverflow.com/questions/19237868/get-all-children-to-one-list-recursive-c-sharp
|
||||
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
|
||||
|
||||
@@ -150,14 +150,14 @@ namespace Barotrauma
|
||||
SaveUtil.LoadGame(SavePath);
|
||||
}
|
||||
|
||||
public void StartRound(string levelSeed, float? difficulty = null, bool loadSecondSub = false)
|
||||
public void StartRound(string levelSeed, float? difficulty = null)
|
||||
{
|
||||
Level randomLevel = Level.CreateRandom(levelSeed, difficulty);
|
||||
|
||||
StartRound(randomLevel, true, loadSecondSub);
|
||||
StartRound(randomLevel, true);
|
||||
}
|
||||
|
||||
public void StartRound(Level level, bool reloadSub = true, bool loadSecondSub = false, bool mirrorLevel = false)
|
||||
public void StartRound(Level level, bool reloadSub = true, bool mirrorLevel = false)
|
||||
{
|
||||
//make sure no status effects have been carried on from the next round
|
||||
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
|
||||
@@ -177,7 +177,7 @@ namespace Barotrauma
|
||||
|
||||
if (reloadSub || Submarine.MainSub != Submarine) { Submarine.Load(true); }
|
||||
Submarine.MainSub = Submarine;
|
||||
if (loadSecondSub)
|
||||
if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1)
|
||||
{
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
{
|
||||
|
||||
@@ -173,8 +173,8 @@ namespace Barotrauma.Items.Components
|
||||
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
|
||||
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
|
||||
|
||||
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine);
|
||||
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine);
|
||||
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
|
||||
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
|
||||
|
||||
DockingTarget = target;
|
||||
DockingTarget.DockingTarget = this;
|
||||
@@ -703,8 +703,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, 1.0f);
|
||||
|
||||
DockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
|
||||
item.Submarine.DockedTo.Remove(DockingTarget.item.Submarine);
|
||||
DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine);
|
||||
item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine);
|
||||
|
||||
if (door != null && DockingTarget.door != null)
|
||||
{
|
||||
|
||||
@@ -287,24 +287,21 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (!isBroken)
|
||||
if (isBroken) { return true; }
|
||||
bool hasRequiredItems = HasRequiredItems(character, false);
|
||||
if (HasAccess(character))
|
||||
{
|
||||
bool hasRequiredItems = HasRequiredItems(character, false);
|
||||
if (HasAccess(character))
|
||||
{
|
||||
float originalPickingTime = PickingTime;
|
||||
PickingTime = 0;
|
||||
ToggleState(ActionType.OnUse, character);
|
||||
PickingTime = originalPickingTime;
|
||||
}
|
||||
#if CLIENT
|
||||
else if (hasRequiredItems && character != null && character == Character.Controlled)
|
||||
{
|
||||
GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
|
||||
|
||||
}
|
||||
#endif
|
||||
float originalPickingTime = PickingTime;
|
||||
PickingTime = 0;
|
||||
ToggleState(ActionType.OnUse, character);
|
||||
PickingTime = originalPickingTime;
|
||||
}
|
||||
#if CLIENT
|
||||
else if (hasRequiredItems && character != null && character == Character.Controlled)
|
||||
{
|
||||
GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -578,13 +578,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
bool isAiming = false;
|
||||
var holdable = item.GetComponent<Holdable>();
|
||||
if (holdable != null)
|
||||
{
|
||||
isAiming = holdable.ControlPose;
|
||||
}
|
||||
sinTime = isAiming ? sinTime + deltaTime * 5 : 0;
|
||||
sinTime += deltaTime * 5;
|
||||
}
|
||||
// Press the trigger only when the tool is approximately facing the target.
|
||||
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
|
||||
|
||||
@@ -870,6 +870,7 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "requireditem":
|
||||
case "requireditems":
|
||||
RelatedItem newRequiredItem = RelatedItem.Load(subElement, returnEmptyRequirements, item.Name);
|
||||
if (newRequiredItem == null) continue;
|
||||
|
||||
|
||||
@@ -22,6 +22,30 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private ItemContainer inputContainer, outputContainer;
|
||||
|
||||
private enum FabricatorState
|
||||
{
|
||||
Active = 1,
|
||||
Paused = 2,
|
||||
Stopped = 0
|
||||
}
|
||||
|
||||
private FabricatorState state;
|
||||
private FabricatorState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return state;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (state == value) { return; }
|
||||
state = value;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public ItemContainer InputContainer
|
||||
{
|
||||
get { return inputContainer; }
|
||||
@@ -61,6 +85,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
state = FabricatorState.Stopped;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -147,12 +173,15 @@ namespace Barotrauma.Items.Components
|
||||
currPowerConsumption = powerConsumption;
|
||||
currPowerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
|
||||
|
||||
if (GameMain.NetworkMember?.IsServer ?? true)
|
||||
{
|
||||
State = FabricatorState.Active;
|
||||
}
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -180,12 +209,15 @@ namespace Barotrauma.Items.Components
|
||||
inputContainer.Inventory.Locked = false;
|
||||
outputContainer.Inventory.Locked = false;
|
||||
|
||||
if (GameMain.NetworkMember?.IsServer ?? true)
|
||||
{
|
||||
State = FabricatorState.Stopped;
|
||||
}
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -199,8 +231,25 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
|
||||
|
||||
hasPower = Voltage >= MinVoltage;
|
||||
if (!hasPower) { return; }
|
||||
if (GameMain.NetworkMember?.IsClient ?? false)
|
||||
{
|
||||
hasPower = State != FabricatorState.Paused;
|
||||
if (!hasPower)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hasPower = Voltage >= MinVoltage;
|
||||
|
||||
if (!hasPower)
|
||||
{
|
||||
State = FabricatorState.Paused;
|
||||
return;
|
||||
}
|
||||
State = FabricatorState.Active;
|
||||
}
|
||||
|
||||
var repairable = item.GetComponent<Repairable>();
|
||||
if (repairable != null)
|
||||
|
||||
@@ -37,23 +37,10 @@ namespace Barotrauma.Items.Components
|
||||
private float currFlow;
|
||||
public float CurrFlow
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (!IsActive) { return 0.0f; }
|
||||
return Math.Abs(currFlow);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get => base.IsActive;
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
if (!IsActive)
|
||||
{
|
||||
powerConsumption = 0;
|
||||
}
|
||||
return Math.Abs(currFlow);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ namespace Barotrauma.Items.Components
|
||||
private PrismaticJoint stickJoint;
|
||||
private Body stickTarget;
|
||||
|
||||
private Attack attack;
|
||||
private readonly Attack attack;
|
||||
|
||||
private Vector2 launchPos;
|
||||
|
||||
public List<Body> IgnoredBodies;
|
||||
|
||||
@@ -220,6 +222,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.Drop(null);
|
||||
|
||||
launchPos = item.SimPosition;
|
||||
|
||||
item.body.Enabled = true;
|
||||
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
@@ -415,7 +419,9 @@ namespace Barotrauma.Items.Components
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
|
||||
collisionCategory: Physics.CollisionWall);
|
||||
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure)
|
||||
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure &&
|
||||
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
|
||||
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
|
||||
{
|
||||
target = wallBody.FixtureList.First();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,12 @@ namespace Barotrauma.Items.Components
|
||||
Wire wire = wireItem.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
if (Item.ItemList.Any(it => it != item && (it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)))
|
||||
{
|
||||
if (wire.Item.body != null) { wire.Item.body.Enabled = false; }
|
||||
wire.IsActive = false;
|
||||
wire.UpdateSections();
|
||||
}
|
||||
DisconnectedWires.Add(wire);
|
||||
base.IsActive = true;
|
||||
}
|
||||
|
||||
@@ -272,13 +272,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateAITarget(AITarget target)
|
||||
{
|
||||
target.Enabled = IsActive;
|
||||
if (!IsActive) { return; }
|
||||
if (target.MaxSightRange <= 0)
|
||||
{
|
||||
target.MaxSightRange = Range * 5;
|
||||
}
|
||||
target.SightRange = target.MaxSightRange * lightBrightness;
|
||||
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
|
||||
}
|
||||
|
||||
partial void SetLightSourceState(bool enabled, float brightness);
|
||||
|
||||
@@ -374,8 +374,9 @@ namespace Barotrauma.Items.Components
|
||||
reload = reloadTime;
|
||||
|
||||
projectile.Drop(null);
|
||||
projectile.body.Dir = 1.0f;
|
||||
if (projectile.body == null) { return; }
|
||||
|
||||
projectile.body.Dir = 1.0f;
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.body.Enabled = true;
|
||||
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation);
|
||||
@@ -591,7 +592,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
if (e is Item projectileContainer) { CheckProjectileContainer(projectileContainer, projectiles, returnFirst); }
|
||||
if (returnFirst && projectiles.Any()) return projectiles;
|
||||
if (returnFirst && projectiles.Any()) { return projectiles; }
|
||||
}
|
||||
|
||||
return projectiles;
|
||||
@@ -600,15 +601,15 @@ namespace Barotrauma.Items.Components
|
||||
private void CheckProjectileContainer(Item projectileContainer, List<Projectile> projectiles, bool returnFirst)
|
||||
{
|
||||
var containedItems = projectileContainer.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
if (containedItems == null) { return; }
|
||||
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
var projectileComponent = containedItem.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
if (projectileComponent != null && projectileComponent.Item.body != null)
|
||||
{
|
||||
projectiles.Add(projectileComponent);
|
||||
if (returnFirst) return;
|
||||
if (returnFirst) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -617,10 +618,10 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item subContainedItem in containedItem.ContainedItems)
|
||||
{
|
||||
projectileComponent = subContainedItem.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
if (projectileComponent != null && projectileComponent.Item.body != null)
|
||||
{
|
||||
projectiles.Add(projectileComponent);
|
||||
if (returnFirst) return;
|
||||
if (returnFirst) { return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,7 +1343,7 @@ namespace Barotrauma
|
||||
UpdateTransform();
|
||||
if (CurrentHull == null && body.SimPosition.Y < ConvertUnits.ToSimUnits(Level.MaxEntityDepth))
|
||||
{
|
||||
Spawner.AddToRemoveQueue(this);
|
||||
Spawner?.AddToRemoveQueue(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace Barotrauma
|
||||
public void Save(XElement element)
|
||||
{
|
||||
element.Add(
|
||||
new XAttribute("identifiers", JoinedIdentifiers),
|
||||
new XAttribute("items", JoinedIdentifiers),
|
||||
new XAttribute("type", type.ToString()),
|
||||
new XAttribute("optional", IsOptional),
|
||||
new XAttribute("ignoreineditor", IgnoreInEditor));
|
||||
|
||||
@@ -310,6 +310,7 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
|
||||
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
|
||||
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
|
||||
minWidth = Math.Min(minWidth, maxWidth);
|
||||
}
|
||||
@@ -1622,7 +1623,7 @@ namespace Barotrauma
|
||||
EndOutpost = outpost;
|
||||
if (GameMain.GameSession?.EndLocation != null) { outpost.Name = GameMain.GameSession.EndLocation.Name; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsModeStartOutpostCompatible()
|
||||
|
||||
@@ -721,8 +721,10 @@ namespace Barotrauma.RuinGeneration
|
||||
|
||||
//doors create their own gaps, don't create an additional one if there's a door at this
|
||||
bool doorFound = false;
|
||||
foreach (Door door in doors)
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door == null) { continue; }
|
||||
if (Math.Abs(door.Item.WorldPosition.X - gapRect.Value.Center.X) < 5 &&
|
||||
Math.Abs(door.Item.WorldPosition.Y - gapRect.Value.Center.Y) < 5)
|
||||
{
|
||||
|
||||
@@ -105,15 +105,20 @@ namespace Barotrauma
|
||||
{
|
||||
LinkedSubmarine sl = new LinkedSubmarine(mainSub);
|
||||
sl.GenerateWallVertices(element);
|
||||
if (sl.wallVertices.Any())
|
||||
{
|
||||
sl.Rect = new Rectangle(
|
||||
(int)sl.wallVertices.Min(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Max(v => v.Y + position.Y),
|
||||
(int)sl.wallVertices.Max(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Min(v => v.Y + position.Y));
|
||||
|
||||
sl.Rect = new Rectangle(
|
||||
(int)sl.wallVertices.Min(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Max(v => v.Y + position.Y),
|
||||
(int)sl.wallVertices.Max(v => v.X + position.X),
|
||||
(int)sl.wallVertices.Min(v => v.Y + position.Y));
|
||||
|
||||
sl.rect = new Rectangle((int)position.X, (int)position.Y, 1, 1);
|
||||
|
||||
sl.Rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
sl.Rect = new Rectangle((int)position.X, (int)position.Y, 10, 10);
|
||||
}
|
||||
return sl;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace Barotrauma
|
||||
|
||||
protected string originalName;
|
||||
protected string identifier;
|
||||
protected ContentPackage contentPackage;
|
||||
|
||||
public Sprite sprite;
|
||||
|
||||
|
||||
@@ -77,7 +77,18 @@ namespace Barotrauma
|
||||
|
||||
private SubmarineBody subBody;
|
||||
|
||||
public readonly List<Submarine> DockedTo;
|
||||
public readonly Dictionary<Submarine, DockingPort> ConnectedDockingPorts;
|
||||
public IEnumerable<Submarine> DockedTo
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ConnectedDockingPorts == null) { yield break; }
|
||||
foreach (Submarine sub in ConnectedDockingPorts.Keys)
|
||||
{
|
||||
yield return sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 lastPickedPosition;
|
||||
private static float lastPickedFraction;
|
||||
@@ -442,7 +453,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
DockedTo = new List<Submarine>();
|
||||
ConnectedDockingPorts = new Dictionary<Submarine, DockingPort>();
|
||||
|
||||
FreeID();
|
||||
}
|
||||
@@ -561,27 +572,32 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns a rect that contains the borders of this sub and all subs docked to it
|
||||
/// </summary>
|
||||
public Rectangle GetDockedBorders()
|
||||
public Rectangle GetDockedBorders(List<Submarine> checkd=null)
|
||||
{
|
||||
Rectangle dockedBorders = Borders;
|
||||
dockedBorders.Y -= dockedBorders.Height;
|
||||
if (checkd == null) { checkd = new List<Submarine>(); }
|
||||
checkd.Add(this);
|
||||
|
||||
var connectedSubs = GetConnectedSubs();
|
||||
Rectangle dockedBorders = Borders;
|
||||
|
||||
var connectedSubs = DockedTo.Where(s => !checkd.Contains(s) && !s.IsOutpost).ToList();
|
||||
|
||||
foreach (Submarine dockedSub in connectedSubs)
|
||||
{
|
||||
if (dockedSub == this) continue;
|
||||
//use docking ports instead of world position to determine
|
||||
//borders, as world position will not necessarily match where
|
||||
//the subs are supposed to go
|
||||
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
|
||||
if (expectedLocation == null) { continue; }
|
||||
|
||||
Vector2 diff = dockedSub.Submarine == this ? dockedSub.WorldPosition : dockedSub.WorldPosition - WorldPosition;
|
||||
|
||||
Rectangle dockedSubBorders = dockedSub.Borders;
|
||||
dockedSubBorders.Y -= dockedSubBorders.Height;
|
||||
dockedSubBorders.Location += MathUtils.ToPoint(diff);
|
||||
Rectangle dockedSubBorders = dockedSub.GetDockedBorders(checkd);
|
||||
dockedSubBorders.Location += MathUtils.ToPoint(expectedLocation.Value);
|
||||
|
||||
dockedBorders.Y = -dockedBorders.Y;
|
||||
dockedSubBorders.Y = -dockedSubBorders.Y;
|
||||
dockedBorders = Rectangle.Union(dockedBorders, dockedSubBorders);
|
||||
dockedBorders.Y = -dockedBorders.Y;
|
||||
}
|
||||
|
||||
dockedBorders.Y += dockedBorders.Height;
|
||||
return dockedBorders;
|
||||
}
|
||||
|
||||
@@ -1131,25 +1147,37 @@ namespace Barotrauma
|
||||
prevPosition = position;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
public void SetPosition(Vector2 position, List<Submarine> checkd=null)
|
||||
{
|
||||
if (!MathUtils.IsValid(position)) return;
|
||||
|
||||
|
||||
if (checkd == null) { checkd = new List<Submarine>(); }
|
||||
if (checkd.Contains(this)) { return; }
|
||||
|
||||
checkd.Add(this);
|
||||
|
||||
subBody.SetPosition(position);
|
||||
|
||||
foreach (Submarine sub in loaded)
|
||||
foreach (Submarine dockedSub in DockedTo)
|
||||
{
|
||||
if (sub != this && sub.Submarine == this)
|
||||
{
|
||||
sub.SetPosition(position + sub.WorldPosition);
|
||||
sub.Submarine = null;
|
||||
}
|
||||
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
|
||||
if (expectedLocation == null) { continue; }
|
||||
|
||||
dockedSub.SetPosition(position + expectedLocation.Value, checkd);
|
||||
}
|
||||
//Level.Loaded.SetPosition(-position);
|
||||
//prevPosition = position;
|
||||
}
|
||||
|
||||
public static Vector2? CalculateDockOffset(Submarine sub, Submarine dockedSub)
|
||||
{
|
||||
Item myPort = sub.ConnectedDockingPorts.ContainsKey(dockedSub) ? sub.ConnectedDockingPorts[dockedSub].Item : null;
|
||||
if (myPort == null) { return null; }
|
||||
Item theirPort = dockedSub.ConnectedDockingPorts.ContainsKey(sub) ? dockedSub.ConnectedDockingPorts[sub].Item : null;
|
||||
if (theirPort == null) { return null; }
|
||||
return (myPort.Position - sub.HiddenSubPosition) - (theirPort.Position - dockedSub.HiddenSubPosition);
|
||||
}
|
||||
|
||||
public void Translate(Vector2 amount)
|
||||
{
|
||||
if (amount == Vector2.Zero || !MathUtils.IsValid(amount)) return;
|
||||
@@ -1733,7 +1761,7 @@ namespace Barotrauma
|
||||
if (MainSub == this) MainSub = null;
|
||||
if (MainSubs[1] == this) MainSubs[1] = null;
|
||||
|
||||
DockedTo?.Clear();
|
||||
ConnectedDockingPorts?.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -21,16 +21,27 @@ namespace Barotrauma.Networking
|
||||
private Character character;
|
||||
public Character Character
|
||||
{
|
||||
get { return character; }
|
||||
get
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && (character?.ID ?? 0) != CharacterID)
|
||||
{
|
||||
Character = Entity.FindEntityByID(CharacterID) as Character;
|
||||
}
|
||||
return character;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
if (value != null)
|
||||
{
|
||||
CharacterID = value.ID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value!=null)
|
||||
if (value != null)
|
||||
{
|
||||
DebugConsole.NewMessage(value.Name, Microsoft.Xna.Framework.Color.Yellow);
|
||||
}
|
||||
@@ -52,6 +63,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public UInt16 CharacterID;
|
||||
|
||||
private Vector2 spectate_position;
|
||||
public Vector2? SpectatePos
|
||||
{
|
||||
@@ -98,7 +111,22 @@ namespace Barotrauma.Networking
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool InGame;
|
||||
private bool inGame;
|
||||
public bool InGame
|
||||
{
|
||||
get
|
||||
{
|
||||
return inGame;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
}
|
||||
inGame = value;
|
||||
}
|
||||
}
|
||||
public bool HasSpawned; //has the client spawned as a character during the current round
|
||||
|
||||
private List<Client> kickVoters;
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Barotrauma.Networking
|
||||
RESPONSE_STARTGAME, //tell the server whether you're ready to start
|
||||
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
|
||||
|
||||
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
|
||||
|
||||
ERROR //tell the server that an error occurred
|
||||
}
|
||||
enum ClientNetObject
|
||||
@@ -60,6 +62,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
QUERY_STARTGAME, //ask the clients whether they're ready to start
|
||||
STARTGAME, //start a new round
|
||||
STARTGAMEFINALIZE, //finalize round initialization
|
||||
ENDGAME,
|
||||
|
||||
TRAITOR_MESSAGE,
|
||||
|
||||
@@ -159,8 +159,9 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) { return 0; }
|
||||
UInt64 retVal;
|
||||
if (str.StartsWith("STEAM64_", StringComparison.InvariantCultureIgnoreCase)) { str = str.Substring(8); }
|
||||
if (UInt64.TryParse(str, out retVal) && retVal >(1<<52)) { return retVal; }
|
||||
if (str.ToUpper().IndexOf("STEAM_") != 0) { return 0; }
|
||||
if (!str.StartsWith("STEAM_", StringComparison.InvariantCultureIgnoreCase)) { return 0; }
|
||||
string[] split = str.Substring(6).Split(':');
|
||||
if (split.Length != 3) { return 0; }
|
||||
|
||||
@@ -179,7 +180,11 @@ namespace Barotrauma.Steam
|
||||
UInt64 accountNumber = (uint64 >> 1) & 0x7fffffff;
|
||||
UInt64 universe = (uint64 >> 56) & 0xff;
|
||||
|
||||
return "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
|
||||
string retVal = "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
|
||||
|
||||
if (SteamIDStringToUInt64(retVal) != uint64) { return "STEAM64_" + uint64.ToString(); }
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Takes flipping (Dir) into account.
|
||||
/// </summary>
|
||||
public float TransformedRotation => Dir < 0 ? Rotation - MathHelper.Pi : Rotation;
|
||||
public float TransformedRotation => TransformRotation(Rotation, Dir);
|
||||
|
||||
public static float TransformRotation(float rot, float dir) => dir < 0 ? rot - MathHelper.Pi : rot;
|
||||
|
||||
public Vector2 LinearVelocity
|
||||
{
|
||||
@@ -351,9 +353,9 @@ namespace Barotrauma
|
||||
|
||||
public PhysicsBody(LimbParams limbParams, Vector2 position)
|
||||
{
|
||||
float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Ragdoll.LimbScale;
|
||||
float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Ragdoll.LimbScale;
|
||||
float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Ragdoll.LimbScale;
|
||||
float radius = ConvertUnits.ToSimUnits(limbParams.Radius) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
|
||||
float height = ConvertUnits.ToSimUnits(limbParams.Height) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
|
||||
float width = ConvertUnits.ToSimUnits(limbParams.Width) * limbParams.Scale * limbParams.Ragdoll.LimbScale;
|
||||
density = limbParams.Density;
|
||||
CreateBody(width, height, radius, density);
|
||||
FarseerBody.BodyType = BodyType.Dynamic;
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class DelayedListElement
|
||||
{
|
||||
public DelayedEffect Parent;
|
||||
public Entity Entity;
|
||||
public Vector2? WorldPosition;
|
||||
public List<ISerializableEntity> Targets;
|
||||
public readonly DelayedEffect Parent;
|
||||
public readonly Entity Entity;
|
||||
public readonly Vector2? WorldPosition;
|
||||
public readonly List<ISerializableEntity> Targets;
|
||||
public float StartTimer;
|
||||
|
||||
public DelayedListElement(DelayedEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float delay, Vector2? worldPosition)
|
||||
{
|
||||
Parent = parentEffect;
|
||||
Entity = parentEntity;
|
||||
Targets = new List<ISerializableEntity>(targets);
|
||||
StartTimer = delay;
|
||||
WorldPosition = worldPosition;
|
||||
}
|
||||
}
|
||||
class DelayedEffect : StatusEffect
|
||||
{
|
||||
@@ -27,28 +37,18 @@ namespace Barotrauma
|
||||
|
||||
public override void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
|
||||
{
|
||||
if (this.type != type || !HasRequiredItems(entity)) return;
|
||||
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.FirstOrDefault() == target)) return;
|
||||
|
||||
if (targetIdentifiers != null && !IsValidTarget(target)) return;
|
||||
if (!HasRequiredConditions(new List<ISerializableEntity>() { target })) return;
|
||||
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
||||
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.FirstOrDefault() == target)) { return; }
|
||||
if (targetIdentifiers != null && !IsValidTarget(target)) { return; }
|
||||
if (!HasRequiredConditions(target.ToEnumerable())) { return; }
|
||||
|
||||
DelayedListElement element = new DelayedListElement
|
||||
{
|
||||
Parent = this,
|
||||
StartTimer = delay,
|
||||
Entity = entity,
|
||||
WorldPosition = worldPosition,
|
||||
Targets = new List<ISerializableEntity>() { target }
|
||||
};
|
||||
|
||||
DelayList.Add(element);
|
||||
DelayList.Add(new DelayedListElement(this, entity, target.ToEnumerable(), delay, worldPosition));
|
||||
}
|
||||
|
||||
public override void Apply(ActionType type, float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||
{
|
||||
if (this.type != type || !HasRequiredItems(entity)) return;
|
||||
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) return;
|
||||
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
||||
if (!Stackable && DelayList.Any(d => d.Parent == this && d.Targets.SequenceEqual(targets))) { return; }
|
||||
|
||||
currentTargets.Clear();
|
||||
foreach (ISerializableEntity target in targets)
|
||||
@@ -61,18 +61,9 @@ namespace Barotrauma
|
||||
currentTargets.Add(target);
|
||||
}
|
||||
|
||||
if (!HasRequiredConditions(currentTargets)) return;
|
||||
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
DelayedListElement element = new DelayedListElement
|
||||
{
|
||||
Parent = this,
|
||||
StartTimer = delay,
|
||||
Entity = entity,
|
||||
WorldPosition = worldPosition,
|
||||
Targets = currentTargets
|
||||
};
|
||||
|
||||
DelayList.Add(element);
|
||||
DelayList.Add(new DelayedListElement(this, entity, currentTargets, delay, worldPosition));
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
@@ -88,7 +79,7 @@ namespace Barotrauma
|
||||
|
||||
element.StartTimer -= deltaTime;
|
||||
|
||||
if (element.StartTimer > 0.0f) continue;
|
||||
if (element.StartTimer > 0.0f) { continue; }
|
||||
|
||||
element.Parent.Apply(1.0f, element.Entity, element.Targets, element.WorldPosition);
|
||||
DelayList.Remove(element);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,11 +10,27 @@ namespace Barotrauma
|
||||
{
|
||||
class DurationListElement
|
||||
{
|
||||
public StatusEffect Parent;
|
||||
public Entity Entity;
|
||||
public List<ISerializableEntity> Targets;
|
||||
public readonly StatusEffect Parent;
|
||||
public readonly Entity Entity;
|
||||
public readonly List<ISerializableEntity> Targets;
|
||||
public Character User { get; private set; }
|
||||
|
||||
public float Timer;
|
||||
public Character User;
|
||||
|
||||
public DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float duration, Character user)
|
||||
{
|
||||
Parent = parentEffect;
|
||||
Entity = parentEntity;
|
||||
Targets = new List<ISerializableEntity>(targets);
|
||||
Timer = duration;
|
||||
User = user;
|
||||
}
|
||||
|
||||
public void Reset(float duration, Character newUser)
|
||||
{
|
||||
Timer = duration;
|
||||
User = newUser;
|
||||
}
|
||||
}
|
||||
|
||||
partial class StatusEffect
|
||||
@@ -476,10 +493,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool HasRequiredConditions(List<ISerializableEntity> targets)
|
||||
public virtual bool HasRequiredConditions(IEnumerable<ISerializableEntity> targets)
|
||||
{
|
||||
if (!propertyConditionals.Any()) { return true; }
|
||||
if (requiredItems.Any() && requiredItems.All(ri => ri.MatchOnEmpty) && targets.Count == 0) { return true; }
|
||||
if (requiredItems.Any() && requiredItems.All(ri => ri.MatchOnEmpty) && targets.Count() == 0) { return true; }
|
||||
switch (conditionalComparison)
|
||||
{
|
||||
case PropertyConditional.Comparison.Or:
|
||||
@@ -560,33 +577,26 @@ namespace Barotrauma
|
||||
|
||||
public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition = null)
|
||||
{
|
||||
if (this.type != type || !HasRequiredItems(entity)) return;
|
||||
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
||||
|
||||
if (targetIdentifiers != null && !IsValidTarget(target)) return;
|
||||
if (targetIdentifiers != null && !IsValidTarget(target)) { return; }
|
||||
|
||||
if (duration > 0.0f && !Stackable)
|
||||
{
|
||||
//ignore if not stackable and there's already an identical statuseffect
|
||||
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.FirstOrDefault() == target);
|
||||
if (existingEffect != null)
|
||||
{
|
||||
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
|
||||
existingEffect.User = user;
|
||||
return;
|
||||
}
|
||||
existingEffect?.Reset(Math.Max(existingEffect.Timer, duration), user);
|
||||
return;
|
||||
}
|
||||
|
||||
List<ISerializableEntity> targets = new List<ISerializableEntity> { target };
|
||||
|
||||
if (!HasRequiredConditions(targets)) return;
|
||||
|
||||
Apply(deltaTime, entity, targets, worldPosition);
|
||||
if (!HasRequiredConditions(target.ToEnumerable())) { return; }
|
||||
Apply(deltaTime, entity, target.ToEnumerable(), worldPosition);
|
||||
}
|
||||
|
||||
protected readonly List<ISerializableEntity> currentTargets = new List<ISerializableEntity>();
|
||||
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||
{
|
||||
if (this.type != type) return;
|
||||
if (this.type != type) { return; }
|
||||
|
||||
currentTargets.Clear();
|
||||
foreach (ISerializableEntity target in targets)
|
||||
@@ -601,7 +611,7 @@ namespace Barotrauma
|
||||
|
||||
if (targetIdentifiers != null && currentTargets.Count == 0) { return; }
|
||||
|
||||
if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) return;
|
||||
if (!HasRequiredItems(entity) || !HasRequiredConditions(currentTargets)) { return; }
|
||||
|
||||
if (duration > 0.0f && !Stackable)
|
||||
{
|
||||
@@ -609,8 +619,7 @@ namespace Barotrauma
|
||||
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(currentTargets));
|
||||
if (existingEffect != null)
|
||||
{
|
||||
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
|
||||
existingEffect.User = user;
|
||||
existingEffect?.Reset(Math.Max(existingEffect.Timer, duration), user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -618,7 +627,7 @@ namespace Barotrauma
|
||||
Apply(deltaTime, entity, currentTargets, worldPosition);
|
||||
}
|
||||
|
||||
protected void Apply(float deltaTime, Entity entity, List<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||
protected void Apply(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||
{
|
||||
if (lifeTime > 0)
|
||||
{
|
||||
@@ -685,16 +694,7 @@ namespace Barotrauma
|
||||
|
||||
if (duration > 0.0f)
|
||||
{
|
||||
DurationListElement element = new DurationListElement
|
||||
{
|
||||
Parent = this,
|
||||
Timer = duration,
|
||||
Entity = entity,
|
||||
Targets = targets,
|
||||
User = user
|
||||
};
|
||||
|
||||
DurationList.Add(element);
|
||||
DurationList.Add(new DurationListElement(this, entity, targets, duration, user));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -732,7 +732,7 @@ namespace Barotrauma
|
||||
character.LastDamageSource = entity;
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
limb.character.DamageLimb(position, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
|
||||
limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
|
||||
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability);
|
||||
//only apply non-limb-specific afflictions to the first limb
|
||||
if (!affliction.Prefab.LimbSpecific) { break; }
|
||||
@@ -741,7 +741,7 @@ namespace Barotrauma
|
||||
else if (target is Limb limb)
|
||||
{
|
||||
if (limb.character.Removed || limb.Removed) { continue; }
|
||||
limb.character.DamageLimb(position, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
|
||||
limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source);
|
||||
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability);
|
||||
}
|
||||
}
|
||||
@@ -853,7 +853,7 @@ namespace Barotrauma
|
||||
ApplyProjSpecific(deltaTime, entity, targets, hull, position);
|
||||
}
|
||||
|
||||
partial void ApplyProjSpecific(float deltaTime, Entity entity, List<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition);
|
||||
partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition);
|
||||
|
||||
private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime)
|
||||
{
|
||||
@@ -934,12 +934,12 @@ namespace Barotrauma
|
||||
if (target is Character character)
|
||||
{
|
||||
if (character.Removed) { continue; }
|
||||
character.AddDamage(character.WorldPosition, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attacker: element.User);
|
||||
character.AddDamage(character.WorldPosition, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User);
|
||||
}
|
||||
else if (target is Limb limb)
|
||||
{
|
||||
if (limb.character.Removed || limb.Removed) { continue; }
|
||||
limb.character.DamageLimb(limb.WorldPosition, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
|
||||
limb.character.DamageLimb(limb.WorldPosition, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,12 @@ namespace Barotrauma
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (textPacks == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to get the text \"{textTag}\" (no text packs loaded).");
|
||||
return textTag;
|
||||
}
|
||||
|
||||
if (!textPacks.ContainsKey(Language))
|
||||
{
|
||||
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
|
||||
|
||||
@@ -118,6 +118,10 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < subDirs.Length; i++)
|
||||
{
|
||||
if (i == subDirs.Length - 1 && string.IsNullOrEmpty(subDirs[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
string enumPath = string.IsNullOrEmpty(filename) ? "./" : filename;
|
||||
List<string> filePaths = Directory.GetFileSystemEntries(enumPath).Select(s => Path.GetFileName(s)).ToList();
|
||||
if (filePaths.Any(s => s.Equals(subDirs[i], StringComparison.Ordinal)))
|
||||
@@ -571,5 +575,19 @@ namespace Barotrauma
|
||||
#endif
|
||||
return path;
|
||||
}
|
||||
|
||||
public static float GetEasing(TransitionMode easing, float t)
|
||||
{
|
||||
return easing switch
|
||||
{
|
||||
TransitionMode.Smooth => MathUtils.SmoothStep(t),
|
||||
TransitionMode.Smoother => MathUtils.SmootherStep(t),
|
||||
TransitionMode.EaseIn => MathUtils.EaseIn(t),
|
||||
TransitionMode.EaseOut => MathUtils.EaseOut(t),
|
||||
TransitionMode.Exponential => t * t,
|
||||
TransitionMode.Linear => t,
|
||||
_ => t,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user