Faction Test v1.0.1.0
This commit is contained in:
@@ -352,21 +352,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
|
||||
{
|
||||
if (myTeam == otherTeam) { return true; }
|
||||
return myTeam switch
|
||||
{
|
||||
// NPCs are friendly to the same team and the friendly NPCs
|
||||
CharacterTeamType.None or CharacterTeamType.Team1 or CharacterTeamType.Team2 => otherTeam == CharacterTeamType.FriendlyNPC,
|
||||
// Friendly NPCs are friendly to both player teams
|
||||
CharacterTeamType.FriendlyNPC => otherTeam == CharacterTeamType.Team1 || otherTeam == CharacterTeamType.Team2,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(Character me, Character other) => IsOnFriendlyTeam(me.TeamID, other.TeamID);
|
||||
|
||||
public void ReequipUnequipped()
|
||||
{
|
||||
foreach (var item in unequippedItems)
|
||||
@@ -460,7 +445,7 @@ namespace Barotrauma
|
||||
if (EscapeTarget != null)
|
||||
{
|
||||
var door = EscapeTarget.ConnectedDoor;
|
||||
bool isClosedDoor = door != null && !door.IsOpen;
|
||||
bool isClosedDoor = door != null && door.IsClosed;
|
||||
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
|
||||
float sqrDist = diff.LengthSquared();
|
||||
bool isClose = sqrDist < MathUtils.Pow2(100);
|
||||
|
||||
@@ -206,13 +206,19 @@ namespace Barotrauma
|
||||
private set;
|
||||
} = new HashSet<Submarine>();
|
||||
|
||||
public bool IsTargetingPlayerTeam => IsTargetInPlayerTeam(SelectedAiTarget);
|
||||
public static bool IsTargetBeingChasedBy(Character target, Character character)
|
||||
=> character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && (enemyAI.State == AIState.Attack || enemyAI.State == AIState.Aggressive);
|
||||
public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c);
|
||||
private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character);
|
||||
|
||||
private bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
|
||||
private static bool IsTargetInPlayerTeam(AITarget target) => target?.Entity?.Submarine != null && target.Entity.Submarine.Info.IsPlayer || target?.Entity is Character targetCharacter && targetCharacter.IsOnPlayerTeam;
|
||||
|
||||
private bool IsAttackingOwner(Character other) =>
|
||||
PetBehavior != null && PetBehavior.Owner != null &&
|
||||
!other.IsUnconscious && !other.IsArrested &&
|
||||
other.AIController is HumanAIController humanAI &&
|
||||
humanAI.ObjectiveManager.CurrentObjective is AIObjectiveCombat combat &&
|
||||
combat.Enemy != null && combat.Enemy == PetBehavior.Owner;
|
||||
|
||||
private bool reverse;
|
||||
public bool Reverse
|
||||
@@ -308,11 +314,10 @@ namespace Barotrauma
|
||||
insideSteering = new IndoorsSteeringManager(this, Character.Params.AI.CanOpenDoors, canAttackDoors);
|
||||
steeringManager = outsideSteering;
|
||||
State = AIState.Idle;
|
||||
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
|
||||
myBodies.Add(Character.AnimController.Collider.FarseerBody);
|
||||
CreatureMetrics.UnlockInEditor(Character.SpeciesName);
|
||||
}
|
||||
|
||||
private CharacterParams.AIParams _aiParams;
|
||||
@@ -354,7 +359,7 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "owner";
|
||||
}
|
||||
else if (targetCharacter.AIController is HumanAIController && !IsOnFriendlyTeam(Character, targetCharacter))
|
||||
else if (PetBehavior != null && (!Character.IsOnFriendlyTeam(targetCharacter) || IsAttackingOwner(targetCharacter)))
|
||||
{
|
||||
targetingTag = "hostile";
|
||||
}
|
||||
@@ -683,19 +688,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
bool IsValid(Character.Attacker a)
|
||||
bool ShouldRetaliate(Character.Attacker a)
|
||||
{
|
||||
Character c = a.Character;
|
||||
if (c.IsDead || c.Removed) { return false; }
|
||||
if (!Character.IsFriendly(c)) { return true; }
|
||||
if (!c.IsPlayer) { return false; }
|
||||
// Only apply the threshold to players
|
||||
return a.Damage >= selectedTargetingParams.Threshold;
|
||||
if (c == null || c.IsUnconscious || c.Removed) { return false; }
|
||||
// Can't target characters of same species/group because that would make us hostile to all friendly characters in the same species/group.
|
||||
if (Character.IsSameSpeciesOrGroup(c)) { return false; }
|
||||
if (targetCharacter.IsSameSpeciesOrGroup(c)) { return false; }
|
||||
if (c.IsPlayer || Character.IsOnFriendlyTeam(c))
|
||||
{
|
||||
return a.Damage >= selectedTargetingParams.Threshold;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
|
||||
if (attacker?.AiTarget != null && !Character.IsSameSpeciesOrGroup(attacker) && !targetCharacter.IsSameSpeciesOrGroup(attacker))
|
||||
Character attacker = targetCharacter.LastAttackers.LastOrDefault(ShouldRetaliate)?.Character;
|
||||
if (attacker?.AiTarget != null)
|
||||
{
|
||||
// Can't retaliate on characters of same species or group because that would make us hostile to all friendly characters in the same group.
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
State = AIState.Attack;
|
||||
@@ -869,7 +877,7 @@ namespace Barotrauma
|
||||
var pathSteering = SteeringManager as IndoorsSteeringManager;
|
||||
if (pathSteering == null)
|
||||
{
|
||||
if (SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f))
|
||||
if (Level.Loaded != null && Level.Loaded.GetRealWorldDepth(WorldPosition.Y) > Character.CharacterHealth.CrushDepth * 0.75f)
|
||||
{
|
||||
// Steer straight up if very deep
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.UnitY);
|
||||
@@ -1503,7 +1511,7 @@ namespace Barotrauma
|
||||
{
|
||||
hitTarget = limb.character;
|
||||
}
|
||||
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
|
||||
if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget) && !IsAttackingOwner(hitTarget))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2413,7 +2421,7 @@ namespace Barotrauma
|
||||
{
|
||||
t = limb.character;
|
||||
}
|
||||
if (t != null && (t == target || !Character.IsFriendly(t)))
|
||||
if (t != null && (t == target || (!Character.IsFriendly(t) || IsAttackingOwner(t))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -3062,7 +3070,8 @@ namespace Barotrauma
|
||||
// In the attack state allow going into non-allowed zone only when chasing a target.
|
||||
if (State == targetParams.State && SelectedAiTarget == aiTarget) { break; }
|
||||
}
|
||||
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
bool insideSameSub = aiTarget?.Entity?.Submarine != null && aiTarget.Entity.Submarine == Character.Submarine;
|
||||
if (!insideSameSub && !IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
{
|
||||
// If we have recently been damaged by the target (or another player/bot in the same team) allow targeting it even when we are in the idle state.
|
||||
bool isTargetInPlayerTeam = IsTargetInPlayerTeam(aiTarget);
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace Barotrauma
|
||||
UseIndoorSteeringOutside = false;
|
||||
}
|
||||
|
||||
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID))
|
||||
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !Character.IsOnFriendlyTeam(Character.Submarine.TeamID))
|
||||
{
|
||||
// Spot enemies while staying outside or inside an enemy ship.
|
||||
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
|
||||
@@ -444,7 +444,7 @@ namespace Barotrauma
|
||||
if (objectiveManager.CurrentObjective == null) { return; }
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
bool run = objectiveManager.CurrentObjective.ForceRun || !objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
|
||||
bool run = (objectiveManager.CurrentObjective.ForceRun && !objectiveManager.CurrentObjective.ForceWalk) || (!objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority);
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
|
||||
{
|
||||
if (Character.CurrentHull == null)
|
||||
@@ -541,12 +541,12 @@ namespace Barotrauma
|
||||
if (Character.LockHands) { return; }
|
||||
if (ObjectiveManager.CurrentObjective == null) { return; }
|
||||
if (Character.CurrentHull == null) { return; }
|
||||
bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold && Character.NeedsOxygen;
|
||||
bool shouldActOnSuffocation = Character.IsLowInOxygen && !Character.AnimController.HeadInWater && HasDivingSuit(Character, requireOxygenTank: false) && !HasItem(Character, AIObjectiveFindDivingGear.OXYGEN_SOURCE, out _, conditionPercentage: 1);
|
||||
bool isCarrying = ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>();
|
||||
|
||||
bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective)
|
||||
{
|
||||
if (!Character.NeedsAir) { return false; }
|
||||
if (Character.IsImmuneToPressure) { return false; }
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
Hull targetHull = gotoObjective.GetTargetHull();
|
||||
return gotoObjective.Target != null && targetHull == null ||
|
||||
@@ -566,17 +566,17 @@ namespace Barotrauma
|
||||
gotoObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
if (!oxygenLow)
|
||||
if (!shouldActOnSuffocation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Diving gear
|
||||
if (oxygenLow || findItemState != FindItemState.OtherItem)
|
||||
if (shouldActOnSuffocation || findItemState != FindItemState.OtherItem)
|
||||
{
|
||||
bool needsGear = NeedsDivingGear(Character.CurrentHull, out _);
|
||||
if (!needsGear || oxygenLow)
|
||||
if (!needsGear || shouldActOnSuffocation)
|
||||
{
|
||||
bool isCurrentObjectiveFindSafety = ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>();
|
||||
bool shouldKeepTheGearOn =
|
||||
@@ -584,21 +584,21 @@ namespace Barotrauma
|
||||
Character.AnimController.InWater ||
|
||||
Character.AnimController.HeadInWater ||
|
||||
Character.Submarine == null ||
|
||||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
|
||||
(!Character.IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) ||
|
||||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOnAlsoWhenInactive) ||
|
||||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10 ||
|
||||
Character.CurrentHull.IsWetRoom;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
|
||||
bool removeDivingSuit = !shouldKeepTheGearOn && !IsOrderedToWait();
|
||||
if (oxygenLow && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
|
||||
if (shouldActOnSuffocation && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
|
||||
{
|
||||
shouldKeepTheGearOn = false;
|
||||
// Remove the suit before we pass out
|
||||
removeDivingSuit = true;
|
||||
}
|
||||
bool takeMaskOff = !shouldKeepTheGearOn;
|
||||
if (!shouldKeepTheGearOn && !oxygenLow)
|
||||
if (!shouldKeepTheGearOn && !shouldActOnSuffocation)
|
||||
{
|
||||
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
|
||||
{
|
||||
@@ -621,9 +621,10 @@ namespace Barotrauma
|
||||
}
|
||||
else if (gotoObjective.Mimic)
|
||||
{
|
||||
bool targetHasDivingGear = HasDivingGear(gotoObjective.Target as Character, requireOxygenTank: false);
|
||||
if (!removeSuit)
|
||||
{
|
||||
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
|
||||
removeDivingSuit = !targetHasDivingGear;
|
||||
if (removeDivingSuit)
|
||||
{
|
||||
removeSuit = true;
|
||||
@@ -631,7 +632,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!removeMask)
|
||||
{
|
||||
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
|
||||
takeMaskOff = !targetHasDivingGear;
|
||||
if (takeMaskOff)
|
||||
{
|
||||
removeMask = true;
|
||||
@@ -647,7 +648,7 @@ namespace Barotrauma
|
||||
var divingSuit = Character.Inventory.FindItemByTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR);
|
||||
if (divingSuit != null && !divingSuit.HasTag(AIObjectiveFindDivingGear.DIVING_GEAR_WEARABLE_INDOORS))
|
||||
{
|
||||
if (oxygenLow || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
if (shouldActOnSuffocation || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
divingSuit.Drop(Character);
|
||||
HandleRelocation(divingSuit);
|
||||
@@ -783,20 +784,23 @@ namespace Barotrauma
|
||||
|
||||
private void HandleRelocation(Item item)
|
||||
{
|
||||
if (item.Submarine?.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (item.SpawnedInCurrentOutpost) { return; }
|
||||
if (item.Submarine == null) { return; }
|
||||
// Only affects bots in the player team
|
||||
if (!Character.IsOnPlayerTeam) { return; }
|
||||
// Don't relocate if the item is on a sub of the same team
|
||||
if (item.Submarine.TeamID == Character.TeamID) { return; }
|
||||
if (itemsToRelocate.Contains(item)) { return; }
|
||||
itemsToRelocate.Add(item);
|
||||
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
|
||||
{
|
||||
if (itemsToRelocate.Contains(item)) { return; }
|
||||
itemsToRelocate.Add(item);
|
||||
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
|
||||
{
|
||||
myPort.OnUnDocked += Relocate;
|
||||
}
|
||||
var campaign = GameMain.GameSession.Campaign;
|
||||
if (campaign != null)
|
||||
{
|
||||
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
|
||||
campaign.BeforeLevelLoading += Relocate;
|
||||
}
|
||||
myPort.OnUnDocked += Relocate;
|
||||
}
|
||||
var campaign = GameMain.GameSession.Campaign;
|
||||
if (campaign != null)
|
||||
{
|
||||
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
|
||||
campaign.BeforeLevelLoading += Relocate;
|
||||
}
|
||||
|
||||
void Relocate()
|
||||
@@ -982,15 +986,15 @@ namespace Barotrauma
|
||||
if (target.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRescueAll.IsValidTarget(target, Character))
|
||||
{
|
||||
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && (!Character.IsMedic || Character == target) && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
var orderPrefab = OrderPrefab.Prefabs["requestfirstaid"];
|
||||
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
|
||||
targetHull = hull;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item item in Item.ItemList)
|
||||
}
|
||||
foreach (Item item in Item.RepairableItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
@@ -1161,7 +1165,7 @@ namespace Barotrauma
|
||||
freezeAI = true;
|
||||
}
|
||||
}
|
||||
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||
if (attacker == null || attacker.IsUnconscious || attacker.Removed)
|
||||
{
|
||||
// Don't react to the damage if there's no attacker.
|
||||
// We might consider launching the retreat combat objective in some cases, so that the bot does not just stand somewhere getting damaged and dying.
|
||||
@@ -1199,7 +1203,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
float cumulativeDamage = realDamage + Character.GetDamageDoneByAttacker(attacker);
|
||||
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null;
|
||||
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && attacker.CombatAction == null;
|
||||
if (isAccidental)
|
||||
{
|
||||
if (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold)
|
||||
@@ -1209,7 +1213,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.AlienInfectedType) > 0;
|
||||
// Inform other NPCs
|
||||
if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold)
|
||||
{
|
||||
@@ -1279,7 +1283,7 @@ namespace Barotrauma
|
||||
if (otherCharacter.Submarine != attacker.Submarine) { continue; }
|
||||
if (otherCharacter.Info?.Job == null || otherCharacter.IsInstigator) { continue; }
|
||||
if (otherCharacter.IsPlayer) { continue; }
|
||||
if (!(otherCharacter.AIController is HumanAIController otherHumanAI)) { continue; }
|
||||
if (otherCharacter.AIController is not HumanAIController otherHumanAI) { continue; }
|
||||
if (!otherHumanAI.IsFriendly(Character)) { continue; }
|
||||
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
|
||||
if (!isWitnessing)
|
||||
@@ -1299,7 +1303,7 @@ namespace Barotrauma
|
||||
|
||||
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage = 0, bool isWitnessing = false)
|
||||
{
|
||||
if (!(c.AIController is HumanAIController humanAI)) { return AIObjectiveCombat.CombatMode.None; }
|
||||
if (c.AIController is not HumanAIController humanAI) { return AIObjectiveCombat.CombatMode.None; }
|
||||
if (!IsFriendly(attacker))
|
||||
{
|
||||
if (c.Submarine == null)
|
||||
@@ -1327,7 +1331,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (attacker.IsPlayer && c.TeamID == attacker.TeamID)
|
||||
{
|
||||
if (GameMain.IsSingleplayer || Character.TeamID != attacker.TeamID)
|
||||
if (GameMain.IsSingleplayer || c.TeamID != attacker.TeamID)
|
||||
{
|
||||
// Bots in the player team never act aggressively in single player when attacked by the player
|
||||
// In multiplayer, they react only to players attacking them or other crew members
|
||||
@@ -1345,11 +1349,11 @@ namespace Barotrauma
|
||||
isAttackerFightingEnemy = true;
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
if (isWitnessing && Character.CombatAction != null && !c.IsSecurity)
|
||||
if (isWitnessing && c.CombatAction != null && !c.IsSecurity)
|
||||
{
|
||||
return Character.CombatAction.WitnessReaction;
|
||||
return c.CombatAction.WitnessReaction;
|
||||
}
|
||||
if (attacker.IsPlayer && FindInstigator() is Character instigator)
|
||||
if (!attacker.IsInstigator && c.IsOnFriendlyTeam(attacker) && FindInstigator() is Character instigator)
|
||||
{
|
||||
// The guards don't react to player's aggressions when there's an instigator around
|
||||
isAttackerFightingEnemy = true;
|
||||
@@ -1359,11 +1363,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
{
|
||||
return Character.CombatAction != null ? Character.CombatAction.GuardReaction : AIObjectiveCombat.CombatMode.None;
|
||||
return attacker.CombatAction != null ? attacker.CombatAction.GuardReaction : AIObjectiveCombat.CombatMode.Offensive;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Character.CombatAction != null ? Character.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.None;
|
||||
return attacker.CombatAction != null ? attacker.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1546,7 +1550,7 @@ namespace Barotrauma
|
||||
|
||||
public bool NeedsDivingGear(Hull hull, out bool needsSuit)
|
||||
{
|
||||
if (!Character.NeedsAir)
|
||||
if (Character.IsImmuneToPressure)
|
||||
{
|
||||
needsSuit = false;
|
||||
return false;
|
||||
@@ -1557,30 +1561,30 @@ namespace Barotrauma
|
||||
hull.LethalPressure > 0 ||
|
||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
|
||||
{
|
||||
needsSuit = !Character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
needsSuit = true;
|
||||
return true;
|
||||
}
|
||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||
if (Character.CharacterHealth.OxygenLowResistance < 1 && (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasDivingGear(Character character, float conditionPercentage = 0) => HasDivingSuit(character, conditionPercentage) || HasDivingMask(character, conditionPercentage);
|
||||
public static bool HasDivingGear(Character character, float conditionPercentage = 0, bool requireOxygenTank = true) => HasDivingSuit(character, conditionPercentage, requireOxygenTank) || HasDivingMask(character, conditionPercentage, requireOxygenTank);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving suit in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
public static bool HasDivingSuit(Character character, float conditionPercentage = 0)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true,
|
||||
public static bool HasDivingSuit(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true,
|
||||
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes));
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true);
|
||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||
|
||||
private static List<Item> matchingItems = new List<Item>();
|
||||
|
||||
@@ -1745,7 +1749,9 @@ namespace Barotrauma
|
||||
}
|
||||
if (!someoneSpoke)
|
||||
{
|
||||
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
if (!item.StolenDuringRound &&
|
||||
Level.Loaded?.Type == LevelData.LevelType.Outpost &&
|
||||
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
{
|
||||
var reputationLoss = MathHelper.Clamp(
|
||||
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
|
||||
@@ -1843,7 +1849,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case "reportbrokendevices":
|
||||
foreach (var item in Item.ItemList)
|
||||
foreach (var item in Item.RepairableItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, character))
|
||||
@@ -1924,11 +1930,12 @@ namespace Barotrauma
|
||||
bool isCurrentHull = character == Character && character.CurrentHull == hull;
|
||||
if (hull == null)
|
||||
{
|
||||
float hullSafety = character.IsProtectedFromPressure ? 0 : 100;
|
||||
if (isCurrentHull)
|
||||
{
|
||||
CurrentHullSafety = character.NeedsAir ? 0 : 100;
|
||||
CurrentHullSafety = hullSafety;
|
||||
}
|
||||
return CurrentHullSafety;
|
||||
return hullSafety;
|
||||
}
|
||||
if (isCurrentHull && visibleHulls == null)
|
||||
{
|
||||
@@ -1936,10 +1943,9 @@ namespace Barotrauma
|
||||
visibleHulls = VisibleHulls;
|
||||
}
|
||||
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = character.IsProtectedFromPressure();
|
||||
bool ignoreOxygen = HasDivingGear(character);
|
||||
bool ignoreOxygen = character.IsProtectedFromPressure || HasDivingGear(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater: false, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
if (isCurrentHull)
|
||||
{
|
||||
CurrentHullSafety = safety;
|
||||
@@ -1949,15 +1955,33 @@ namespace Barotrauma
|
||||
|
||||
private static float CalculateHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
||||
{
|
||||
if (hull == null) { return character.NeedsAir ? 0 : 100; }
|
||||
if (hull.LethalPressure > 0 && character.PressureProtection <= 0 && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure)) { return 0; }
|
||||
bool isProtectedFromPressure = character.IsProtectedFromPressure;
|
||||
if (hull == null) { return isProtectedFromPressure ? 100 : 0; }
|
||||
if (hull.LethalPressure > 0 && !isProtectedFromPressure) { return 0; }
|
||||
// Oxygen factor should be 1 with 70% oxygen or more and 0.1 when the oxygen level is 30% or lower.
|
||||
// With insufficient oxygen, the safety of the hull should be 39, all the other factors aside. So, just below the HULL_SAFETY_THRESHOLD.
|
||||
float oxygenFactor = ignoreOxygen ? 1 : MathHelper.Lerp((HULL_SAFETY_THRESHOLD - 1) / 100, 1, MathUtils.InverseLerp(HULL_LOW_OXYGEN_PERCENTAGE, 100 - HULL_LOW_OXYGEN_PERCENTAGE, hull.OxygenPercentage));
|
||||
float waterFactor = ignoreWater ? 1 : MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, hull.WaterPercentage / 100);
|
||||
if (!character.NeedsAir)
|
||||
float waterFactor = 1;
|
||||
if (!ignoreWater)
|
||||
{
|
||||
if (visibleHulls != null)
|
||||
{
|
||||
// Take the visible hulls into account too, because otherwise multi-hull rooms on several floors (with platforms) will yield unexpected results.
|
||||
float relativeWaterVolume = visibleHulls.Sum(s => s.WaterVolume) / visibleHulls.Sum(s => s.Volume);
|
||||
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
float relativeWaterVolume = hull.WaterVolume / hull.Volume;
|
||||
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
|
||||
}
|
||||
}
|
||||
if (character.CharacterHealth.OxygenLowResistance >= 1)
|
||||
{
|
||||
oxygenFactor = 1;
|
||||
}
|
||||
if (isProtectedFromPressure)
|
||||
{
|
||||
waterFactor = 1;
|
||||
}
|
||||
float fireFactor = 1;
|
||||
@@ -2045,21 +2069,39 @@ namespace Barotrauma
|
||||
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
|
||||
{
|
||||
bool sameTeam = me.TeamID == other.TeamID;
|
||||
bool teamGood = sameTeam || !onlySameTeam && IsOnFriendlyTeam(me, other);
|
||||
bool teamGood = sameTeam || !onlySameTeam && me.IsOnFriendlyTeam(other);
|
||||
if (!teamGood) { return false; }
|
||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||
if (me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!sameTeam && me.TeamID == CharacterTeamType.None && other.IsPet)
|
||||
if (other.IsPet)
|
||||
{
|
||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||
return false;
|
||||
if (!sameTeam && me.TeamID == CharacterTeamType.None) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
if ((me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1) ||
|
||||
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
{
|
||||
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
|
||||
Identifier npcFaction = npc.Faction;
|
||||
Identifier currentLocationFaction = campaign.Map?.CurrentLocation?.Faction?.Prefab.Identifier ?? Identifier.Empty;
|
||||
if (npcFaction.IsEmpty)
|
||||
{
|
||||
//if faction identifier is not specified, assume the NPC is a member of the faction that owns the outpost
|
||||
npcFaction = currentLocationFaction;
|
||||
}
|
||||
if (!currentLocationFaction.IsEmpty && npcFaction == currentLocationFaction)
|
||||
{
|
||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Barotrauma
|
||||
base.Update(speed);
|
||||
float step = 1.0f / 60.0f;
|
||||
checkDoorsTimer -= step;
|
||||
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsOpen)
|
||||
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsFullyOpen)
|
||||
{
|
||||
buttonPressTimer = 0;
|
||||
}
|
||||
@@ -211,7 +211,7 @@ namespace Barotrauma
|
||||
currentTarget = target;
|
||||
Vector2 currentPos = host.SimPosition;
|
||||
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
|
||||
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
@@ -342,7 +342,7 @@ namespace Barotrauma
|
||||
CheckDoorsInPath();
|
||||
doorsChecked = true;
|
||||
}
|
||||
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsOpen)
|
||||
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && lastDoor.door.IsOpening)
|
||||
{
|
||||
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
|
||||
Reset();
|
||||
@@ -510,7 +510,7 @@ namespace Barotrauma
|
||||
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
{
|
||||
if (door.IsBroken) { return true; }
|
||||
if (!door.IsOpen)
|
||||
if (door.IsClosed)
|
||||
{
|
||||
if (!door.Item.IsInteractable(character)) { return false; }
|
||||
if (!ShouldBreakDoor(door))
|
||||
@@ -536,7 +536,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (var linked in door.Item.linkedTo)
|
||||
{
|
||||
if (!(linked is Item linkedItem)) { continue; }
|
||||
if (linked is not Item linkedItem) { continue; }
|
||||
var button = linkedItem.GetComponent<Controller>();
|
||||
if (button == null) { continue; }
|
||||
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
|
||||
@@ -785,7 +785,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (hull.WaterVolume / hull.Rect.Width > 100.0f)
|
||||
{
|
||||
if (!HumanAIController.HasDivingSuit(character))
|
||||
if (!HumanAIController.HasDivingSuit(character) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
penalty += 500.0f;
|
||||
}
|
||||
@@ -808,7 +808,7 @@ namespace Barotrauma
|
||||
|
||||
private float? GetSingleNodePenalty(PathNode node)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { return null; }
|
||||
if (!node.Waypoint.IsTraversable) { return null; }
|
||||
if (node.IsBlocked()) { return null; }
|
||||
float penalty = 0.0f;
|
||||
if (node.Waypoint.ConnectedGap != null && node.Waypoint.ConnectedGap.Open < 0.9f)
|
||||
|
||||
+23
-14
@@ -352,7 +352,7 @@ namespace Barotrauma
|
||||
Weapon = null;
|
||||
continue;
|
||||
}
|
||||
if (WeaponComponent.IsLoaded(character))
|
||||
if (WeaponComponent.IsNotEmpty(character))
|
||||
{
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
@@ -470,7 +470,7 @@ namespace Barotrauma
|
||||
// Not in the inventory anymore or cannot find the weapon component
|
||||
return false;
|
||||
}
|
||||
if (!WeaponComponent.IsLoaded(character))
|
||||
if (!WeaponComponent.IsNotEmpty(character))
|
||||
{
|
||||
// Try reloading (and seek ammo)
|
||||
if (!Reload(seekAmmo))
|
||||
@@ -541,7 +541,7 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (!weapon.IsLoaded(character))
|
||||
if (!weapon.IsNotEmpty(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
||||
{
|
||||
@@ -554,7 +554,15 @@ namespace Barotrauma
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (Enemy.IsKnockedDown)
|
||||
|
||||
if (Enemy.Params.Health.StunImmunity)
|
||||
{
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
else if (Enemy.IsKnockedDown)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
@@ -640,11 +648,11 @@ namespace Barotrauma
|
||||
{
|
||||
statusEffects = statusEffects.Concat(hitEffects);
|
||||
}
|
||||
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == "stun" ? a.Strength : 0);
|
||||
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == AfflictionPrefab.StunType ? a.Strength : 0);
|
||||
float effectsStun = statusEffects.None() ? 0 : statusEffects.Max(se =>
|
||||
{
|
||||
float stunAmount = 0;
|
||||
var stunAffliction = se.Afflictions.Find(a => a.Identifier == "stun");
|
||||
var stunAffliction = se.Afflictions.Find(a => a.Identifier == AfflictionPrefab.StunType);
|
||||
if (stunAffliction != null)
|
||||
{
|
||||
stunAmount = stunAffliction.Strength;
|
||||
@@ -1168,30 +1176,31 @@ namespace Barotrauma
|
||||
if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.WorldPosition - Weapon.WorldPosition) < MathHelper.PiOver4 + aimFactor)
|
||||
{
|
||||
if (myBodies == null)
|
||||
{
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody != null)
|
||||
// Check that we don't hit friendlies. No need to check the walls, because there's a separate check for that at 1096 (which intentionally has a small delay)
|
||||
var pickedBodies = Submarine.PickBodies(Weapon.SimPosition, Character.GetRelativeSimPosition(from: Weapon, to: Enemy), myBodies, Physics.CollisionCharacter);
|
||||
foreach (var body in pickedBodies)
|
||||
{
|
||||
Character target = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
if (body.UserData is Character c)
|
||||
{
|
||||
target = c;
|
||||
}
|
||||
else if (pickedBody.UserData is Limb limb)
|
||||
else if (body.UserData is Limb limb)
|
||||
{
|
||||
target = limb.character;
|
||||
}
|
||||
if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
|
||||
if (target != null && (target != Enemy || HumanAIController.IsFriendly(target)))
|
||||
{
|
||||
UseWeapon(deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
UseWeapon(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -200,7 +201,8 @@ namespace Barotrauma
|
||||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
|
||||
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.DefaultReach)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
@@ -244,7 +246,8 @@ namespace Barotrauma
|
||||
|
||||
public bool IsInTargetSlot(Item item)
|
||||
{
|
||||
if (container?.Inventory is ItemInventory inventory && TargetSlot is not null)
|
||||
if (TargetSlot == null) { return true; }
|
||||
if (container?.Inventory is ItemInventory inventory)
|
||||
{
|
||||
return inventory.IsInSlot(item, (int)TargetSlot);
|
||||
}
|
||||
|
||||
+39
-29
@@ -19,7 +19,6 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
private float useExtinquisherTimer;
|
||||
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -44,7 +43,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
|
||||
float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y;
|
||||
float yDist = Math.Abs(characterY - 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));
|
||||
@@ -119,24 +119,18 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
break;
|
||||
}
|
||||
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X) - fs.DamageRange;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - fs.WorldPosition.Y);
|
||||
bool inRange = xDist + yDist < extinguisher.Range;
|
||||
// Use the hull position, because the fire x pos is sometimes inside a wall -> the bot can't ever see it and continues running towards the wall.
|
||||
ISpatialEntity lookTarget = character.CurrentHull == targetHull || character.CurrentHull.linkedTo.Contains(targetHull) ? targetHull : fs as ISpatialEntity;
|
||||
bool move = !inRange || !character.CanSeeTarget(lookTarget);
|
||||
if ((inRange && character.CanSeeTarget(lookTarget)) || useExtinquisherTimer > 0)
|
||||
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.CurrentHull.WorldPosition.Y - targetHull.WorldPosition.Y);
|
||||
float dist = xDist + yDist;
|
||||
bool inRange = dist < extinguisher.Range;
|
||||
bool isInDamageRange = fs.IsInDamageRange(character, fs.DamageRange) && character.CanSeeTarget(targetHull);
|
||||
bool moveCloser = !isInDamageRange && (!inRange || !character.CanSeeTarget(targetHull));
|
||||
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
|
||||
if (operateExtinguisher)
|
||||
{
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
{
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
// Aim
|
||||
character.CursorPosition = fs.Position;
|
||||
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
|
||||
float dist = fromCharacterToFireSource.Length();
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
|
||||
if (extinguisherItem.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
@@ -148,25 +142,29 @@ namespace Barotrauma
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, FormatCapitals.Yes).Value, null, 0, "putoutfire".ToIdentifier(), 10.0f);
|
||||
}
|
||||
// Prevents running into the flames.
|
||||
objectiveManager.CurrentObjective.ForceWalk = true;
|
||||
}
|
||||
if (move)
|
||||
if (moveCloser)
|
||||
{
|
||||
//go to the first firesource
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: Math.Max(fs.DamageRange, extinguisher.Range * 0.7f))
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
|
||||
TargetName = fs.Hull.DisplayName
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range * 0.8f)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
|
||||
TargetName = fs.Hull.DisplayName,
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
|
||||
{
|
||||
gotoObjective.requiredCondition = () => character.CanSeeTarget(targetHull);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!operateExtinguisher || isInDamageRange)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
// Don't walk into the flames.
|
||||
RemoveSubObjective(ref gotoObjective);
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
// Only target one fire source at the time.
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -177,8 +175,20 @@ namespace Barotrauma
|
||||
base.Reset();
|
||||
getExtinguisherObjective = null;
|
||||
gotoObjective = null;
|
||||
useExtinquisherTimer = 0;
|
||||
sinTime = 0;
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-24
@@ -47,31 +47,25 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
if (!character.NeedsAir)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = (
|
||||
objectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
Priority = (
|
||||
objectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
&& character.IsProtectedFromPressure ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
|
||||
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
|
||||
(HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
|
||||
(needsSuit ?
|
||||
!HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)) :
|
||||
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character))))
|
||||
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)))))
|
||||
{
|
||||
Priority = 100;
|
||||
}
|
||||
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
|
||||
character.Submarine != null && !AIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
|
||||
character.Submarine != null && !character.IsOnFriendlyTeam(character.Submarine.TeamID))
|
||||
{
|
||||
// Ordered to follow, hold position, or return back to main sub inside a hostile sub
|
||||
// -> ignore find safety unless we need to find a diving gear
|
||||
@@ -117,6 +111,11 @@ namespace Barotrauma
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
if (currenthullSafety >= 100)
|
||||
{
|
||||
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
|
||||
Priority = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -137,12 +136,14 @@ namespace Barotrauma
|
||||
private float retryTimer;
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (resetPriority) { return; }
|
||||
var currentHull = character.CurrentHull;
|
||||
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0 && character.PressureProtection <= 0;
|
||||
if (!character.LockHands && (!dangerousPressure || cannotFindSafeHull))
|
||||
bool dangerousPressure = !character.IsProtectedFromPressure && (currentHull == null || currentHull.LethalPressure > 0);
|
||||
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
||||
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
|
||||
{
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull, out bool needsDivingSuit);
|
||||
bool needsEquipment = false;
|
||||
bool needsEquipment = shouldActOnSuffocation;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.GetMinOxygen(character));
|
||||
@@ -218,7 +219,11 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character, conditionPercentage: 50)
|
||||
AllowGoingOutside =
|
||||
character.IsProtectedFromPressure ||
|
||||
character.CurrentHull == null ||
|
||||
character.CurrentHull.IsTaggedAirlock() ||
|
||||
character.CurrentHull.LeadsOutside(character)
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
@@ -349,8 +354,8 @@ namespace Barotrauma
|
||||
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
|
||||
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
|
||||
//path calculations, only to discard all of them when going through the hulls in the outpost)
|
||||
float hullSuitability = EstimateHullSuitability(character, hull);
|
||||
if (!hulls.Any())
|
||||
float hullSuitability = EstimateHullSuitability(character, hull);
|
||||
if (hulls.None())
|
||||
{
|
||||
hulls.Add(hull);
|
||||
}
|
||||
@@ -445,9 +450,12 @@ namespace Barotrauma
|
||||
{
|
||||
hullSafety = 100;
|
||||
}
|
||||
float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y;
|
||||
float yDist = Math.Abs(characterY - potentialHull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float distance = Math.Abs(character.WorldPosition.X - potentialHull.WorldPosition.X) + yDist;
|
||||
// Huge preference for closer targets
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, potentialHull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 10000, distance));
|
||||
hullSafety *= distanceFactor;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
// Intentionally exclude wrecks from this check
|
||||
|
||||
+23
-11
@@ -155,17 +155,21 @@ namespace Barotrauma
|
||||
bool canOperate = toLeak.LengthSquared() < reach * reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () =>
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak)
|
||||
{
|
||||
// Use an empty filter to override the default
|
||||
EndNodeFilter = n => true
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (CheckObjectiveSpecific()) { IsCompleted = true; }
|
||||
else
|
||||
{
|
||||
if (CheckObjectiveSpecific()) { IsCompleted = true; }
|
||||
else
|
||||
{
|
||||
// Failed to operate. Probably too far.
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
// Failed to operate. Probably too far.
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -178,7 +182,7 @@ namespace Barotrauma
|
||||
requiredCondition = () =>
|
||||
Leak.Submarine == character.Submarine &&
|
||||
Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
|
||||
endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull),
|
||||
endNodeFilter = IsSuitableEndNode,
|
||||
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
|
||||
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
|
||||
},
|
||||
@@ -197,6 +201,14 @@ namespace Barotrauma
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective));
|
||||
|
||||
bool IsSuitableEndNode(PathNode n)
|
||||
{
|
||||
if (n.Waypoint.CurrentHull is null) { return false; }
|
||||
if (n.Waypoint.CurrentHull.ConnectedGaps.Contains(Leak)) { return true; }
|
||||
// Accept also nodes located in the linked hulls (multi-hull rooms)
|
||||
return Leak.linkedTo.Any(e => e is Hull h && h.linkedTo.Contains(n.Waypoint.CurrentHull));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-12
@@ -54,7 +54,7 @@ namespace Barotrauma
|
||||
public bool AllowVariants { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool Wear { get; set; }
|
||||
public bool RequireLoaded { get; set; }
|
||||
public bool RequireNonEmpty { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool SpeakIfFails { get; set; }
|
||||
@@ -123,6 +123,11 @@ namespace Barotrauma
|
||||
return ignoredTags;
|
||||
}
|
||||
|
||||
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
|
||||
{
|
||||
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(DefaultReach);
|
||||
}
|
||||
|
||||
private bool CheckInventory()
|
||||
{
|
||||
if (IdentifiersOrTags == null) { return false; }
|
||||
@@ -155,11 +160,6 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (IdentifiersOrTags != null && !isDoneSeeking)
|
||||
{
|
||||
if (checkInventory)
|
||||
@@ -171,9 +171,14 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isDoneSeeking)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!AllowDangerousPressure)
|
||||
{
|
||||
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0 && character.PressureProtection <= 0;
|
||||
bool dangerousPressure = !character.IsProtectedFromPressure && (character.CurrentHull == null || character.CurrentHull.LethalPressure > 0);
|
||||
if (dangerousPressure)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -192,6 +197,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (targetItem == null || targetItem.Removed)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -307,7 +317,8 @@ namespace Barotrauma
|
||||
{
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
AbortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
SpeakIfFails = false
|
||||
SpeakIfFails = false,
|
||||
endNodeFilter = CreateEndNodeFilter(moveToTarget)
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
@@ -391,10 +402,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (!itemInventory.Container.HasRequiredItems(character, addMessage: false)) { continue; }
|
||||
}
|
||||
float itemPriority = 1;
|
||||
float itemPriority = item.Prefab.BotPriority;
|
||||
if (GetItemPriority != null)
|
||||
{
|
||||
itemPriority = GetItemPriority(item);
|
||||
itemPriority *= GetItemPriority(item);
|
||||
}
|
||||
Entity rootInventoryOwner = item.GetRootInventoryOwner();
|
||||
if (rootInventoryOwner is Item ownerItem)
|
||||
@@ -513,7 +524,7 @@ namespace Barotrauma
|
||||
float lowestCost = float.MaxValue;
|
||||
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
|
||||
{
|
||||
if (!(prefab is ItemPrefab itemPrefab)) { continue; }
|
||||
if (prefab is not ItemPrefab itemPrefab) { continue; }
|
||||
if (IdentifiersOrTags.Any(id => id == prefab.Identifier || prefab.Tags.Contains(id)))
|
||||
{
|
||||
float cost = itemPrefab.DefaultPrice != null && itemPrefab.CanBeBought ?
|
||||
@@ -561,7 +572,7 @@ namespace Barotrauma
|
||||
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; }
|
||||
if (RequireNonEmpty && item.Components.Any(i => !i.IsNotEmpty(character))) { return false; }
|
||||
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
public bool CheckInventory { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool RequireLoaded { get; set; }
|
||||
public bool RequireNonEmpty { get; set; }
|
||||
public bool RequireAllItems { get; set; }
|
||||
|
||||
private readonly ImmutableArray<Identifier> gearTags;
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireLoaded = RequireLoaded,
|
||||
RequireNonEmpty = RequireNonEmpty,
|
||||
ItemCount = count,
|
||||
SpeakIfFails = RequireAllItems
|
||||
},
|
||||
|
||||
+50
-24
@@ -50,6 +50,7 @@ namespace Barotrauma
|
||||
private readonly float minDistance = 50;
|
||||
private readonly float seekGapsInterval = 1;
|
||||
private float seekGapsTimer;
|
||||
private bool cantFindDivingGear;
|
||||
|
||||
/// <summary>
|
||||
/// Display units
|
||||
@@ -90,7 +91,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool UseDistanceRelativeToAimSourcePos { get; set; } = false;
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
public override bool AllowInAnySub => true;
|
||||
@@ -263,48 +264,73 @@ namespace Barotrauma
|
||||
}
|
||||
if (!Abandon)
|
||||
{
|
||||
if (getDivingGearIfNeeded && !character.LockHands)
|
||||
if (getDivingGearIfNeeded)
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
if (Mimic)
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
|
||||
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool tryToGetDivingSuit = needsDivingSuit;
|
||||
if (Mimic && !character.IsImmuneToPressure)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
{
|
||||
needsDivingGear = true;
|
||||
needsDivingSuit = true;
|
||||
tryToGetDivingGear = true;
|
||||
tryToGetDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget))
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
needsDivingGear = true;
|
||||
tryToGetDivingGear = true;
|
||||
}
|
||||
}
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
|
||||
if (needsDivingSuit)
|
||||
if (tryToGetDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
else if (tryToGetDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
if (needsEquipment)
|
||||
if (character.LockHands)
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
}
|
||||
if (cantFindDivingGear && needsDivingSuit)
|
||||
{
|
||||
// Don't try to reach the target without a suit because it's lethal.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (needsEquipment && !cantFindDivingGear)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
}
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
// Shouldn't try to reach the target without a suit, because it's lethal.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without requiring the diving suit
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
});
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -170,7 +170,8 @@ namespace Barotrauma
|
||||
TargetHull = character.CurrentHull;
|
||||
}
|
||||
|
||||
if (behavior == BehaviorType.StayInHull)
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid)
|
||||
{
|
||||
currentTarget = TargetHull;
|
||||
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
||||
@@ -190,9 +191,6 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (currentTarget != null && !currentTargetIsInvalid)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsEscorted)
|
||||
|
||||
+7
-6
@@ -364,8 +364,7 @@ namespace Barotrauma
|
||||
CurrentOrders.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
var currentOrderInfo = character.GetCurrentOrder(currentOrder);
|
||||
if (currentOrderInfo is Order)
|
||||
if (character.GetCurrentOrder(currentOrder) is Order currentOrderInfo)
|
||||
{
|
||||
int currentPriority = currentOrderInfo.ManualPriority;
|
||||
if (currentOrder.ManualPriority != currentPriority)
|
||||
@@ -539,7 +538,8 @@ namespace Barotrauma
|
||||
KeepActiveWhenReady = true,
|
||||
CheckInventory = true,
|
||||
Equip = false,
|
||||
FindAllItems = true
|
||||
FindAllItems = true,
|
||||
RequireNonEmpty = false
|
||||
};
|
||||
break;
|
||||
case "findweapon":
|
||||
@@ -555,7 +555,8 @@ namespace Barotrauma
|
||||
KeepActiveWhenReady = false,
|
||||
CheckInventory = false,
|
||||
EvaluateCombatPriority = true,
|
||||
FindAllItems = false
|
||||
FindAllItems = false,
|
||||
RequireNonEmpty = true
|
||||
};
|
||||
}
|
||||
prepareObjective.KeepActiveWhenReady = false;
|
||||
@@ -600,9 +601,9 @@ namespace Barotrauma
|
||||
|
||||
Order dismissOrder = currentOrder.GetDismissal();
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
|
||||
if (GameMain.GameSession?.CrewManager is CrewManager cm && cm.IsSinglePlayer)
|
||||
{
|
||||
GameMain.GameSession.CrewManager.SetCharacterOrder(character, dismissOrder);
|
||||
character.SetOrder(dismissOrder, isNewOrder: true, speak: false);
|
||||
}
|
||||
#else
|
||||
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(dismissOrder, character, character));
|
||||
|
||||
+6
-1
@@ -23,6 +23,11 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
|
||||
/// <summary>
|
||||
/// If undefined, a default filter will be used.
|
||||
/// </summary>
|
||||
public Func<PathNode, bool> EndNodeFilter;
|
||||
|
||||
public bool Override { get; set; } = true;
|
||||
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
|
||||
@@ -232,7 +237,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
|
||||
{
|
||||
TargetName = target.Item.Name,
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null
|
||||
endNodeFilter = EndNodeFilter ?? AIObjectiveGetItem.CreateEndNodeFilter(target.Item)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+4
-3
@@ -27,6 +27,7 @@ namespace Barotrauma
|
||||
public bool FindAllItems { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool RequireNonEmpty { get; set; }
|
||||
|
||||
private AIObjective GetSubObjective()
|
||||
{
|
||||
@@ -74,7 +75,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
|
||||
}
|
||||
else if (items.Any(i => i.Components.Any(i => !i.IsLoaded(character))))
|
||||
else if (items.Any(i => i.Components.Any(i => !i.IsNotEmpty(character))))
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
@@ -106,7 +107,7 @@ namespace Barotrauma
|
||||
CheckInventory = CheckInventory,
|
||||
Equip = Equip,
|
||||
EvaluateCombatPriority = EvaluateCombatPriority,
|
||||
RequireLoaded = true,
|
||||
RequireNonEmpty = RequireNonEmpty,
|
||||
RequireAllItems = requireAll
|
||||
},
|
||||
onCompleted: () =>
|
||||
@@ -157,7 +158,7 @@ namespace Barotrauma
|
||||
{
|
||||
EvaluateCombatPriority = EvaluateCombatPriority,
|
||||
SpeakIfFails = true,
|
||||
RequireLoaded = true
|
||||
RequireNonEmpty = RequireNonEmpty
|
||||
};
|
||||
}
|
||||
if (!TryAddSubObjective(ref getSingleItemObjective, getItemConstructor,
|
||||
|
||||
+22
-12
@@ -17,7 +17,6 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveContainItem refuelObjective;
|
||||
private float previousCondition = -1;
|
||||
private RepairTool repairTool;
|
||||
|
||||
private const float WaitTimeBeforeRepair = 0.5f;
|
||||
@@ -196,15 +195,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
if (previousCondition == -1)
|
||||
{
|
||||
previousCondition = Item.Condition;
|
||||
}
|
||||
else if (Item.Condition < previousCondition)
|
||||
{
|
||||
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
||||
Abandon = true;
|
||||
}
|
||||
CheckPreviousCondition(deltaTime);
|
||||
}
|
||||
if (Abandon)
|
||||
{
|
||||
@@ -229,7 +220,6 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
previousCondition = -1;
|
||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
|
||||
{
|
||||
TargetName = Item.Name
|
||||
@@ -251,6 +241,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private const float conditionCheckDelay = 1;
|
||||
private float conditionCheckTimer;
|
||||
private float previousCondition;
|
||||
private void CheckPreviousCondition(float deltaTime)
|
||||
{
|
||||
if (Item == null || Item.Removed) { return; }
|
||||
conditionCheckTimer -= deltaTime;
|
||||
if (conditionCheckTimer > 0) { return; }
|
||||
conditionCheckTimer = conditionCheckDelay;
|
||||
if (previousCondition > -1 && Item.Condition < previousCondition)
|
||||
{
|
||||
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the previous condition is not yet stored or if it's valid (greater or equal to current condition), save the condition for the next check here.
|
||||
previousCondition = Item.Condition;
|
||||
}
|
||||
}
|
||||
|
||||
private void FindRepairTool()
|
||||
{
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
@@ -303,7 +314,6 @@ namespace Barotrauma
|
||||
base.Reset();
|
||||
goToObjective = null;
|
||||
refuelObjective = null;
|
||||
previousCondition = -1;
|
||||
repairTool = null;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-15
@@ -320,10 +320,10 @@ namespace Barotrauma
|
||||
foreach (KeyValuePair<Identifier, float> treatmentSuitability in currentTreatmentSuitabilities)
|
||||
{
|
||||
if (treatmentSuitability.Value <= cprSuitability) { continue; }
|
||||
if (MapEntityPrefab.Find(null, treatmentSuitability.Key, showErrorMessages: false) is ItemPrefab itemPrefab)
|
||||
if (ItemPrefab.Prefabs.TryGet(treatmentSuitability.Key, out ItemPrefab itemPrefab))
|
||||
{
|
||||
if (!Item.ItemList.Any(it => ((MapEntity)it).Prefab.Identifier == treatmentSuitability.Key)) { continue; }
|
||||
suitableItemIdentifiers.Add(treatmentSuitability.Key);
|
||||
if (Item.ItemList.None(it => it.Prefab.Identifier == treatmentSuitability.Key)) { continue; }
|
||||
suitableItemIdentifiers.Add(itemPrefab.Identifier);
|
||||
//only list the first 4 items
|
||||
if (itemNameList.Count < 4)
|
||||
{
|
||||
@@ -482,18 +482,6 @@ namespace Barotrauma
|
||||
|
||||
public static IEnumerable<Affliction> GetSortedAfflictions(Character character, bool excludeBuffs = true) => CharacterHealth.SortAfflictionsBySeverity(character.CharacterHealth.GetAllAfflictions(), excludeBuffs);
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
|
||||
{
|
||||
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; }
|
||||
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
|
||||
yield return affliction;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
+22
-3
@@ -26,7 +26,7 @@ namespace Barotrauma
|
||||
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
|
||||
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer && target.HealthPercentage < 100 ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,15 +67,34 @@ namespace Barotrauma
|
||||
float vitality = 100;
|
||||
vitality -= character.Bleeding * 2;
|
||||
vitality += Math.Min(character.Oxygen, 0);
|
||||
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
|
||||
foreach (Affliction affliction in AIObjectiveRescue.GetTreatableAfflictions(character))
|
||||
foreach (Affliction affliction in GetTreatableAfflictions(character))
|
||||
{
|
||||
float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
|
||||
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
}
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
|
||||
{
|
||||
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
if (affliction.Prefab.TreatmentSuitability.None(kvp => kvp.Value > 0)) { continue; }
|
||||
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
|
||||
yield return affliction;
|
||||
}
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Character target)
|
||||
=> new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior, isSteeringThroughGap;
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
|
||||
@@ -27,6 +27,15 @@ namespace Barotrauma
|
||||
turrets.Add(turret);
|
||||
// Set false, because we manage the turrets in the Update method.
|
||||
turret.AutoOperate = false;
|
||||
// Set to full condition, because items don't work when they are broken.
|
||||
turret.Item.Condition = turret.Item.MaxCondition;
|
||||
foreach (MapEntity linkedEntity in turret.Item.linkedTo)
|
||||
{
|
||||
if (linkedEntity is Item linkedItem)
|
||||
{
|
||||
linkedItem.Condition = linkedItem.MaxCondition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadAllTurrets();
|
||||
@@ -264,27 +273,52 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
|
||||
bool someoneNearby = false;
|
||||
bool isSomeoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 2.0f;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
#if SERVER
|
||||
foreach (var client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, Submarine.WorldPosition) < minDist * minDist)
|
||||
var spectatePos = client.SpectatePos;
|
||||
if (spectatePos.HasValue)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
if (IsCloseEnough(spectatePos.Value, minDist))
|
||||
{
|
||||
isSomeoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
#else
|
||||
if (IsCloseEnough(GameMain.GameScreen.Cam.Position, minDist))
|
||||
{
|
||||
if (c != Character.Controlled && !c.IsRemotePlayer) { continue; }
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, Submarine.WorldPosition) < minDist * minDist)
|
||||
isSomeoneNearby = true;
|
||||
}
|
||||
#endif
|
||||
if (!isSomeoneNearby)
|
||||
{
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (IsCloseEnough(submarine.WorldPosition, minDist))
|
||||
{
|
||||
isSomeoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!someoneNearby) { return; }
|
||||
if (!isSomeoneNearby)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.IsPlayer && !c.IsOnPlayerTeam) { continue; }
|
||||
if (IsCloseEnough(c.WorldPosition, minDist))
|
||||
{
|
||||
isSomeoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSomeoneNearby) { return; }
|
||||
OperateTurrets(deltaTime, Config.Entity);
|
||||
if (!IsClient)
|
||||
{
|
||||
@@ -292,6 +326,7 @@ namespace Barotrauma
|
||||
UpdateReinforcements(deltaTime);
|
||||
}
|
||||
}
|
||||
private bool IsCloseEnough(Vector2 targetPos, float minDist) => Vector2.DistanceSquared(targetPos, Submarine.WorldPosition) < minDist * minDist;
|
||||
|
||||
private void SpawnInitialCells()
|
||||
{
|
||||
|
||||
@@ -541,11 +541,11 @@ namespace Barotrauma
|
||||
float wobbleStrength = 0.0f;
|
||||
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == heldItem)
|
||||
{
|
||||
wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: "damage");
|
||||
wobbleStrength += Character.CharacterHealth.GetLimbDamage(rightHand, afflictionType: AfflictionPrefab.DamageType);
|
||||
}
|
||||
if (character.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == heldItem)
|
||||
{
|
||||
wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: "damage");
|
||||
wobbleStrength += Character.CharacterHealth.GetLimbDamage(leftHand, afflictionType: AfflictionPrefab.DamageType);
|
||||
}
|
||||
if (wobbleStrength <= 0.1f) { return 0.0f; }
|
||||
wobbleStrength = (float)Math.Min(wobbleStrength, 1.0f);
|
||||
|
||||
+72
-43
@@ -150,8 +150,10 @@ namespace Barotrauma
|
||||
|
||||
private readonly float movementLerp;
|
||||
|
||||
private float cprAnimTimer;
|
||||
private float cprPump;
|
||||
private float cprAnimTimer,cprPump;
|
||||
|
||||
private float fallingProneAnimTimer;
|
||||
const float FallingProneAnimDuration = 1.0f;
|
||||
|
||||
private bool swimming;
|
||||
//time until the character can switch from walking to swimming or vice versa
|
||||
@@ -268,7 +270,8 @@ namespace Barotrauma
|
||||
if (deathAnimTimer < deathAnimDuration)
|
||||
{
|
||||
deathAnimTimer += deltaTime;
|
||||
UpdateDying(deltaTime);
|
||||
//the force/torque used to move the limbs goes from 1 to 0 during the death anim duration
|
||||
UpdateFallingProne(1.0f - deathAnimTimer / deathAnimDuration);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -278,6 +281,11 @@ namespace Barotrauma
|
||||
|
||||
if (!character.CanMove)
|
||||
{
|
||||
if (fallingProneAnimTimer < FallingProneAnimDuration)
|
||||
{
|
||||
fallingProneAnimTimer += deltaTime;
|
||||
UpdateFallingProne(1.0f);
|
||||
}
|
||||
levitatingCollider = false;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
@@ -291,18 +299,20 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
fallingProneAnimTimer = 0.0f;
|
||||
|
||||
//re-enable collider
|
||||
if (!Collider.Enabled)
|
||||
{
|
||||
var lowestLimb = FindLowestLimb();
|
||||
|
||||
|
||||
Collider.SetTransform(new Vector2(
|
||||
Collider.SimPosition.X,
|
||||
Math.Max(lowestLimb.SimPosition.Y + (Collider.Radius + Collider.Height / 2), Collider.SimPosition.Y)),
|
||||
Collider.Rotation);
|
||||
|
||||
Collider.FarseerBody.ResetDynamics();
|
||||
Collider.FarseerBody.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.Enabled = true;
|
||||
}
|
||||
|
||||
@@ -444,12 +454,13 @@ namespace Barotrauma
|
||||
aiming = false;
|
||||
wasAimingMelee = aimingMelee;
|
||||
aimingMelee = false;
|
||||
IsHanging = false;
|
||||
IsHanging = IsHanging && character.IsRagdolled;
|
||||
}
|
||||
|
||||
void UpdateStanding()
|
||||
{
|
||||
if (CurrentGroundedParams == null) { return; }
|
||||
var currentGroundedParams = CurrentGroundedParams;
|
||||
if (currentGroundedParams == null) { return; }
|
||||
Vector2 handPos;
|
||||
|
||||
Limb leftFoot = GetLimb(LimbType.LeftFoot);
|
||||
@@ -472,7 +483,7 @@ namespace Barotrauma
|
||||
walkCycleMultiplier *= 1.5f;
|
||||
}
|
||||
|
||||
float getUpForce = CurrentGroundedParams.GetUpForce / RagdollParams.JointScale;
|
||||
float getUpForce = currentGroundedParams.GetUpForce / RagdollParams.JointScale;
|
||||
|
||||
Vector2 colliderPos = GetColliderBottom();
|
||||
if (Math.Abs(TargetMovement.X) > 1.0f)
|
||||
@@ -573,7 +584,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float stepLift = TargetMovement.X == 0.0f ? 0 :
|
||||
(float)Math.Sin(WalkPos * CurrentGroundedParams.StepLiftFrequency + MathHelper.Pi * CurrentGroundedParams.StepLiftOffset) * (CurrentGroundedParams.StepLiftAmount / 100);
|
||||
(float)Math.Sin(WalkPos * currentGroundedParams.StepLiftFrequency + MathHelper.Pi * currentGroundedParams.StepLiftOffset) * (currentGroundedParams.StepLiftAmount / 100);
|
||||
|
||||
float y = colliderPos.Y + stepLift;
|
||||
|
||||
@@ -588,7 +599,7 @@ namespace Barotrauma
|
||||
|
||||
if (!head.Disabled)
|
||||
{
|
||||
y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier;
|
||||
y = colliderPos.Y + stepLift * currentGroundedParams.StepLiftHeadMultiplier;
|
||||
if (HeadPosition.HasValue) { y += HeadPosition.Value; }
|
||||
if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
|
||||
head.PullJointWorldAnchorB =
|
||||
@@ -605,18 +616,18 @@ namespace Barotrauma
|
||||
if (TorsoAngle.HasValue && !torso.Disabled)
|
||||
{
|
||||
float torsoAngle = TorsoAngle.Value;
|
||||
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
|
||||
float herpesStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.SpaceHerpesType);
|
||||
if (Crouching && !movingHorizontally && !Aiming) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
|
||||
torsoAngle -= herpesStrength / 150.0f;
|
||||
torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque);
|
||||
torso.body.SmoothRotate(torsoAngle * Dir, currentGroundedParams.TorsoTorque);
|
||||
}
|
||||
if (!head.Disabled)
|
||||
{
|
||||
if (!Aiming && CurrentGroundedParams.FixedHeadAngle && HeadAngle.HasValue)
|
||||
if (!Aiming && currentGroundedParams.FixedHeadAngle && HeadAngle.HasValue)
|
||||
{
|
||||
float headAngle = HeadAngle.Value;
|
||||
if (Crouching && !movingHorizontally) { headAngle -= HumanCrouchParams.ExtraHeadAngleWhenStationary; }
|
||||
head.body.SmoothRotate(headAngle * Dir, CurrentGroundedParams.HeadTorque);
|
||||
head.body.SmoothRotate(headAngle * Dir, currentGroundedParams.HeadTorque);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -655,16 +666,16 @@ namespace Barotrauma
|
||||
if (footPos.Y < 0.0f) { footPos.Y = -0.15f; }
|
||||
|
||||
//make the character limp if the feet are damaged
|
||||
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength("damage", foot, true);
|
||||
float footAfflictionStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.DamageType, foot, true);
|
||||
footPos.X *= MathHelper.Lerp(1.0f, 0.75f, MathHelper.Clamp(footAfflictionStrength / 50.0f, 0.0f, 1.0f));
|
||||
|
||||
if (CurrentGroundedParams.FootLiftHorizontalFactor > 0)
|
||||
if (currentGroundedParams.FootLiftHorizontalFactor > 0)
|
||||
{
|
||||
// Calculate the foot y dynamically based on the foot position relative to the waist,
|
||||
// so that the foot aims higher when it's behind the waist and lower when it's in the front.
|
||||
float xDiff = (foot.SimPosition.X - waistPos.X + FootMoveOffset.X) * Dir;
|
||||
float min = MathUtils.InverseLerp(1, 0, CurrentGroundedParams.FootLiftHorizontalFactor);
|
||||
float max = 1 + MathUtils.InverseLerp(0, 1, CurrentGroundedParams.FootLiftHorizontalFactor);
|
||||
float min = MathUtils.InverseLerp(1, 0, currentGroundedParams.FootLiftHorizontalFactor);
|
||||
float max = 1 + MathUtils.InverseLerp(0, 1, currentGroundedParams.FootLiftHorizontalFactor);
|
||||
float xFactor = MathHelper.Lerp(min, max, MathUtils.InverseLerp(RagdollParams.JointScale, -RagdollParams.JointScale, xDiff));
|
||||
footPos.Y *= xFactor;
|
||||
}
|
||||
@@ -688,19 +699,19 @@ namespace Barotrauma
|
||||
{
|
||||
foot.DebugRefPos = colliderPos;
|
||||
foot.DebugTargetPos = colliderPos + footPos;
|
||||
MoveLimb(foot, colliderPos + footPos, CurrentGroundedParams.FootMoveStrength);
|
||||
MoveLimb(foot, colliderPos + footPos, currentGroundedParams.FootMoveStrength);
|
||||
FootIK(foot, colliderPos + footPos,
|
||||
CurrentGroundedParams.LegBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
|
||||
currentGroundedParams.LegBendTorque, currentGroundedParams.FootTorque, currentGroundedParams.FootAngleInRadians);
|
||||
}
|
||||
}
|
||||
|
||||
//calculate the positions of hands
|
||||
handPos = torso.SimPosition;
|
||||
handPos.X = -walkPosX * CurrentGroundedParams.HandMoveAmount.X;
|
||||
handPos.X = -walkPosX * currentGroundedParams.HandMoveAmount.X;
|
||||
|
||||
float lowerY = CurrentGroundedParams.HandClampY;
|
||||
float lowerY = currentGroundedParams.HandClampY;
|
||||
|
||||
handPos.Y = lowerY + (float)(Math.Abs(Math.Sin(WalkPos - Math.PI * 1.5f) * CurrentGroundedParams.HandMoveAmount.Y));
|
||||
handPos.Y = lowerY + (float)(Math.Abs(Math.Sin(WalkPos - Math.PI * 1.5f) * currentGroundedParams.HandMoveAmount.Y));
|
||||
|
||||
Vector2 posAddition = new Vector2(Math.Sign(movement.X) * HandMoveOffset.X, HandMoveOffset.Y);
|
||||
|
||||
@@ -708,13 +719,13 @@ namespace Barotrauma
|
||||
{
|
||||
HandIK(rightHand,
|
||||
torso.SimPosition + posAddition + new Vector2(-handPos.X, (Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY),
|
||||
CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
|
||||
currentGroundedParams.ArmMoveStrength, currentGroundedParams.HandMoveStrength);
|
||||
}
|
||||
if (leftHand != null && !leftHand.Disabled)
|
||||
{
|
||||
HandIK(leftHand,
|
||||
torso.SimPosition + posAddition + new Vector2(handPos.X, (Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY),
|
||||
CurrentGroundedParams.ArmMoveStrength, CurrentGroundedParams.HandMoveStrength);
|
||||
currentGroundedParams.ArmMoveStrength, currentGroundedParams.HandMoveStrength);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -745,8 +756,8 @@ namespace Barotrauma
|
||||
{
|
||||
foot.DebugRefPos = colliderPos;
|
||||
foot.DebugTargetPos = footPos;
|
||||
float footMoveForce = CurrentGroundedParams.FootMoveStrength;
|
||||
float legBendTorque = CurrentGroundedParams.LegBendTorque;
|
||||
float footMoveForce = currentGroundedParams.FootMoveStrength;
|
||||
float legBendTorque = currentGroundedParams.LegBendTorque;
|
||||
if (Crouching)
|
||||
{
|
||||
// Keeps the pose
|
||||
@@ -754,7 +765,7 @@ namespace Barotrauma
|
||||
footMoveForce *= 2;
|
||||
}
|
||||
MoveLimb(foot, footPos, footMoveForce);
|
||||
FootIK(foot, footPos, legBendTorque, CurrentGroundedParams.FootTorque, CurrentGroundedParams.FootAngleInRadians);
|
||||
FootIK(foot, footPos, legBendTorque, currentGroundedParams.FootTorque, currentGroundedParams.FootAngleInRadians);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +781,7 @@ namespace Barotrauma
|
||||
var arm = GetLimb(armType);
|
||||
if (arm != null && Math.Abs(arm.body.AngularVelocity) < 10.0f)
|
||||
{
|
||||
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.5f, 0.5f), arm.Mass * 50.0f * CurrentGroundedParams.ArmMoveStrength);
|
||||
arm.body.SmoothRotate(MathHelper.Clamp(-arm.body.AngularVelocity, -0.5f, 0.5f), arm.Mass * 50.0f * currentGroundedParams.ArmMoveStrength);
|
||||
}
|
||||
|
||||
//get the elbow to a neutral rotation
|
||||
@@ -781,14 +792,14 @@ namespace Barotrauma
|
||||
if (elbow != null)
|
||||
{
|
||||
float diff = elbow.JointAngle - (Dir > 0 ? elbow.LowerLimit : elbow.UpperLimit);
|
||||
forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * CurrentGroundedParams.ArmMoveStrength);
|
||||
forearm.body.ApplyTorque(MathHelper.Clamp(-diff, -MathHelper.PiOver2, MathHelper.PiOver2) * forearm.Mass * 100.0f * currentGroundedParams.ArmMoveStrength);
|
||||
}
|
||||
}
|
||||
// Try to keep the wrist straight
|
||||
LimbJoint wrist = GetJointBetweenLimbs(foreArmType, hand.type);
|
||||
if (wrist != null)
|
||||
{
|
||||
hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * CurrentGroundedParams.HandMoveStrength);
|
||||
hand.body.ApplyTorque(MathHelper.Clamp(-wrist.JointAngle, -MathHelper.PiOver2, MathHelper.PiOver2) * hand.Mass * 100f * currentGroundedParams.HandMoveStrength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1292,10 +1303,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDying(float deltaTime)
|
||||
void UpdateFallingProne(float strength)
|
||||
{
|
||||
//the force/torque used to move the limbs goes from 1 to 0 during the death anim duration
|
||||
float strength = 1.0f - deathAnimTimer / deathAnimDuration;
|
||||
if (strength <= 0.0f) { return; }
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
@@ -1319,6 +1329,19 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (torso == null) { return; }
|
||||
|
||||
//make the torso tip over
|
||||
//otherwise it tends to just drop straight down, pinning the characters legs in a weird pose
|
||||
if (!InWater)
|
||||
{
|
||||
//prefer tipping over in the same direction the torso is rotating
|
||||
//or moving
|
||||
//or lastly, in the direction it's facing if it's not moving/rotating
|
||||
float fallDirection = Math.Sign(torso.body.AngularVelocity - torso.body.LinearVelocity.X - Dir * 0.01f);
|
||||
float torque = MathF.Cos(torso.Rotation) * fallDirection * 5.0f * strength;
|
||||
torso.body.ApplyTorque(torque * torso.body.Mass);
|
||||
}
|
||||
|
||||
//attempt to make legs stay in a straight line with the torso to prevent the character from doing a split
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
@@ -1503,12 +1526,12 @@ namespace Barotrauma
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftForearm);
|
||||
if (targetLeftHand == null) targetLeftHand = target.AnimController.GetLimb(LimbType.Torso);
|
||||
if (targetLeftHand == null) targetLeftHand = target.AnimController.MainLimb;
|
||||
if (targetLeftHand == null) { targetLeftHand = target.AnimController.GetLimb(LimbType.Torso); }
|
||||
if (targetLeftHand == null) { targetLeftHand = target.AnimController.MainLimb; }
|
||||
|
||||
Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightForearm);
|
||||
if (targetRightHand == null) targetRightHand = target.AnimController.GetLimb(LimbType.Torso);
|
||||
if (targetRightHand == null) targetRightHand = target.AnimController.MainLimb;
|
||||
if (targetRightHand == null) { targetRightHand = target.AnimController.GetLimb(LimbType.Torso); }
|
||||
if (targetRightHand == null) { targetRightHand = target.AnimController.MainLimb; }
|
||||
|
||||
if (!target.AllowInput)
|
||||
{
|
||||
@@ -1644,18 +1667,24 @@ namespace Barotrauma
|
||||
pullLimb.PullJointEnabled = true;
|
||||
if (targetLimb.type == LimbType.Torso || targetLimb == target.AnimController.MainLimb)
|
||||
{
|
||||
Vector2 pullLimbAnchor = targetLimb.SimPosition;
|
||||
pullLimb.PullJointMaxForce = 5000.0f;
|
||||
if (!character.HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging))
|
||||
{
|
||||
targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY;
|
||||
if (!MathUtils.IsValid(dragDir)) { dragDir = Vector2.UnitY; }
|
||||
|
||||
targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength);
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
float targetDist = Vector2.Distance(targetLimb.SimPosition, shoulderPos);
|
||||
Vector2 dragDir = (targetLimb.SimPosition - shoulderPos) / targetDist;
|
||||
if (!MathUtils.IsValid(dragDir)) { dragDir = -Vector2.UnitY; }
|
||||
if (!InWater)
|
||||
{
|
||||
//lerp the arm downwards when not swimming
|
||||
dragDir = Vector2.Lerp(dragDir, -Vector2.UnitY, 0.5f);
|
||||
}
|
||||
|
||||
Vector2 pullLimbAnchor = shoulderPos + dragDir * Math.Min(targetDist, (upperArmLength + forearmLength) * 2);
|
||||
targetAnchor = shoulderPos + dragDir * (upperArmLength + forearmLength);
|
||||
targetForce = 200.0f;
|
||||
if (target.Submarine != character.Submarine)
|
||||
{
|
||||
|
||||
@@ -1212,7 +1212,9 @@ namespace Barotrauma
|
||||
RefreshFloorY(ignoreStairs: Stairs == null);
|
||||
if (currentHull.WaterPercentage > 0.001f)
|
||||
{
|
||||
float waterSurface = ConvertUnits.ToSimUnits(GetSurfaceY());
|
||||
(float waterSurfaceDisplayUnits, float ceilingDisplayUnits) = GetWaterSurfaceAndCeilingY();
|
||||
float waterSurfaceY = ConvertUnits.ToSimUnits(waterSurfaceDisplayUnits);
|
||||
float ceilingY = ConvertUnits.ToSimUnits(ceilingDisplayUnits);
|
||||
if (targetMovement.Y < 0.0f)
|
||||
{
|
||||
Vector2 colliderBottom = GetColliderBottom();
|
||||
@@ -1222,13 +1224,21 @@ namespace Barotrauma
|
||||
{
|
||||
//set floorY to the position of the floor in the hull below the character
|
||||
var lowerHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(colliderBottom), useWorldCoordinates: false);
|
||||
if (lowerHull != null) floorY = ConvertUnits.ToSimUnits(lowerHull.Rect.Y - lowerHull.Rect.Height);
|
||||
if (lowerHull != null)
|
||||
{
|
||||
floorY = ConvertUnits.ToSimUnits(lowerHull.Rect.Y - lowerHull.Rect.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
float standHeight = HeadPosition ?? TorsoPosition ?? Collider.GetMaxExtent() * 0.5f;
|
||||
if (Collider.SimPosition.Y < waterSurface && waterSurface - floorY > standHeight * 0.8f)
|
||||
if (Collider.SimPosition.Y < waterSurfaceY)
|
||||
{
|
||||
inWater = true;
|
||||
//too deep to stand up, or not enough room to stand up
|
||||
if (waterSurfaceY - floorY > standHeight * 0.8f ||
|
||||
ceilingY - floorY < standHeight * 0.8f)
|
||||
{
|
||||
inWater = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1663,22 +1673,34 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of the surface of water at the position of the character, in display units (taking into account connected hulls above the hull the character is in)
|
||||
/// </summary>
|
||||
public float GetSurfaceY()
|
||||
{
|
||||
return GetWaterSurfaceAndCeilingY().WaterSurfaceY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of the surface of water and the ceiling (= upper edge of the hull) at the position of the character, in display units (taking into account connected hulls above the hull the character is in).
|
||||
/// </summary>
|
||||
private (float WaterSurfaceY, float CeilingY) GetWaterSurfaceAndCeilingY()
|
||||
{
|
||||
//check both hulls: the hull whose coordinate space the ragdoll is in, and the hull whose bounds the character's origin actually is inside
|
||||
if (currentHull == null || character.CurrentHull == null)
|
||||
{
|
||||
return float.PositiveInfinity;
|
||||
return (float.PositiveInfinity, float.PositiveInfinity);
|
||||
}
|
||||
|
||||
float surfacePos = currentHull.Surface;
|
||||
|
||||
float surfaceY = currentHull.Surface;
|
||||
float ceilingY = currentHull.Rect.Y;
|
||||
float surfaceThreshold = ConvertUnits.ToDisplayUnits(Collider.SimPosition.Y + 1.0f);
|
||||
//if the hull is almost full of water, check if there's a water-filled hull above it
|
||||
//and use its water surface instead of the current hull's
|
||||
if (currentHull.Rect.Y - currentHull.Surface < 5.0f)
|
||||
{
|
||||
GetSurfacePos(currentHull, ref surfacePos);
|
||||
void GetSurfacePos(Hull hull, ref float prevSurfacePos)
|
||||
{
|
||||
GetSurfacePos(currentHull, ref surfaceY, ref ceilingY);
|
||||
void GetSurfacePos(Hull hull, ref float prevSurfacePos, ref float ceilingPos)
|
||||
{
|
||||
if (prevSurfacePos > surfaceThreshold) { return; }
|
||||
foreach (Gap gap in hull.ConnectedGaps)
|
||||
@@ -1689,6 +1711,7 @@ namespace Barotrauma
|
||||
//if the gap is above us and leads outside, there's no surface to limit the movement
|
||||
if (!gap.IsRoomToRoom && gap.Position.Y > hull.Position.Y)
|
||||
{
|
||||
ceilingPos += 100000.0f;
|
||||
prevSurfacePos += 100000.0f;
|
||||
return;
|
||||
}
|
||||
@@ -1697,15 +1720,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (linkedTo is Hull otherHull && otherHull != hull && otherHull != currentHull)
|
||||
{
|
||||
prevSurfacePos = Math.Max(surfacePos, otherHull.Surface);
|
||||
GetSurfacePos(otherHull, ref prevSurfacePos);
|
||||
prevSurfacePos = Math.Max(surfaceY, otherHull.Surface);
|
||||
ceilingPos = Math.Max(ceilingPos, otherHull.Rect.Y);
|
||||
GetSurfacePos(otherHull, ref prevSurfacePos, ref ceilingPos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return surfacePos;
|
||||
return (surfaceY, ceilingY);
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true, bool forceMainLimbToCollider = false, bool detachProjectiles = true)
|
||||
|
||||
@@ -343,9 +343,10 @@ namespace Barotrauma
|
||||
return (Duration == 0.0f) ? LevelWallDamage : LevelWallDamage * deltaTime;
|
||||
}
|
||||
|
||||
public float GetItemDamage(float deltaTime)
|
||||
public float GetItemDamage(float deltaTime, float multiplier = 1)
|
||||
{
|
||||
return (Duration == 0.0f) ? ItemDamage : ItemDamage * deltaTime;
|
||||
float dmg = ItemDamage * multiplier;
|
||||
return (Duration == 0.0f) ? dmg : dmg * deltaTime;
|
||||
}
|
||||
|
||||
public float GetTotalDamage(bool includeStructureDamage = false)
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly static List<Character> CharacterList = new List<Character>();
|
||||
|
||||
public const float MaxHighlightDistance = 150.0f;
|
||||
public const float MaxDragDistance = 200.0f;
|
||||
|
||||
partial void UpdateLimbLightSource(Limb limb);
|
||||
|
||||
private bool enabled = true;
|
||||
@@ -176,6 +179,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Identifier? faction;
|
||||
public Identifier Faction
|
||||
{
|
||||
get { return faction ?? HumanPrefab?.Faction ?? Identifier.Empty; }
|
||||
set { faction = value; }
|
||||
}
|
||||
|
||||
private CharacterTeamType teamID;
|
||||
public CharacterTeamType TeamID
|
||||
{
|
||||
@@ -497,7 +507,7 @@ namespace Barotrauma
|
||||
LocalizedString displayName = Params.DisplayName;
|
||||
if (displayName.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Params.SpeciesTranslationOverride))
|
||||
if (Params.SpeciesTranslationOverride.IsEmpty)
|
||||
{
|
||||
displayName = TextManager.Get($"Character.{SpeciesName}");
|
||||
}
|
||||
@@ -529,8 +539,13 @@ namespace Barotrauma
|
||||
}
|
||||
set
|
||||
{
|
||||
bool wasHidden = HideFace;
|
||||
hideFaceTimer = MathHelper.Clamp(hideFaceTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
|
||||
if (info != null && info.IsDisguisedAsAnother != HideFace) info.CheckDisguiseStatus(true);
|
||||
bool isHidden = HideFace;
|
||||
if (isHidden != wasHidden && info != null && info.IsDisguisedAsAnother != isHidden)
|
||||
{
|
||||
info.CheckDisguiseStatus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,7 +775,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (IsUnconscious) { return true; }
|
||||
return CharacterHealth.GetAllAfflictions().Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength);
|
||||
return CharacterHealth.GetAllAfflictions().Any(a => a.Prefab.AfflictionType == AfflictionPrefab.ParalysisType && a.Strength >= a.Prefab.MaxStrength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,9 +837,15 @@ namespace Barotrauma
|
||||
public float HealthPercentage => CharacterHealth.HealthPercentage;
|
||||
public float MaxVitality => CharacterHealth.MaxVitality;
|
||||
public float MaxHealth => MaxVitality;
|
||||
|
||||
/// <summary>
|
||||
/// Was the character in full health at the beginning of the frame?
|
||||
/// </summary>
|
||||
public bool WasFullHealth => CharacterHealth.WasInFullHealth;
|
||||
public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
|
||||
public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached;
|
||||
public float EmpVulnerability => Params.Health.EmpVulnerability;
|
||||
public float PoisonVulnerability => Params.Health.PoisonVulnerability;
|
||||
|
||||
public float Bloodloss
|
||||
{
|
||||
@@ -838,7 +859,7 @@ namespace Barotrauma
|
||||
|
||||
public float Bleeding
|
||||
{
|
||||
get { return CharacterHealth.GetAfflictionStrength("bleeding", true); }
|
||||
get { return CharacterHealth.GetAfflictionStrength(AfflictionPrefab.BleedingType, true); }
|
||||
}
|
||||
|
||||
private bool speechImpedimentSet;
|
||||
@@ -1043,6 +1064,8 @@ namespace Barotrauma
|
||||
|
||||
public bool InWater => AnimController is AnimController { InWater: true };
|
||||
|
||||
public bool IsLowInOxygen => NeedsOxygen && OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
||||
|
||||
public bool GodMode = false;
|
||||
|
||||
public CampaignMode.InteractionType CampaignInteractionType;
|
||||
@@ -1099,6 +1122,12 @@ namespace Barotrauma
|
||||
|
||||
public bool IsInFriendlySub => Submarine != null && Submarine.TeamID == TeamID;
|
||||
|
||||
public float AITurretPriority
|
||||
{
|
||||
get => Params.AITurretPriority;
|
||||
private set => Params.AITurretPriority = value;
|
||||
}
|
||||
|
||||
public delegate void OnDeathHandler(Character character, CauseOfDeath causeOfDeath);
|
||||
public OnDeathHandler OnDeath;
|
||||
|
||||
@@ -1691,7 +1720,7 @@ namespace Barotrauma
|
||||
if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue))
|
||||
{
|
||||
skillLevel += skillValue;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1700,9 +1729,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
skillLevel += GetStatValue(GetSkillStatType(skillIdentifier));
|
||||
|
||||
|
||||
return skillLevel;
|
||||
return Math.Max(skillLevel, 0);
|
||||
}
|
||||
|
||||
// TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner
|
||||
@@ -1900,7 +1927,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (limb != null)
|
||||
{
|
||||
sum += MathHelper.Lerp(0, max, CharacterHealth.GetLimbDamage(limb, afflictionType: "damage"));
|
||||
sum += MathHelper.Lerp(0, max, CharacterHealth.GetLimbDamage(limb, afflictionType: AfflictionPrefab.DamageType));
|
||||
}
|
||||
return Math.Clamp(sum, 0, 1f);
|
||||
}
|
||||
@@ -2201,7 +2228,9 @@ namespace Barotrauma
|
||||
|
||||
if (SelectedCharacter != null)
|
||||
{
|
||||
if (Vector2.DistanceSquared(SelectedCharacter.WorldPosition, WorldPosition) > 90000.0f || !SelectedCharacter.CanBeSelected)
|
||||
if (!SelectedCharacter.CanBeSelected ||
|
||||
(Vector2.DistanceSquared(SelectedCharacter.WorldPosition, WorldPosition) > MaxDragDistance * MaxDragDistance &&
|
||||
SelectedCharacter.GetDistanceToClosestLimb(SimPosition) > ConvertUnits.ToSimUnits(MaxDragDistance)))
|
||||
{
|
||||
DeselectCharacter();
|
||||
}
|
||||
@@ -2494,8 +2523,12 @@ namespace Barotrauma
|
||||
|
||||
if (!skipDistanceCheck)
|
||||
{
|
||||
maxDist = ConvertUnits.ToSimUnits(maxDist);
|
||||
if (Vector2.DistanceSquared(SimPosition, c.SimPosition) > maxDist * maxDist) { return false; }
|
||||
maxDist = Math.Max(ConvertUnits.ToSimUnits(maxDist), c.AnimController.Collider.GetMaxExtent());
|
||||
if (Vector2.DistanceSquared(SimPosition, c.SimPosition) > maxDist * maxDist &&
|
||||
Vector2.DistanceSquared(SimPosition, c.AnimController.MainLimb.SimPosition) > maxDist * maxDist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return checkVisibility ? CanSeeCharacter(c) : true;
|
||||
@@ -2851,6 +2884,23 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
if (Controlled == this)
|
||||
{
|
||||
HealingCooldown.PutOnCooldown();
|
||||
}
|
||||
#elif SERVER
|
||||
if (GameMain.Server?.ConnectedClients is { } clients)
|
||||
{
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
if (c.Character != this) { continue; }
|
||||
|
||||
HealingCooldown.SetCooldown(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
SelectCharacter(FocusedCharacter);
|
||||
#if CLIENT
|
||||
if (Controlled == this)
|
||||
@@ -3070,8 +3120,7 @@ namespace Barotrauma
|
||||
if (NeedsAir)
|
||||
{
|
||||
//implode if not protected from pressure, and either outside or in a high-pressure hull
|
||||
if (!IsProtectedFromPressure() &&
|
||||
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f))
|
||||
if (!IsProtectedFromPressure && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f))
|
||||
{
|
||||
if (CharacterHealth.PressureKillDelay <= 0.0f)
|
||||
{
|
||||
@@ -3098,15 +3147,17 @@ namespace Barotrauma
|
||||
PressureTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) &&
|
||||
PressureProtection < (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f) &&
|
||||
WorldPosition.Y < CharacterHealth.CrushDepth && !HasAbilityFlag(AbilityFlags.ImmuneToPressure))
|
||||
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && !IsProtectedFromPressure)
|
||||
{
|
||||
//implode if below crush depth, and either outside or in a high-pressure hull
|
||||
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
|
||||
float realWorldDepth = Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 0.0f;
|
||||
if (PressureProtection < realWorldDepth && realWorldDepth > CharacterHealth.CrushDepth)
|
||||
{
|
||||
Implode();
|
||||
if (IsDead) { return; }
|
||||
//implode if below crush depth, and either outside or in a high-pressure hull
|
||||
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
|
||||
{
|
||||
Implode();
|
||||
if (IsDead) { return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3140,56 +3191,57 @@ namespace Barotrauma
|
||||
|
||||
UpdateAIChatMessages(deltaTime);
|
||||
|
||||
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
|
||||
bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true;
|
||||
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 8.0f * 8.0f;
|
||||
bool wasRagdolled = false;
|
||||
bool selfRagdolled = false;
|
||||
|
||||
if (IsForceRagdolled)
|
||||
if (GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true)
|
||||
{
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
}
|
||||
else if (this != Controlled)
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll);
|
||||
}
|
||||
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
|
||||
else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll))
|
||||
{
|
||||
if (ragdollingLockTimer > 0.0f)
|
||||
bool wasRagdolled = IsRagdolled;
|
||||
if (IsForceRagdolled)
|
||||
{
|
||||
SetInput(InputType.Ragdoll, false, true);
|
||||
ragdollingLockTimer -= deltaTime;
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
}
|
||||
else if (this != Controlled)
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll);
|
||||
}
|
||||
else
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; }
|
||||
bool tooFastToUnragdoll = bodyMovingTooFast(AnimController.Collider) || bodyMovingTooFast(AnimController.MainLimb.body);
|
||||
bool bodyMovingTooFast(PhysicsBody body)
|
||||
{
|
||||
return
|
||||
body.LinearVelocity.LengthSquared() > 8.0f * 8.0f ||
|
||||
//falling down counts as going too fast
|
||||
(!InWater && body.LinearVelocity.Y < -5.0f);
|
||||
}
|
||||
if (ragdollingLockTimer > 0.0f)
|
||||
{
|
||||
ragdollingLockTimer -= deltaTime;
|
||||
}
|
||||
else if (!tooFastToUnragdoll)
|
||||
{
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.2f; }
|
||||
}
|
||||
if (IsRagdolled)
|
||||
{
|
||||
SetInput(InputType.Ragdoll, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasRagdolled && IsRagdolled)
|
||||
{
|
||||
if (selfRagdolled)
|
||||
if (!wasRagdolled && IsRagdolled)
|
||||
{
|
||||
CheckTalents(AbilityEffectType.OnSelfRagdoll);
|
||||
CheckTalents(AbilityEffectType.OnRagdoll);
|
||||
}
|
||||
// currently does not work when you are stunned, like it should
|
||||
CheckTalents(AbilityEffectType.OnRagdoll);
|
||||
}
|
||||
|
||||
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
|
||||
|
||||
//ragdoll button
|
||||
if (IsRagdolled || !CanMove)
|
||||
{
|
||||
if (AnimController is HumanoidAnimController humanAnimController)
|
||||
{
|
||||
humanAnimController.Crouching = false;
|
||||
}
|
||||
if (IsRagdolled) { AnimController.IgnorePlatforms = true; }
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
return;
|
||||
@@ -3367,6 +3419,20 @@ namespace Barotrauma
|
||||
return distSqr;
|
||||
}
|
||||
|
||||
public float GetDistanceToClosestLimb(Vector2 simPos)
|
||||
{
|
||||
float closestDist = float.MaxValue;
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
float dist = Vector2.Distance(simPos, limb.SimPosition);
|
||||
dist -= limb.body.GetMaxExtent();
|
||||
closestDist = Math.Min(closestDist, dist);
|
||||
if (closestDist <= 0.0f) { return 0.0f; }
|
||||
}
|
||||
return closestDist;
|
||||
}
|
||||
|
||||
private float despawnTimer;
|
||||
private void UpdateDespawn(float deltaTime, bool ignoreThresholds = false, bool createNetworkEvents = true)
|
||||
{
|
||||
@@ -3758,9 +3824,10 @@ namespace Barotrauma
|
||||
message.SendDelay -= deltaTime;
|
||||
if (message.SendDelay > 0.0f) { continue; }
|
||||
|
||||
bool canUseRadio = ChatMessage.CanUseRadio(this, out WifiComponent radio);
|
||||
if (message.MessageType == null)
|
||||
{
|
||||
message.MessageType = ChatMessage.CanUseRadio(this) ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||
message.MessageType = canUseRadio ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||
}
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
|
||||
@@ -3770,6 +3837,11 @@ namespace Barotrauma
|
||||
{
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(Name, modifiedMessage, message.MessageType.Value, this);
|
||||
}
|
||||
if (canUseRadio)
|
||||
{
|
||||
Signal s = new Signal(modifiedMessage, sender: this, source: radio.Item);
|
||||
radio.TransmitSignal(s, sentFromChat: true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if SERVER
|
||||
@@ -4005,17 +4077,7 @@ namespace Barotrauma
|
||||
CheckTalents(AbilityEffectType.OnKillCharacter, abilityCharacterKill);
|
||||
|
||||
if (!IsOnPlayerTeam) { return; }
|
||||
if (CreatureMetrics.Instance.Killed.Contains(target.SpeciesName)) { return; }
|
||||
CreatureMetrics.Instance.Killed.Add(target.SpeciesName);
|
||||
AddEncounter(target);
|
||||
}
|
||||
|
||||
public void AddEncounter(Character other)
|
||||
{
|
||||
if (!IsOnPlayerTeam) { return; }
|
||||
if (CreatureMetrics.Instance.Encountered.Contains(other.SpeciesName)) { return; }
|
||||
CreatureMetrics.Instance.Encountered.Add(other.SpeciesName);
|
||||
CreatureMetrics.Instance.RecentlyEncountered.Add(other.SpeciesName);
|
||||
CreatureMetrics.RecordKill(target.SpeciesName);
|
||||
}
|
||||
|
||||
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false)
|
||||
@@ -4082,17 +4144,23 @@ namespace Barotrauma
|
||||
OnAttackedProjSpecific(attacker, attackResult, stun);
|
||||
if (!wasDead)
|
||||
{
|
||||
TryAdjustAttackerSkill(attacker, CharacterHealth.Vitality - prevVitality);
|
||||
TryAdjustAttackerSkill(attacker, attackResult);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (attackResult.Damage > 0)
|
||||
{
|
||||
LastDamage = attackResult;
|
||||
if (attacker != null)
|
||||
if (attacker != null && attacker != this && !attacker.Removed)
|
||||
{
|
||||
AddAttacker(attacker, attackResult.Damage);
|
||||
AddEncounter(attacker);
|
||||
attacker.AddEncounter(this);
|
||||
if (IsOnPlayerTeam)
|
||||
{
|
||||
CreatureMetrics.AddEncounter(attacker.SpeciesName);
|
||||
}
|
||||
if (attacker.IsOnPlayerTeam)
|
||||
{
|
||||
CreatureMetrics.AddEncounter(SpeciesName);
|
||||
}
|
||||
}
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
@@ -4108,26 +4176,85 @@ namespace Barotrauma
|
||||
|
||||
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun);
|
||||
|
||||
public void TryAdjustAttackerSkill(Character attacker, float healthChange)
|
||||
public void TryAdjustAttackerSkill(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
if (attacker == null) { return; }
|
||||
|
||||
if (!attacker.IsOnPlayerTeam) { return; }
|
||||
bool isEnemy = AIController is EnemyAIController || TeamID != attacker.TeamID;
|
||||
if (isEnemy)
|
||||
if (!isEnemy) { return; }
|
||||
float weaponDamage = 0;
|
||||
float medicalDamage = 0;
|
||||
foreach (var affliction in attackResult.Afflictions)
|
||||
{
|
||||
if (healthChange < 0.0f)
|
||||
if (affliction.Prefab.IsBuff) { continue; }
|
||||
if (Params.IsMachine && !affliction.Prefab.AffectMachines) { continue; }
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType ||
|
||||
affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
{
|
||||
float attackerSkillLevel = attacker.GetSkillLevel("weapons");
|
||||
attacker.Info?.IncreaseSkillLevel("weapons".ToIdentifier(),
|
||||
-healthChange * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, 1.0f));
|
||||
if (!Params.Health.PoisonImmunity)
|
||||
{
|
||||
float relativeVitality = MaxVitality / 100f;
|
||||
// Undo the applied modifiers to get the base value. Poison damage is multiplied by max vitality when it's applied.
|
||||
float dmg = affliction.Strength;
|
||||
if (relativeVitality > 0)
|
||||
{
|
||||
dmg /= relativeVitality;
|
||||
}
|
||||
if (PoisonVulnerability > 0)
|
||||
{
|
||||
dmg /= PoisonVulnerability;
|
||||
}
|
||||
float strength = MaxVitality;
|
||||
if (Params.AI != null)
|
||||
{
|
||||
strength = Params.AI.CombatStrength;
|
||||
}
|
||||
// Adjust the skill gain by the strength of the target. Combat strength >= 1000 gives 2x bonus, combat strength < 333 less than 1x.
|
||||
float vitalityFactor = MathHelper.Lerp(0.5f, 2f, MathUtils.InverseLerp(0, 1000, strength));
|
||||
dmg *= vitalityFactor;
|
||||
medicalDamage += dmg * affliction.Prefab.MedicalSkillGain;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
medicalDamage += affliction.GetVitalityDecrease(null) * affliction.Prefab.MedicalSkillGain;
|
||||
}
|
||||
weaponDamage += affliction.GetVitalityDecrease(null) * affliction.Prefab.WeaponsSkillGain;
|
||||
}
|
||||
else if (healthChange > 0.0f)
|
||||
if (medicalDamage > 0)
|
||||
{
|
||||
float attackerSkillLevel = attacker.GetSkillLevel("medical");
|
||||
attacker.Info?.IncreaseSkillLevel("medical".ToIdentifier(),
|
||||
healthChange * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, 1.0f));
|
||||
IncreaseSkillLevel("medical".ToIdentifier(), medicalDamage);
|
||||
}
|
||||
if (weaponDamage > 0)
|
||||
{
|
||||
IncreaseSkillLevel("weapons".ToIdentifier(), weaponDamage);
|
||||
}
|
||||
|
||||
void IncreaseSkillLevel(Identifier skill, float damage)
|
||||
{
|
||||
float attackerSkillLevel = attacker.GetSkillLevel(skill);
|
||||
// The formula is too generous on low skill levels, hence the minimum divider.
|
||||
float minSkillDivider = 15f;
|
||||
attacker.Info?.IncreaseSkillLevel(skill, damage * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, minSkillDivider));
|
||||
}
|
||||
}
|
||||
|
||||
public void TryAdjustHealerSkill(Character healer, float healthChange = 0, Affliction affliction = null)
|
||||
{
|
||||
if (healer == null) { return; }
|
||||
bool isEnemy = AIController is EnemyAIController || TeamID != healer.TeamID;
|
||||
if (isEnemy) { return; }
|
||||
float medicalGain = healthChange;
|
||||
if (affliction?.Prefab is { IsBuff: true } && (!Params.IsMachine || affliction.Prefab.AffectMachines))
|
||||
{
|
||||
medicalGain += affliction.Strength * affliction.Prefab.MedicalSkillGain;
|
||||
}
|
||||
if (medicalGain <= 0) { return; }
|
||||
Identifier skill = new Identifier("medical");
|
||||
float attackerSkillLevel = healer.GetSkillLevel(skill);
|
||||
// The formula is too generous on low skill levels, hence the minimum divider.
|
||||
float minSkillDivider = 15f;
|
||||
healer.Info?.IncreaseSkillLevel(skill, medicalGain * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, minSkillDivider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -4142,7 +4269,7 @@ namespace Barotrauma
|
||||
if (Screen.Selected != GameMain.GameScreen) { return; }
|
||||
if (newStun > 0 && Params.Health.StunImmunity)
|
||||
{
|
||||
if (EmpVulnerability <= 0 || CharacterHealth.GetAfflictionStrength("emp", allowLimbAfflictions: false) <= 0)
|
||||
if (EmpVulnerability <= 0 || CharacterHealth.GetAfflictionStrength(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -4169,7 +4296,7 @@ namespace Barotrauma
|
||||
float eatingRegen = Params.Health.HealthRegenerationWhenEating;
|
||||
if (eatingRegen > 0)
|
||||
{
|
||||
CharacterHealth.ReduceAfflictionOnAllLimbs("damage".ToIdentifier(), eatingRegen * deltaTime);
|
||||
CharacterHealth.ReduceAfflictionOnAllLimbs(AfflictionPrefab.DamageType, eatingRegen * deltaTime);
|
||||
}
|
||||
}
|
||||
if (statusEffects.TryGetValue(actionType, out var statusEffectList))
|
||||
@@ -4541,6 +4668,7 @@ namespace Barotrauma
|
||||
Submarine = null;
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(worldPos), lerp: false);
|
||||
AnimController.FindHull(worldPos, setSubmarine: true);
|
||||
CurrentHull = AnimController.CurrentHull;
|
||||
if (AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.PathSteering?.ResetPath();
|
||||
@@ -4787,34 +4915,36 @@ namespace Barotrauma
|
||||
return visibleHulls;
|
||||
}
|
||||
|
||||
public Vector2 GetRelativeSimPosition(ISpatialEntity target, Vector2? worldPos = null)
|
||||
public Vector2 GetRelativeSimPosition(ISpatialEntity target, Vector2? worldPos = null) => GetRelativeSimPosition(this, target, worldPos);
|
||||
|
||||
public static Vector2 GetRelativeSimPosition(ISpatialEntity from, ISpatialEntity to, Vector2? worldPos = null)
|
||||
{
|
||||
Vector2 targetPos = target.SimPosition;
|
||||
Vector2 targetPos = to.SimPosition;
|
||||
if (worldPos.HasValue)
|
||||
{
|
||||
Vector2 wp = worldPos.Value;
|
||||
if (target.Submarine != null)
|
||||
if (to.Submarine != null)
|
||||
{
|
||||
wp -= target.Submarine.Position;
|
||||
wp -= to.Submarine.Position;
|
||||
}
|
||||
targetPos = ConvertUnits.ToSimUnits(wp);
|
||||
}
|
||||
if (Submarine == null && target.Submarine != null)
|
||||
if (from.Submarine == null && to.Submarine != null)
|
||||
{
|
||||
// outside and targeting inside
|
||||
targetPos += target.Submarine.SimPosition;
|
||||
targetPos += to.Submarine.SimPosition;
|
||||
}
|
||||
else if (Submarine != null && target.Submarine == null)
|
||||
else if (from.Submarine != null && to.Submarine == null)
|
||||
{
|
||||
// inside and targeting outside
|
||||
targetPos -= Submarine.SimPosition;
|
||||
targetPos -= from.Submarine.SimPosition;
|
||||
}
|
||||
else if (Submarine != target.Submarine)
|
||||
else if (from.Submarine != to.Submarine)
|
||||
{
|
||||
if (Submarine != null && target.Submarine != null)
|
||||
if (from.Submarine != null && to.Submarine != null)
|
||||
{
|
||||
// both inside, but in different subs
|
||||
Vector2 diff = Submarine.SimPosition - target.Submarine.SimPosition;
|
||||
Vector2 diff = from.Submarine.SimPosition - to.Submarine.SimPosition;
|
||||
targetPos -= diff;
|
||||
}
|
||||
}
|
||||
@@ -4836,13 +4966,14 @@ namespace Barotrauma
|
||||
|
||||
public bool HasJob(Identifier identifier) => Info?.Job?.Prefab.Identifier == identifier;
|
||||
|
||||
public bool IsProtectedFromPressure()
|
||||
{
|
||||
return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
|
||||
}
|
||||
/// <summary>
|
||||
/// Is the character currently protected from the pressure by immunity/ability or a status effect (e.g. from a diving suit).
|
||||
/// </summary>
|
||||
public bool IsProtectedFromPressure => IsImmuneToPressure || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
|
||||
|
||||
// Talent logic begins here. Should be encapsulated to its own controller soon
|
||||
public bool IsImmuneToPressure => !NeedsAir || HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
|
||||
#region Talents
|
||||
private readonly List<CharacterTalent> characterTalents = new List<CharacterTalent>();
|
||||
|
||||
public void LoadTalents()
|
||||
@@ -4916,6 +5047,49 @@ namespace Barotrauma
|
||||
return info.UnlockedTalents.Contains(identifier);
|
||||
}
|
||||
|
||||
public bool HasUnlockedAllTalents()
|
||||
{
|
||||
if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))
|
||||
{
|
||||
foreach (TalentSubTree talentSubTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
|
||||
{
|
||||
if (!talentOption.HasMaxTalents(info.UnlockedTalents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasTalents()
|
||||
{
|
||||
return characterTalents.Any();
|
||||
}
|
||||
|
||||
public void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
|
||||
{
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.CheckTalent(abilityEffectType, abilityObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckTalents(AbilityEffectType abilityEffectType)
|
||||
{
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.CheckTalent(abilityEffectType, null);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnTalentGiven(TalentPrefab talentPrefab);
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly HashSet<Hull> sameRoomHulls = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -4942,24 +5116,6 @@ namespace Barotrauma
|
||||
return sameRoomHulls.Contains(character.CurrentHull);
|
||||
}
|
||||
|
||||
public bool HasUnlockedAllTalents()
|
||||
{
|
||||
if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))
|
||||
{
|
||||
foreach (TalentSubTree talentSubTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
|
||||
{
|
||||
if (!talentOption.HasMaxTalents(info.UnlockedTalents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<Character> GetFriendlyCrew(Character character)
|
||||
{
|
||||
if (character is null)
|
||||
@@ -4969,27 +5125,6 @@ namespace Barotrauma
|
||||
return CharacterList.Where(c => HumanAIController.IsFriendly(character, c, onlySameTeam: true) && !c.IsDead);
|
||||
}
|
||||
|
||||
public bool HasTalents()
|
||||
{
|
||||
return characterTalents.Any();
|
||||
}
|
||||
|
||||
public void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
|
||||
{
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.CheckTalent(abilityEffectType, abilityObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckTalents(AbilityEffectType abilityEffectType)
|
||||
{
|
||||
foreach (var characterTalent in characterTalents)
|
||||
{
|
||||
characterTalent.CheckTalent(abilityEffectType, null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRecipeForItem(Identifier recipeIdentifier)
|
||||
{
|
||||
return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
|
||||
@@ -5053,7 +5188,6 @@ namespace Barotrauma
|
||||
#endif
|
||||
|
||||
partial void OnMoneyChanged(int prevAmount, int newAmount);
|
||||
partial void OnTalentGiven(TalentPrefab talentPrefab);
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary is used for stats that are required very frequently. Not very performant, but easier to develop with for now.
|
||||
@@ -5208,7 +5342,24 @@ namespace Barotrauma
|
||||
|
||||
public bool IsFriendly(Character other) => IsFriendly(this, other);
|
||||
|
||||
public static bool IsFriendly(Character me, Character other) => AIController.IsOnFriendlyTeam(me, other) && IsSameSpeciesOrGroup(me, other);
|
||||
public static bool IsFriendly(Character me, Character other) => IsOnFriendlyTeam(me, other) && IsSameSpeciesOrGroup(me, other);
|
||||
|
||||
public static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
|
||||
{
|
||||
if (myTeam == otherTeam) { return true; }
|
||||
return myTeam switch
|
||||
{
|
||||
// NPCs are friendly to the same team and the friendly NPCs
|
||||
CharacterTeamType.None or CharacterTeamType.Team1 or CharacterTeamType.Team2 => otherTeam == CharacterTeamType.FriendlyNPC,
|
||||
// Friendly NPCs are friendly to both player teams
|
||||
CharacterTeamType.FriendlyNPC => otherTeam == CharacterTeamType.Team1 || otherTeam == CharacterTeamType.Team2,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(Character me, Character other) => IsOnFriendlyTeam(me.TeamID, other.TeamID);
|
||||
public bool IsOnFriendlyTeam(Character other) => IsOnFriendlyTeam(TeamID, other.TeamID);
|
||||
public bool IsOnFriendlyTeam(CharacterTeamType otherTeam) => IsOnFriendlyTeam(TeamID, otherTeam);
|
||||
|
||||
public bool IsSameSpeciesOrGroup(Character other) => IsSameSpeciesOrGroup(this, other);
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using static Barotrauma.CharacterInfo;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,6 +17,8 @@ namespace Barotrauma
|
||||
|
||||
public string Name => Identifier.Value;
|
||||
public Identifier VariantOf { get; }
|
||||
public CharacterPrefab ParentPrefab { get; set; }
|
||||
|
||||
public void InheritFrom(CharacterPrefab parent)
|
||||
{
|
||||
ConfigElement = CharacterParams.CreateVariantXml(originalElement, parent.ConfigElement).FromPackage(ConfigElement.ContentPackage);
|
||||
@@ -38,7 +38,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private XElement originalElement;
|
||||
private readonly XElement originalElement;
|
||||
public ContentXElement ConfigElement { get; private set; }
|
||||
|
||||
public CharacterInfoPrefab CharacterInfoPrefab { get; private set; }
|
||||
@@ -49,10 +49,6 @@ namespace Barotrauma
|
||||
public static CharacterFile HumanConfigFile => HumanPrefab.ContentFile as CharacterFile;
|
||||
public static CharacterPrefab HumanPrefab => FindBySpeciesName(HumanSpeciesName);
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a character config file from all currently selected content packages,
|
||||
/// or from a specific package if the contentPackage parameter is given.
|
||||
/// </summary>
|
||||
public static CharacterPrefab FindBySpeciesName(Identifier speciesName)
|
||||
{
|
||||
if (!Prefabs.ContainsKey(speciesName)) { return null; }
|
||||
|
||||
+24
-5
@@ -19,6 +19,10 @@ namespace Barotrauma
|
||||
|
||||
private float fluctuationTimer;
|
||||
|
||||
private AfflictionPrefab.Effect activeEffect;
|
||||
private float prevActiveEffectStrength;
|
||||
protected bool activeEffectDirty = true;
|
||||
|
||||
protected float _strength;
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes), Editable]
|
||||
@@ -46,6 +50,7 @@ namespace Barotrauma
|
||||
Duration = Prefab.Duration;
|
||||
}
|
||||
_strength = newValue;
|
||||
activeEffectDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +152,16 @@ namespace Barotrauma
|
||||
MathHelper.Clamp((int)Math.Floor(strength / maxStrength * strengthTexts.Length), 0, strengthTexts.Length - 1)];
|
||||
}
|
||||
|
||||
public AfflictionPrefab.Effect GetActiveEffect() => Prefab.GetActiveEffect(Strength);
|
||||
public AfflictionPrefab.Effect GetActiveEffect()
|
||||
{
|
||||
if (activeEffectDirty)
|
||||
{
|
||||
activeEffect = Prefab.GetActiveEffect(_strength);
|
||||
prevActiveEffectStrength = _strength;
|
||||
activeEffectDirty = false;
|
||||
}
|
||||
return activeEffect;
|
||||
}
|
||||
|
||||
public float GetVitalityDecrease(CharacterHealth characterHealth)
|
||||
{
|
||||
@@ -158,7 +172,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (strength < Prefab.ActivationThreshold) { return 0.0f; }
|
||||
strength = MathHelper.Clamp(strength, 0.0f, Prefab.MaxStrength);
|
||||
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(strength);
|
||||
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
|
||||
if (currentEffect == null) { return 0.0f; }
|
||||
if (currentEffect.MaxStrength - currentEffect.MinStrength <= 0.0f) { return 0.0f; }
|
||||
|
||||
@@ -349,7 +363,7 @@ namespace Barotrauma
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
{
|
||||
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return 0.0f; }
|
||||
if (GetViableEffect() is not AfflictionPrefab.Effect currentEffect) { return 0.0f; }
|
||||
|
||||
if (currentEffect.AfflictionStatValues.TryGetValue(statType, out var value))
|
||||
{
|
||||
@@ -363,7 +377,7 @@ namespace Barotrauma
|
||||
|
||||
public bool HasFlag(AbilityFlags flagType)
|
||||
{
|
||||
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return false; }
|
||||
if (GetViableEffect() is not AfflictionPrefab.Effect currentEffect) { return false; }
|
||||
return currentEffect.AfflictionAbilityFlags.HasFlag(flagType);
|
||||
}
|
||||
|
||||
@@ -415,6 +429,7 @@ namespace Barotrauma
|
||||
}
|
||||
// Don't use the property, because it's virtual and some afflictions like husk overload it for external use.
|
||||
_strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength);
|
||||
activeEffectDirty |= !MathUtils.NearlyEqual(prevActiveEffectStrength, _strength);
|
||||
|
||||
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
|
||||
{
|
||||
@@ -442,7 +457,10 @@ namespace Barotrauma
|
||||
var currentEffect = GetActiveEffect();
|
||||
if (currentEffect != null)
|
||||
{
|
||||
currentEffect.StatusEffects.ForEach(se => ApplyStatusEffect(type, se, deltaTime, characterHealth, targetLimb));
|
||||
foreach (var statusEffect in currentEffect.StatusEffects)
|
||||
{
|
||||
ApplyStatusEffect(type, statusEffect, deltaTime, characterHealth, targetLimb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,6 +499,7 @@ namespace Barotrauma
|
||||
{
|
||||
_nonClampedStrength = strength;
|
||||
_strength = _nonClampedStrength;
|
||||
activeEffectDirty |= !MathUtils.NearlyEqual(_strength, prevActiveEffectStrength);
|
||||
}
|
||||
|
||||
public bool ShouldShowIcon(Character afflictedCharacter)
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ namespace Barotrauma
|
||||
DeactivateHusk();
|
||||
highestStrength = 0;
|
||||
}
|
||||
activeEffectDirty = true;
|
||||
}
|
||||
}
|
||||
private float highestStrength;
|
||||
|
||||
+61
-9
@@ -319,6 +319,7 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
public readonly float MinInterval, MaxInterval;
|
||||
public readonly float MinStrength, MaxStrength;
|
||||
|
||||
public PeriodicEffect(ContentXElement element, string parentDebugName)
|
||||
{
|
||||
@@ -333,23 +334,38 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
MinInterval = Math.Max(element.GetAttributeFloat("mininterval", 1.0f), 1.0f);
|
||||
MaxInterval = Math.Max(element.GetAttributeFloat("maxinterval", 1.0f), MinInterval);
|
||||
MinInterval = Math.Max(element.GetAttributeFloat(nameof(MinInterval), 1.0f), 1.0f);
|
||||
MaxInterval = Math.Max(element.GetAttributeFloat(nameof(MaxInterval), 1.0f), MinInterval);
|
||||
MinStrength = Math.Max(element.GetAttributeFloat(nameof(MinStrength), 0f), 0f);
|
||||
MaxStrength = Math.Max(element.GetAttributeFloat(nameof(MaxStrength), MinStrength), MinStrength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly Identifier DamageType = "damage".ToIdentifier();
|
||||
public static readonly Identifier BurnType = "burn".ToIdentifier();
|
||||
public static readonly Identifier BleedingType = "bleeding".ToIdentifier();
|
||||
public static readonly Identifier ParalysisType = "paralysis".ToIdentifier();
|
||||
public static readonly Identifier PoisonType = "poison".ToIdentifier();
|
||||
public static readonly Identifier StunType = "stun".ToIdentifier();
|
||||
public static readonly Identifier EMPType = "emp".ToIdentifier();
|
||||
public static readonly Identifier SpaceHerpesType = "spaceherpes".ToIdentifier();
|
||||
public static readonly Identifier AlienInfectedType = "alieninfected".ToIdentifier();
|
||||
public static readonly Identifier InvertControlsType = "invertcontrols".ToIdentifier();
|
||||
public static readonly Identifier HuskInfectionType = "huskinfection".ToIdentifier();
|
||||
|
||||
public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"];
|
||||
public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"];
|
||||
public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"];
|
||||
public static AfflictionPrefab Bleeding => Prefabs["bleeding"];
|
||||
public static AfflictionPrefab Burn => Prefabs["burn"];
|
||||
public static AfflictionPrefab Bleeding => Prefabs[BleedingType];
|
||||
public static AfflictionPrefab Burn => Prefabs[BurnType];
|
||||
public static AfflictionPrefab OxygenLow => Prefabs["oxygenlow"];
|
||||
public static AfflictionPrefab Bloodloss => Prefabs["bloodloss"];
|
||||
public static AfflictionPrefab Pressure => Prefabs["pressure"];
|
||||
public static AfflictionPrefab Stun => Prefabs["stun"];
|
||||
public static AfflictionPrefab Stun => Prefabs[StunType];
|
||||
public static AfflictionPrefab RadiationSickness => Prefabs["radiationsickness"];
|
||||
|
||||
|
||||
public static readonly PrefabCollection<AfflictionPrefab> Prefabs = new PrefabCollection<AfflictionPrefab>();
|
||||
|
||||
public override void Dispose() { }
|
||||
@@ -413,8 +429,8 @@ namespace Barotrauma
|
||||
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
|
||||
public float KarmaChangeOnApplied;
|
||||
|
||||
public float BurnOverlayAlpha;
|
||||
public float DamageOverlayAlpha;
|
||||
public readonly float BurnOverlayAlpha;
|
||||
public readonly float DamageOverlayAlpha;
|
||||
|
||||
//steam achievement given when the affliction is removed from the controlled character
|
||||
public readonly Identifier AchievementOnRemoved;
|
||||
@@ -425,6 +441,20 @@ namespace Barotrauma
|
||||
public readonly Sprite AfflictionOverlay;
|
||||
public readonly bool AfflictionOverlayAlphaIsLinear;
|
||||
|
||||
public readonly bool DamageParticles;
|
||||
|
||||
/// <summary>
|
||||
/// An arbitrary modifier that affects how much medical skill is increased when you apply the affliction on a target.
|
||||
/// If the affliction causes damage or is of type poison or paralysis, the skill is increased only when the target is hostile.
|
||||
/// If the affliction is of type buff, the skill is increased only when the target is friendly.
|
||||
/// </summary>
|
||||
public readonly float MedicalSkillGain;
|
||||
/// <summary>
|
||||
/// An arbitrary modifier that affects how much weapons skill is increased when you apply the affliction on a target.
|
||||
/// The skill is increased only when the target is hostile.
|
||||
/// </summary>
|
||||
public readonly float WeaponsSkillGain;
|
||||
|
||||
private readonly List<Effect> effects = new List<Effect>();
|
||||
private readonly List<PeriodicEffect> periodicEffects = new List<PeriodicEffect>();
|
||||
|
||||
@@ -519,8 +549,14 @@ namespace Barotrauma
|
||||
|
||||
KarmaChangeOnApplied = element.GetAttributeFloat(nameof(KarmaChangeOnApplied), 0.0f);
|
||||
|
||||
CauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}").Fallback(element.GetAttributeString("causeofdeathdescription", ""));
|
||||
SelfCauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}").Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
|
||||
CauseOfDeathDescription =
|
||||
TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}")
|
||||
.Fallback(TextManager.Get(element.GetAttributeString("causeofdeathdescription", "")))
|
||||
.Fallback(element.GetAttributeString("causeofdeathdescription", ""));
|
||||
SelfCauseOfDeathDescription =
|
||||
TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}")
|
||||
.Fallback(TextManager.Get(element.GetAttributeString("selfcauseofdeathdescription", "")))
|
||||
.Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
|
||||
|
||||
IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
|
||||
AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false);
|
||||
@@ -530,6 +566,10 @@ namespace Barotrauma
|
||||
|
||||
ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
|
||||
|
||||
DamageParticles = element.GetAttributeBool(nameof(DamageParticles), true);
|
||||
WeaponsSkillGain = element.GetAttributeFloat(nameof(WeaponsSkillGain), 0.0f);
|
||||
MedicalSkillGain = element.GetAttributeFloat(nameof(MedicalSkillGain), 0.0f);
|
||||
|
||||
List<Description> descriptions = new List<Description>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
@@ -604,6 +644,18 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < effects.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < effects.Count; j++)
|
||||
{
|
||||
var a = effects[i];
|
||||
var b = effects[j];
|
||||
if (a.MinStrength < b.MaxStrength && b.MinStrength < a.MaxStrength)
|
||||
{
|
||||
DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearEffects()
|
||||
|
||||
@@ -230,6 +230,11 @@ namespace Barotrauma
|
||||
|
||||
public float StunTimer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Was the character in full health at the beginning of the frame?
|
||||
/// </summary>
|
||||
public bool WasInFullHealth { get; private set; }
|
||||
|
||||
public Affliction PressureAffliction
|
||||
{
|
||||
get { return pressureAffliction; }
|
||||
@@ -334,12 +339,12 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public T GetAffliction<T>(string identifier, bool allowLimbAfflictions = true) where T : Affliction
|
||||
public T GetAffliction<T>(Identifier identifier, bool allowLimbAfflictions = true) where T : Affliction
|
||||
{
|
||||
return GetAffliction(identifier, allowLimbAfflictions) as T;
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string identifier, Limb limb)
|
||||
public Affliction GetAffliction(Identifier identifier, Limb limb)
|
||||
{
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
{
|
||||
@@ -375,7 +380,7 @@ namespace Barotrauma
|
||||
/// <param name="limb">The limb the affliction is attached to</param>
|
||||
/// <param name="requireLimbSpecific">Does the affliction have to be attached to only the specific limb.
|
||||
/// Most monsters for example don't have separate healths for different limbs, essentially meaning that every affliction is applied to every limb.</param>
|
||||
public float GetAfflictionStrength(string afflictionType, Limb limb, bool requireLimbSpecific)
|
||||
public float GetAfflictionStrength(Identifier afflictionType, Limb limb, bool requireLimbSpecific)
|
||||
{
|
||||
if (requireLimbSpecific && limbHealths.Count == 1) { return 0.0f; }
|
||||
|
||||
@@ -396,7 +401,7 @@ namespace Barotrauma
|
||||
return strength;
|
||||
}
|
||||
|
||||
public float GetAfflictionStrength(string afflictionType, bool allowLimbAfflictions = true)
|
||||
public float GetAfflictionStrength(Identifier afflictionType, bool allowLimbAfflictions = true)
|
||||
{
|
||||
float strength = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
@@ -478,16 +483,19 @@ namespace Barotrauma
|
||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||
}
|
||||
|
||||
public void ReduceAfflictionOnAllLimbs(Identifier affliction, float amount, ActionType? treatmentAction = null)
|
||||
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null)
|
||||
{
|
||||
if (affliction.IsEmpty) { throw new ArgumentException($"{nameof(affliction)} is empty"); }
|
||||
|
||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||
|
||||
matchingAfflictions.Clear();
|
||||
matchingAfflictions.AddRange(afflictions.Keys);
|
||||
matchingAfflictions.RemoveAll(a =>
|
||||
a.Prefab.Identifier != affliction &&
|
||||
a.Prefab.AfflictionType != affliction);
|
||||
|
||||
foreach (var affliction in afflictions)
|
||||
{
|
||||
if (affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType)
|
||||
{
|
||||
matchingAfflictions.Add(affliction.Key);
|
||||
}
|
||||
}
|
||||
|
||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||
}
|
||||
|
||||
@@ -504,18 +512,21 @@ namespace Barotrauma
|
||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||
}
|
||||
|
||||
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier affliction, float amount, ActionType? treatmentAction = null)
|
||||
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null)
|
||||
{
|
||||
if (affliction.IsEmpty) { throw new ArgumentException($"{nameof(affliction)} is empty"); }
|
||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||
|
||||
|
||||
matchingAfflictions.Clear();
|
||||
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
||||
|
||||
matchingAfflictions.RemoveAll(a =>
|
||||
a.Prefab.Identifier != affliction &&
|
||||
a.Prefab.AfflictionType != affliction);
|
||||
|
||||
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
||||
foreach (var affliction in afflictions)
|
||||
{
|
||||
if ((affliction.Key.Prefab.Identifier == afflictionIdOrType || affliction.Key.Prefab.AfflictionType == afflictionIdOrType) &&
|
||||
affliction.Value == targetLimbHealth)
|
||||
{
|
||||
matchingAfflictions.Add(affliction.Key);
|
||||
}
|
||||
}
|
||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||
}
|
||||
|
||||
@@ -617,7 +628,7 @@ namespace Barotrauma
|
||||
KillIfOutOfVitality();
|
||||
}
|
||||
|
||||
public float GetLimbDamage(Limb limb, string afflictionType = null)
|
||||
public float GetLimbDamage(Limb limb, Identifier afflictionType)
|
||||
{
|
||||
float damageStrength;
|
||||
if (limb.IsSevered)
|
||||
@@ -630,16 +641,16 @@ namespace Barotrauma
|
||||
// Therefore with e.g. 80 health, the max damage per limb would be 40.
|
||||
// Having at least 40 damage on both legs would cause maximum limping.
|
||||
float max = MaxVitality / 2;
|
||||
if (string.IsNullOrEmpty(afflictionType))
|
||||
if (afflictionType.IsEmpty)
|
||||
{
|
||||
float damage = GetAfflictionStrength("damage", limb, true);
|
||||
float bleeding = GetAfflictionStrength("bleeding", limb, true);
|
||||
float burn = GetAfflictionStrength("burn", limb, true);
|
||||
float damage = GetAfflictionStrength(AfflictionPrefab.DamageType, limb, true);
|
||||
float bleeding = GetAfflictionStrength(AfflictionPrefab.BleedingType, limb, true);
|
||||
float burn = GetAfflictionStrength(AfflictionPrefab.BurnType, limb, true);
|
||||
damageStrength = Math.Min(damage + bleeding + burn, max);
|
||||
}
|
||||
else
|
||||
{
|
||||
damageStrength = Math.Min(GetAfflictionStrength("damage", limb, true), max);
|
||||
damageStrength = Math.Min(GetAfflictionStrength(afflictionType, limb, true), max);
|
||||
}
|
||||
return damageStrength / max;
|
||||
}
|
||||
@@ -696,15 +707,16 @@ namespace Barotrauma
|
||||
if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun")
|
||||
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == AfflictionPrefab.StunType)
|
||||
{
|
||||
if (Character.EmpVulnerability <= 0 || GetAfflictionStrength("emp", allowLimbAfflictions: false) <= 0)
|
||||
if (Character.EmpVulnerability <= 0 || GetAfflictionStrength(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Character.Params.Health.PoisonImmunity && newAffliction.Prefab.AfflictionType == "poison") { return; }
|
||||
if (Character.EmpVulnerability <= 0 && newAffliction.Prefab.AfflictionType == "emp") { return; }
|
||||
if (Character.Params.Health.PoisonImmunity &&
|
||||
(newAffliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType || newAffliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)) { return; }
|
||||
if (Character.EmpVulnerability <= 0 && newAffliction.Prefab.AfflictionType == AfflictionPrefab.EMPType) { return; }
|
||||
if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s == Character.SpeciesName)) { return; }
|
||||
|
||||
Affliction existingAffliction = null;
|
||||
@@ -742,7 +754,8 @@ namespace Barotrauma
|
||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
|
||||
newAffliction.Source);
|
||||
afflictions.Add(copyAffliction, limbHealth);
|
||||
|
||||
MedicalClinic.OnAfflictionCountChanged(Character);
|
||||
|
||||
Character.HealthUpdateInterval = 0.0f;
|
||||
|
||||
CalculateVitality();
|
||||
@@ -766,6 +779,8 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
WasInFullHealth = vitality >= MaxVitality;
|
||||
|
||||
UpdateOxygen(deltaTime);
|
||||
|
||||
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
|
||||
@@ -813,10 +828,16 @@ namespace Barotrauma
|
||||
}
|
||||
Character.StackSpeedMultiplier(affliction.GetSpeedMultiplier());
|
||||
}
|
||||
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
}
|
||||
|
||||
if (afflictionsToRemove.Count is not 0)
|
||||
{
|
||||
MedicalClinic.OnAfflictionCountChanged(Character);
|
||||
}
|
||||
}
|
||||
|
||||
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.MovementSpeed));
|
||||
@@ -870,6 +891,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 0-1.
|
||||
/// </summary>
|
||||
public float OxygenLowResistance => !Character.NeedsOxygen ? 1 : GetResistance(oxygenLowAffliction.Prefab);
|
||||
|
||||
private void UpdateOxygen(float deltaTime)
|
||||
{
|
||||
if (!Character.NeedsOxygen)
|
||||
@@ -978,6 +1004,8 @@ namespace Barotrauma
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
Character.Kill(type, affliction);
|
||||
|
||||
WasInFullHealth = false;
|
||||
#if CLIENT
|
||||
DisplayVitalityDelay = 0.0f;
|
||||
DisplayedVitality = Vitality;
|
||||
|
||||
@@ -490,13 +490,9 @@ namespace Barotrauma
|
||||
|
||||
public int RefJointIndex => Params.RefJoint;
|
||||
|
||||
private List<WearableSprite> wearingItems;
|
||||
public List<WearableSprite> WearingItems
|
||||
{
|
||||
get { return wearingItems; }
|
||||
}
|
||||
public readonly List<WearableSprite> WearingItems = new List<WearableSprite>();
|
||||
|
||||
public List<WearableSprite> OtherWearables { get; private set; } = new List<WearableSprite>();
|
||||
public readonly List<WearableSprite> OtherWearables = new List<WearableSprite>();
|
||||
|
||||
public bool PullJointEnabled
|
||||
{
|
||||
@@ -640,7 +636,6 @@ namespace Barotrauma
|
||||
this.ragdoll = ragdoll;
|
||||
this.character = character;
|
||||
this.Params = limbParams;
|
||||
wearingItems = new List<WearableSprite>();
|
||||
dir = Direction.Right;
|
||||
body = new PhysicsBody(limbParams);
|
||||
type = limbParams.Type;
|
||||
@@ -772,7 +767,7 @@ namespace Barotrauma
|
||||
tempModifiers.Add(damageModifier);
|
||||
}
|
||||
}
|
||||
foreach (WearableSprite wearable in wearingItems)
|
||||
foreach (WearableSprite wearable in WearingItems)
|
||||
{
|
||||
foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
|
||||
{
|
||||
@@ -791,10 +786,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (!foundMatchingModifier && random > affliction.Probability) { continue; }
|
||||
float finalDamageModifier = damageMultiplier;
|
||||
if (affliction.Prefab.AfflictionType == "emp" && character.EmpVulnerability > 0)
|
||||
if (character.EmpVulnerability > 0 && affliction.Prefab.AfflictionType == AfflictionPrefab.EMPType)
|
||||
{
|
||||
finalDamageModifier *= character.EmpVulnerability;
|
||||
}
|
||||
if (!character.Params.Health.PoisonImmunity &&
|
||||
(affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType || affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType))
|
||||
{
|
||||
finalDamageModifier *= character.PoisonVulnerability;
|
||||
}
|
||||
foreach (DamageModifier damageModifier in tempModifiers)
|
||||
{
|
||||
float damageModifierValue = damageModifier.DamageMultiplier;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
public Identifier SpeciesName { get; private set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "If the creature is a variant that needs to use a pre-existing translation."), Editable]
|
||||
public string SpeciesTranslationOverride { get; private set; }
|
||||
public Identifier SpeciesTranslationOverride { get; private set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "If the display name is not defined, the game first tries to find the translated name. If that is not found, the species name will be used."), Editable]
|
||||
public string DisplayName { get; private set; }
|
||||
@@ -113,6 +113,12 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool DrawLast { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, "Tells the bots how much they should prefer targeting this character with submarine weapons. Defaults to 1. Set 0 to tell the bots not to target this character at all. Distance to the target affects the decision making."), Editable]
|
||||
public float AITurretPriority { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, "Tells the bots how much they should prefer targeting this character with submarine weapons tagged as \"slowturret\", like railguns. The tag is arbitrary and can be added to any turrets, just like the priority. Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making."), Editable]
|
||||
public float AISlowTurretPriority { get; set; }
|
||||
|
||||
public readonly CharacterFile File;
|
||||
|
||||
public XDocument VariantFile { get; private set; }
|
||||
@@ -479,7 +485,7 @@ namespace Barotrauma
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool DoesBleed { get; set; }
|
||||
|
||||
[Serialize(float.NegativeInfinity, IsPropertySaveable.Yes), Editable(minValue: float.NegativeInfinity, maxValue: 0)]
|
||||
[Serialize(float.PositiveInfinity, IsPropertySaveable.Yes), Editable(minValue: 0, maxValue: float.PositiveInfinity)]
|
||||
public float CrushDepth { get; set; }
|
||||
|
||||
// Make editable?
|
||||
@@ -504,6 +510,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
public bool PoisonImmunity { get; set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, description: "1 = default, 0 = immune."), Editable(MinValueFloat = 0f, MaxValueFloat = 1000, DecimalCount = 1)]
|
||||
public float PoisonVulnerability { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes), Editable]
|
||||
public float EmpVulnerability { get; set; }
|
||||
|
||||
@@ -512,7 +521,20 @@ namespace Barotrauma
|
||||
|
||||
// TODO: limbhealths, sprite?
|
||||
|
||||
public HealthParams(ContentXElement element, CharacterParams character) : base(element, character) { }
|
||||
public HealthParams(ContentXElement element, CharacterParams character) : base(element, character)
|
||||
{
|
||||
//backwards compatibility
|
||||
if (CrushDepth < 0)
|
||||
{
|
||||
//invert y, convert to meters, and add 1000 to be on the safe side (previously the value would be from the bottom of the level)
|
||||
float newCrushDepth = -CrushDepth * Physics.DisplayToRealWorldRatio + 1000;
|
||||
DebugConsole.AddWarning($"Character \"{character.SpeciesName}\" has a negative crush depth. "+
|
||||
"Previously the crush depths were defined as display units (e.g. -30000 would correspond to 300 meters below the level), "+
|
||||
"but now they're in meters (e.g. 3000 would correspond to a depth of 3000 meters displayed on the nav terminal). "+
|
||||
$"Changing the crush depth from {CrushDepth} to {newCrushDepth}.");
|
||||
CrushDepth = newCrushDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InventoryParams : SubParam
|
||||
|
||||
-1
@@ -28,7 +28,6 @@ namespace Barotrauma.Abilities
|
||||
conditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-13
@@ -37,19 +37,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
if (category != MapEntityCategory.None)
|
||||
{
|
||||
if (!itemPrefab.Category.HasFlag(category)) { return false; }
|
||||
}
|
||||
|
||||
if (identifiers.Any())
|
||||
{
|
||||
if (!identifiers.Any(t => itemPrefab.Identifier == t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
|
||||
return MatchesItem(itemPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -57,5 +45,22 @@ namespace Barotrauma.Abilities
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesItem(ItemPrefab itemPrefab)
|
||||
{
|
||||
if (category != MapEntityCategory.None)
|
||||
{
|
||||
if (!itemPrefab.Category.HasFlag(category)) { return false; }
|
||||
}
|
||||
|
||||
if (identifiers.Any())
|
||||
{
|
||||
if (!identifiers.Any(t => itemPrefab.Identifier == t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-15
@@ -8,19 +8,19 @@ namespace Barotrauma.Abilities
|
||||
class AbilityConditionMission : AbilityConditionData
|
||||
{
|
||||
private readonly ImmutableHashSet<MissionType> missionType;
|
||||
private readonly ImmutableHashSet<Identifier> factions;
|
||||
private readonly bool isAffiliated;
|
||||
|
||||
public AbilityConditionMission(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
string[] missionTypeStrings = conditionElement.GetAttributeStringArray("missiontype", new []{ "None" })!;
|
||||
HashSet<MissionType> missionTypes = new HashSet<MissionType>();
|
||||
factions = conditionElement.GetAttributeIdentifierImmutableHashSet("faction", ImmutableHashSet<Identifier>.Empty);
|
||||
isAffiliated = conditionElement.GetAttributeBool("isaffiliated", false);
|
||||
|
||||
foreach (string missionTypeString in missionTypeStrings)
|
||||
{
|
||||
if (!Enum.TryParse(missionTypeString, out MissionType parsedMission) || parsedMission is MissionType.None)
|
||||
{
|
||||
if (factions.IsEmpty)
|
||||
if (!isAffiliated)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in AbilityConditionMission \"{characterTalent.DebugIdentifier}\" - \"{missionTypeString}\" is not a valid mission type.");
|
||||
}
|
||||
@@ -37,23 +37,28 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (abilityObject is IAbilityMission { Mission: { } mission })
|
||||
{
|
||||
if (factions.Any())
|
||||
{
|
||||
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
|
||||
if (!isAffiliated) { return CheckMissionType(); }
|
||||
|
||||
foreach (var (factionIdentifier, amount) in mission.ReputationRewards)
|
||||
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
|
||||
|
||||
foreach (var (factionIdentifier, amount) in mission.ReputationRewards)
|
||||
{
|
||||
if (amount <= 0) { continue; }
|
||||
if (GetMatchingFaction(factionIdentifier) is { } faction &&
|
||||
Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
if (amount <= 0) { continue; }
|
||||
if (factions.FirstOrDefault(faction => factionIdentifier == faction.Prefab.Identifier) is Faction faction &&
|
||||
Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return CheckMissionType();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return missionType.Contains(mission.Prefab.Type);
|
||||
return false;
|
||||
|
||||
Faction GetMatchingFaction(Identifier factionIdentifier) =>
|
||||
factionIdentifier == "location"
|
||||
? mission.OriginLocation?.Faction
|
||||
: factions.FirstOrDefault(f => factionIdentifier == f.Prefab.Identifier);
|
||||
|
||||
bool CheckMissionType() => missionType.IsEmpty || missionType.Contains(mission.Prefab.Type);
|
||||
}
|
||||
|
||||
LogAbilityConditionError(abilityObject, typeof(IAbilityMission));
|
||||
|
||||
+19
-3
@@ -1,19 +1,35 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
internal sealed class CharacterAbilityRemoveRandomIngredient : CharacterAbility
|
||||
{
|
||||
public CharacterAbilityRemoveRandomIngredient(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { }
|
||||
private readonly AbilityConditionItem? condition;
|
||||
|
||||
public CharacterAbilityRemoveRandomIngredient(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
var conditionElement = abilityElement.GetChildElement(nameof(AbilityConditionItem));
|
||||
if (conditionElement != null)
|
||||
{
|
||||
condition = new AbilityConditionItem(CharacterTalent, conditionElement);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is not Fabricator.AbilityFabricationItemIngredients { Items.Count: > 0 } ingredients) { return; }
|
||||
|
||||
int randomIndex = Rand.Int(ingredients.Items.Count, Rand.RandSync.Unsynced);
|
||||
ingredients.Items.RemoveAt(randomIndex);
|
||||
List<Item> applicableIngredients = condition == null ?
|
||||
ingredients.Items.ToList() :
|
||||
ingredients.Items.Where(it => condition.MatchesItem(it.Prefab)).ToList();
|
||||
if (applicableIngredients.None()) { return; }
|
||||
|
||||
ingredients.Items.Remove(applicableIngredients.GetRandom(Rand.RandSync.Unsynced));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user