Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -331,6 +331,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (targetSlot < 0) { return false; }
|
||||
//the item should always stay in the Any slot if it's containable in one
|
||||
if (pickable.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
targetInventory.TryPutItem(item, Character, CharacterInventory.AnySlot);
|
||||
}
|
||||
return targetInventory.TryPutItem(item, targetSlot, allowSwapping, allowCombine: false, Character);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -344,12 +344,12 @@ namespace Barotrauma
|
||||
private Identifier GetTargetingTag(AITarget aiTarget)
|
||||
{
|
||||
if (aiTarget?.Entity == null) { return Identifier.Empty; }
|
||||
string targetingTag = string.Empty;
|
||||
Identifier targetingTag = Identifier.Empty;
|
||||
if (aiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter.IsDead)
|
||||
{
|
||||
targetingTag = "dead";
|
||||
targetingTag = "dead".ToIdentifier();
|
||||
}
|
||||
else if (AIParams.TryGetTarget(targetCharacter.CharacterHealth.GetActiveAfflictionTags(), out CharacterParams.TargetParams tp) && tp.Threshold >= Character.GetDamageDoneByAttacker(targetCharacter))
|
||||
{
|
||||
@@ -357,11 +357,11 @@ namespace Barotrauma
|
||||
}
|
||||
else if (PetBehavior != null && aiTarget.Entity == PetBehavior.Owner)
|
||||
{
|
||||
targetingTag = "owner";
|
||||
targetingTag = "owner".ToIdentifier();
|
||||
}
|
||||
else if (PetBehavior != null && (!Character.IsOnFriendlyTeam(targetCharacter) || IsAttackingOwner(targetCharacter)))
|
||||
{
|
||||
targetingTag = "hostile";
|
||||
targetingTag = "hostile".ToIdentifier();
|
||||
}
|
||||
else if (AIParams.TryGetTarget(targetCharacter, out CharacterParams.TargetParams tP))
|
||||
{
|
||||
@@ -373,25 +373,25 @@ namespace Barotrauma
|
||||
{
|
||||
// Pets see other pets as pets by default.
|
||||
// Monsters see them only as pet only when they have a matching ai target. Otherwise they use the other tags, specified below.
|
||||
targetingTag = "pet";
|
||||
targetingTag = "pet".ToIdentifier();
|
||||
}
|
||||
else if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
{
|
||||
targetingTag = "husk";
|
||||
targetingTag = "husk".ToIdentifier();
|
||||
}
|
||||
else if (!Character.IsSameSpeciesOrGroup(targetCharacter))
|
||||
{
|
||||
if (enemy.CombatStrength > CombatStrength)
|
||||
{
|
||||
targetingTag = "stronger";
|
||||
targetingTag = "stronger".ToIdentifier();
|
||||
}
|
||||
else if (enemy.CombatStrength < CombatStrength)
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
targetingTag = "weaker".ToIdentifier();
|
||||
}
|
||||
else
|
||||
{
|
||||
targetingTag = "equal";
|
||||
targetingTag = "equal".ToIdentifier();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,27 +406,27 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetingTag.IsNullOrEmpty())
|
||||
if (targetingTag.IsEmpty)
|
||||
{
|
||||
if (targetItem.GetComponent<Sonar>() != null)
|
||||
{
|
||||
targetingTag = "sonar";
|
||||
targetingTag = "sonar".ToIdentifier();
|
||||
}
|
||||
if (targetItem.GetComponent<Door>() != null)
|
||||
{
|
||||
targetingTag = "door";
|
||||
targetingTag = "door".ToIdentifier();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity is Structure)
|
||||
{
|
||||
targetingTag = "wall";
|
||||
targetingTag = "wall".ToIdentifier();
|
||||
}
|
||||
else if (aiTarget.Entity is Hull)
|
||||
{
|
||||
targetingTag = "room";
|
||||
targetingTag = "room".ToIdentifier();
|
||||
}
|
||||
return targetingTag.ToIdentifier();
|
||||
return targetingTag;
|
||||
}
|
||||
|
||||
public override void SelectTarget(AITarget target) => SelectTarget(target, 100);
|
||||
@@ -767,7 +767,7 @@ namespace Barotrauma
|
||||
mainLimb.body.SmoothRotate(rotation, Character.AnimController.SwimFastParams.TorsoTorque);
|
||||
}
|
||||
}
|
||||
if (disableTailCoroutine == null && SelectedAiTarget.Entity is Item i && i.HasTag("guardianshelter"))
|
||||
if (disableTailCoroutine == null && SelectedAiTarget.Entity is Item i && i.HasTag(Tags.GuardianShelter))
|
||||
{
|
||||
if (!CoroutineManager.IsCoroutineRunning(disableTailCoroutine))
|
||||
{
|
||||
@@ -864,10 +864,11 @@ namespace Barotrauma
|
||||
}
|
||||
// Ensure that the creature keeps inside the level
|
||||
SteerInsideLevel(deltaTime);
|
||||
float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
|
||||
steeringManager.Update(speed);
|
||||
float targetMovement = useSteeringLengthAsMovementSpeed ? Steering.Length() : speed;
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, targetMovement);
|
||||
float defaultSpeed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
|
||||
//calculate a normalized Steering value at this point: we multiply it with the actual, desired speed in ApplyMovementLimits
|
||||
steeringManager.Update(1.0f);
|
||||
float speed = useSteeringLengthAsMovementSpeed ? Steering.Length() : defaultSpeed;
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, speed);
|
||||
if (Character.CurrentHull != null && Character.AnimController.InWater)
|
||||
{
|
||||
// Limit the swimming speed inside the sub.
|
||||
@@ -1935,14 +1936,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (advance)
|
||||
{
|
||||
if (pathSteering != null)
|
||||
{
|
||||
pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
}
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2310,7 +2304,7 @@ namespace Barotrauma
|
||||
if (damageTarget != null)
|
||||
{
|
||||
Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
|
||||
item.Use(deltaTime, Character);
|
||||
item.Use(deltaTime, user: Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2545,6 +2539,7 @@ namespace Barotrauma
|
||||
item.body.LinearVelocity *= 0.9f;
|
||||
item.body.LinearVelocity -= velocity * 0.25f;
|
||||
bool wasBroken = item.Condition <= 0.0f;
|
||||
item.LastEatenTime = (float)Timing.TotalTimeUnpaused;
|
||||
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
|
||||
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
if (item.Condition <= 0.0f)
|
||||
@@ -3116,6 +3111,13 @@ namespace Barotrauma
|
||||
// ignore if owner is tagged to be explicitly ignored (Feign Death)
|
||||
continue;
|
||||
}
|
||||
var characterTargetingTag = GetTargetingTag(owner.AiTarget);
|
||||
if (!characterTargetingTag.IsEmpty)
|
||||
{
|
||||
// if the enemy is configured to ignore the target character, ignore the provocative item they're holding/wearing too
|
||||
var characterTargetingParams = GetTargetParams(characterTargetingTag);
|
||||
if (characterTargetingParams?.State == AIState.Idle) { continue; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace Barotrauma
|
||||
Vector2 forward = VectorExtensions.Forward(rotation);
|
||||
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(toTarget, forward));
|
||||
if (angle > 70) { continue; }
|
||||
if (!Character.CanSeeCharacter(c)) { continue; }
|
||||
if (!Character.CanSeeTarget(c)) { continue; }
|
||||
if (dist < closestDistance || closestEnemy == null)
|
||||
{
|
||||
closestEnemy = c;
|
||||
@@ -465,7 +465,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Allows bots to heal targets autonomously while swimming outside of the sub.
|
||||
if (AIObjectiveRescueAll.IsValidTarget(Character, Character))
|
||||
if (AIObjectiveRescueAll.IsValidTarget(Character, Character, out _))
|
||||
{
|
||||
AddTargets<AIObjectiveRescueAll, Character>(Character, Character);
|
||||
}
|
||||
@@ -480,24 +480,33 @@ namespace Barotrauma
|
||||
if (objectiveManager.CurrentObjective == null) { return; }
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
bool run = (objectiveManager.CurrentObjective.ForceRun && !objectiveManager.CurrentObjective.ForceWalk) || (!objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority);
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
|
||||
var currentObjective = objectiveManager.CurrentObjective;
|
||||
bool run = !currentObjective.ForceWalk && (currentObjective.ForceRun || objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority);
|
||||
if (currentObjective is AIObjectiveGoTo goTo)
|
||||
{
|
||||
if (Character.CurrentHull == null)
|
||||
if (run && goTo == objectiveManager.ForcedOrder && goTo.IsWaitOrder && !Character.IsOnPlayerTeam)
|
||||
{
|
||||
run = Vector2.DistanceSquared(Character.WorldPosition, goTo.Target.WorldPosition) > 300 * 300;
|
||||
// NPCs with a wait order don't run.
|
||||
run = false;
|
||||
}
|
||||
else
|
||||
else if (goTo.Target != null)
|
||||
{
|
||||
float yDiff = goTo.Target.WorldPosition.Y - Character.WorldPosition.Y;
|
||||
if (Math.Abs(yDiff) > 100)
|
||||
if (Character.CurrentHull == null)
|
||||
{
|
||||
run = true;
|
||||
run = Vector2.DistanceSquared(Character.WorldPosition, goTo.Target.WorldPosition) > 300 * 300;
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDiff = goTo.Target.WorldPosition.X - Character.WorldPosition.X;
|
||||
run = Math.Abs(xDiff) > 500;
|
||||
float yDiff = goTo.Target.WorldPosition.Y - Character.WorldPosition.Y;
|
||||
if (Math.Abs(yDiff) > 100)
|
||||
{
|
||||
run = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDiff = goTo.Target.WorldPosition.X - Character.WorldPosition.X;
|
||||
run = Math.Abs(xDiff) > 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -577,7 +586,7 @@ namespace Barotrauma
|
||||
if (Character.LockHands) { return; }
|
||||
if (ObjectiveManager.CurrentObjective == null) { return; }
|
||||
if (Character.CurrentHull == null) { return; }
|
||||
bool shouldActOnSuffocation = Character.IsLowInOxygen && !Character.AnimController.HeadInWater && HasDivingSuit(Character, requireOxygenTank: false) && !HasItem(Character, AIObjectiveFindDivingGear.OXYGEN_SOURCE, out _, conditionPercentage: 1);
|
||||
bool shouldActOnSuffocation = Character.IsLowInOxygen && !Character.AnimController.HeadInWater && HasDivingSuit(Character, requireOxygenTank: false) && !HasItem(Character, Tags.OxygenSource, out _, conditionPercentage: 1);
|
||||
bool isCarrying = ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>();
|
||||
|
||||
bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective)
|
||||
@@ -594,7 +603,7 @@ namespace Barotrauma
|
||||
if (findItemState != FindItemState.OtherItem)
|
||||
{
|
||||
var decontain = ObjectiveManager.GetActiveObjectives<AIObjectiveDecontainItem>().LastOrDefault();
|
||||
if (decontain != null && decontain.TargetItem != null && decontain.TargetItem.HasTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR) &&
|
||||
if (decontain != null && decontain.TargetItem != null && decontain.TargetItem.HasTag(Tags.HeavyDivingGear) &&
|
||||
ObjectiveManager.GetActiveObjective() is AIObjectiveGoTo gotoObjective && NeedsDivingGearOnPath(gotoObjective))
|
||||
{
|
||||
// Don't try to put the diving suit in a locker if the suit would be needed in any hull in the path to the locker.
|
||||
@@ -680,8 +689,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (removeDivingSuit)
|
||||
{
|
||||
var divingSuit = Character.Inventory.FindItemByTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR);
|
||||
if (divingSuit != null && !divingSuit.HasTag(AIObjectiveFindDivingGear.DIVING_GEAR_WEARABLE_INDOORS))
|
||||
var divingSuit = Character.Inventory.FindItemByTag(Tags.HeavyDivingGear);
|
||||
if (divingSuit != null && !divingSuit.HasTag(Tags.DivingGearWearableIndoors))
|
||||
{
|
||||
if (shouldActOnSuffocation || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
@@ -723,9 +732,9 @@ namespace Barotrauma
|
||||
}
|
||||
if (takeMaskOff)
|
||||
{
|
||||
if (Character.HasEquippedItem(AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR))
|
||||
if (Character.HasEquippedItem(Tags.LightDivingGear))
|
||||
{
|
||||
var mask = Character.Inventory.FindItemByTag(AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR);
|
||||
var mask = Character.Inventory.FindItemByTag(Tags.LightDivingGear);
|
||||
if (mask != null)
|
||||
{
|
||||
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
@@ -928,7 +937,7 @@ namespace Barotrauma
|
||||
if (isPreferencesDefined)
|
||||
{
|
||||
// Use any valid locker as a fall back container.
|
||||
return container.Item.HasTag("locker") ? 0.5f : 0;
|
||||
return container.Item.HasTag(Tags.FallbackLocker) ? 0.5f : 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -1069,7 +1078,7 @@ namespace Barotrauma
|
||||
foreach (Character target in Character.CharacterList)
|
||||
{
|
||||
if (target.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRescueAll.IsValidTarget(target, Character))
|
||||
if (AIObjectiveRescueAll.IsValidTarget(target, Character, out _))
|
||||
{
|
||||
if (AddTargets<AIObjectiveRescueAll, Character>(Character, target) && newOrder == null && (!Character.IsMedic || Character == target) && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
@@ -1287,6 +1296,7 @@ namespace Barotrauma
|
||||
minorDamageThreshold = 10;
|
||||
majorDamageThreshold = 40;
|
||||
}
|
||||
bool eitherIsMentallyUnstable = IsMentallyUnstable || attacker.AIController is { IsMentallyUnstable: true };
|
||||
if (IsFriendly(attacker))
|
||||
{
|
||||
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
|
||||
@@ -1296,7 +1306,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
float cumulativeDamage = realDamage + Character.GetDamageDoneByAttacker(attacker);
|
||||
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && attacker.CombatAction == null;
|
||||
bool isAccidental = attacker.IsBot && !eitherIsMentallyUnstable && attacker.CombatAction == null;
|
||||
if (isAccidental)
|
||||
{
|
||||
if (attacker.TeamID != Character.TeamID || (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold))
|
||||
@@ -1306,7 +1316,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.AlienInfectedType) > 0;
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.AlienInfectedType) > 0;
|
||||
// Inform other NPCs
|
||||
if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold)
|
||||
{
|
||||
@@ -1378,6 +1388,11 @@ namespace Barotrauma
|
||||
if (otherCharacter.IsPlayer) { continue; }
|
||||
if (otherCharacter.AIController is not HumanAIController otherHumanAI) { continue; }
|
||||
if (!otherHumanAI.IsFriendly(Character)) { continue; }
|
||||
if (attacker.AIController is EnemyAIController enemyAI && otherHumanAI.IsFriendly(attacker))
|
||||
{
|
||||
// Don't react to friendly enemy AI attacking other characters. E.g. husks attacking someone when whe are a cultist.
|
||||
continue;
|
||||
}
|
||||
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
|
||||
if (!isWitnessing)
|
||||
{
|
||||
@@ -1411,6 +1426,10 @@ namespace Barotrauma
|
||||
humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() is Controller ?
|
||||
AIObjectiveCombat.CombatMode.None : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
if (c.IsKiller)
|
||||
{
|
||||
return AIObjectiveCombat.CombatMode.Offensive;
|
||||
}
|
||||
return
|
||||
humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() ||
|
||||
humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders) ?
|
||||
@@ -1442,6 +1461,10 @@ namespace Barotrauma
|
||||
isAttackerFightingEnemy = true;
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
if (c.IsKiller)
|
||||
{
|
||||
return AIObjectiveCombat.CombatMode.Offensive;
|
||||
}
|
||||
if (isWitnessing && c.CombatAction != null && !c.IsSecurity)
|
||||
{
|
||||
return c.CombatAction.WitnessReaction;
|
||||
@@ -1452,7 +1475,7 @@ namespace Barotrauma
|
||||
isAttackerFightingEnemy = true;
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (instigator.CombatAction != null ? instigator.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
|
||||
}
|
||||
if (attacker.TeamID == CharacterTeamType.FriendlyNPC && attacker.AIController != null && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable))
|
||||
if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !eitherIsMentallyUnstable)
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
{
|
||||
@@ -1653,7 +1676,7 @@ namespace Barotrauma
|
||||
needsSuit = (hull == null || hull.LethalPressure > 0) && !Character.IsImmuneToPressure;
|
||||
return needsAir || needsSuit;
|
||||
}
|
||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||
if (hull.WaterPercentage > 60 || (hull.IsWetRoom && hull.WaterPercentage > 10) || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||
{
|
||||
return needsAir;
|
||||
}
|
||||
@@ -1666,14 +1689,14 @@ namespace Barotrauma
|
||||
/// Check whether the character has a diving suit in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
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,
|
||||
=> HasItem(character, Tags.HeavyDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true,
|
||||
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes | InvSlotType.InnerClothes));
|
||||
|
||||
/// <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, bool requireOxygenTank = true)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||
|
||||
private static List<Item> matchingItems = new List<Item>();
|
||||
|
||||
@@ -1739,7 +1762,7 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!otherCharacter.CanSeeCharacter(character)) { continue; }
|
||||
if (!otherCharacter.CanSeeTarget(character)) { continue; }
|
||||
|
||||
if (!otherHumanAI.structureDamageAccumulator.ContainsKey(character)) { otherHumanAI.structureDamageAccumulator.Add(character, 0.0f); }
|
||||
float prevAccumulatedDamage = otherHumanAI.structureDamageAccumulator[character];
|
||||
@@ -1816,7 +1839,7 @@ namespace Barotrauma
|
||||
bool someoneSpoke = false;
|
||||
bool stolenItemsInside = item.OwnInventory?.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true).Any() ?? false;
|
||||
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker"))
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag(Tags.HandLockerItem))
|
||||
{
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
@@ -1827,14 +1850,14 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
|
||||
if (!otherCharacter.CanSeeCharacter(thief)) { continue; }
|
||||
if (!otherCharacter.CanSeeTarget(thief)) { continue; }
|
||||
// Don't react if the player is taking an extinguisher and there's any fires on the sub, or diving gear when the sub is flooding
|
||||
// -> allow them to use the emergency items
|
||||
if (thief.Submarine != null)
|
||||
{
|
||||
var connectedHulls = thief.Submarine.GetHulls(alsoFromConnectedSubs: true);
|
||||
if (item.HasTag("fireextinguisher") && connectedHulls.Any(h => h.FireSources.Any())) { continue; }
|
||||
if (item.HasTag("diving") && connectedHulls.Any(h => h.ConnectedGaps.Any(g => AIObjectiveFixLeaks.IsValidTarget(g, thief)))) { continue; }
|
||||
if (item.HasTag(Tags.FireExtinguisher) && connectedHulls.Any(h => h.FireSources.Any())) { continue; }
|
||||
if (item.HasTag(Tags.DivingGear) && connectedHulls.Any(h => h.ConnectedGaps.Any(g => AIObjectiveFixLeaks.IsValidTarget(g, thief)))) { continue; }
|
||||
}
|
||||
if (!someoneSpoke)
|
||||
{
|
||||
@@ -1966,7 +1989,7 @@ namespace Barotrauma
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (c.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRescueAll.IsValidTarget(c, character))
|
||||
if (AIObjectiveRescueAll.IsValidTarget(c, character, out _))
|
||||
{
|
||||
AddTargets<AIObjectiveRescueAll, Character>(character, c);
|
||||
}
|
||||
@@ -2171,7 +2194,10 @@ namespace Barotrauma
|
||||
{
|
||||
bool sameTeam = me.TeamID == other.TeamID;
|
||||
bool teamGood = sameTeam || !onlySameTeam && me.IsOnFriendlyTeam(other);
|
||||
if (!teamGood) { return false; }
|
||||
if (!teamGood)
|
||||
{
|
||||
return other.IsHusk && me.IsDisguisedAsHusk;
|
||||
}
|
||||
if (other.IsPet)
|
||||
{
|
||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||
|
||||
@@ -241,7 +241,6 @@ namespace Barotrauma
|
||||
float priority = MathHelper.Lerp(3, 1, character.Params.PathFinderPriority);
|
||||
findPathTimer = priority * Rand.Range(1.0f, 1.2f);
|
||||
IsPathDirty = false;
|
||||
return DiffToCurrentNode();
|
||||
|
||||
void SkipCurrentPathNodes()
|
||||
{
|
||||
@@ -284,15 +283,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 diff = DiffToCurrentNode();
|
||||
var collider = character.AnimController.Collider;
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController;
|
||||
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.Height / 2 + collider.Radius)
|
||||
{
|
||||
// TODO: might cause some edge cases -> do we need this?
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
if (diff == Vector2.Zero) { return Vector2.Zero; }
|
||||
return Vector2.Normalize(diff) * weight;
|
||||
}
|
||||
@@ -401,16 +391,15 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
bool nextLadderSameAsCurrent = currentLadder == nextLadder;
|
||||
float colliderHeight = collider.Height / 2 + collider.Radius;
|
||||
float heightDiff = currentPath.CurrentNode.SimPosition.Y - collider.SimPosition.Y;
|
||||
float distanceMargin = ConvertUnits.ToDisplayUnits(colliderSize.X);
|
||||
if (currentLadder != null && nextLadder != null)
|
||||
{
|
||||
//climbing ladders -> don't move horizontally
|
||||
diff.X = 0.0f;
|
||||
}
|
||||
//at the same height as the waypoint
|
||||
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
||||
float colliderHeight = collider.Height / 2 + collider.Radius;
|
||||
float distanceMargin = ConvertUnits.ToDisplayUnits(colliderSize.X);
|
||||
if (heightDiff < colliderHeight * 1.25f)
|
||||
if (Math.Abs(heightDiff) < colliderHeight * 1.25f)
|
||||
{
|
||||
if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
{
|
||||
@@ -463,10 +452,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
return ConvertUnits.ToSimUnits(diff);
|
||||
}
|
||||
else if (character.AnimController.InWater)
|
||||
{
|
||||
// Swimming
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
if (door == null || door.CanBeTraversed)
|
||||
{
|
||||
@@ -502,6 +491,13 @@ namespace Barotrauma
|
||||
bool isTargetTooLow = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y;
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 5, 0, 1));
|
||||
float colliderHeight = collider.Height / 2 + collider.Radius;
|
||||
float heightDiff = currentPath.CurrentNode.SimPosition.Y - collider.SimPosition.Y;
|
||||
if (heightDiff < colliderHeight)
|
||||
{
|
||||
//the waypoint is between the top and bottom of the collider, no need to move vertically.
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
if (currentPath.CurrentNode.Stairs != null)
|
||||
{
|
||||
bool isNextNodeInSameStairs = currentPath.NextNode?.Stairs == currentPath.CurrentNode.Stairs;
|
||||
|
||||
@@ -328,7 +328,7 @@ namespace Barotrauma
|
||||
if (checkedSpeakers.Any(s => !potentialSpeaker.CanHearCharacter(s))) { return false; }
|
||||
|
||||
//check if the character is close enough to see the rest of the speakers (this should be replaced with a more performant method)
|
||||
if (checkedSpeakers.Any(s => !potentialSpeaker.CanSeeCharacter(s))) { return false; }
|
||||
if (checkedSpeakers.Any(s => !potentialSpeaker.CanSeeTarget(s))) { return false; }
|
||||
|
||||
//check if the character has an appropriate personality
|
||||
if (selectedConversation.allowedSpeakerTags.Count > 0)
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Barotrauma
|
||||
public virtual bool AllowOutsideSubmarine => false;
|
||||
public virtual bool AllowInFriendlySubs => false;
|
||||
public virtual bool AllowInAnySub => false;
|
||||
public virtual bool AllowWhileHandcuffed => true;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
private float _cumulatedDevotion;
|
||||
@@ -246,32 +247,43 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IgnoreAtOutpost && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!AllowWhileHandcuffed && character.LockHands) { return false; }
|
||||
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
|
||||
// Evaluate ignored at outpost first, because it has higher priority than AllowInAnySub or AllowInFriendlySubs.
|
||||
if (IsIgnoredAtOutpost()) { return false; }
|
||||
if (AllowInAnySub) { return true; }
|
||||
if ((AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) || character.IsEscorted) { return true; }
|
||||
return character.Submarine.TeamID == character.TeamID ||
|
||||
character.Submarine.TeamID == character.OriginalTeamID ||
|
||||
character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID || sub.TeamID == character.OriginalTeamID);
|
||||
return character.Submarine.TeamID == character.TeamID || character.Submarine.TeamID == character.OriginalTeamID;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true only when at a friendly outpost and when the order is set to be ignored there.
|
||||
/// Note that even if this returns false, the objective can be disallowed, because AllowInFriendlySubs is false.
|
||||
/// </summary>
|
||||
public bool IsIgnoredAtOutpost()
|
||||
{
|
||||
if (!IgnoreAtOutpost) { return false; }
|
||||
if (!Level.IsLoadedFriendlyOutpost) { return false; }
|
||||
if (!character.IsOnPlayerTeam) { return false; }
|
||||
if (character.Submarine?.Info == null) { return false; }
|
||||
return character.Submarine.Info.IsOutpost && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC;
|
||||
}
|
||||
|
||||
protected void HandleNonAllowed()
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !IsIgnoredAtOutpost();
|
||||
}
|
||||
|
||||
protected virtual float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
if (isOrder)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
}
|
||||
@@ -446,7 +458,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool Check()
|
||||
private bool Check()
|
||||
{
|
||||
if (AbortCondition != null && AbortCondition(this))
|
||||
{
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(PowerContainer battery)
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
if (battery.OutputDisabled) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
|
||||
+10
-7
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "cleanup item".ToIdentifier();
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => false;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public readonly Item item;
|
||||
public bool IsPriority { get; set; }
|
||||
@@ -30,8 +31,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
else
|
||||
@@ -119,18 +119,21 @@ namespace Barotrauma
|
||||
if (item.IgnoreByAI(character))
|
||||
{
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
if (item.ParentInventory != null)
|
||||
else if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrder<AIObjectiveCleanupItems>()))
|
||||
if (!objectiveManager.HasOrder<AIObjectiveCleanupItems>())
|
||||
{
|
||||
// Don't allow taking items from containers in the idle state.
|
||||
Abandon = true;
|
||||
}
|
||||
else if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character))
|
||||
{
|
||||
// Target was picked up or moved by someone.
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return IsCompleted;
|
||||
return !Abandon && IsCompleted;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+7
-7
@@ -14,8 +14,6 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<Item> prioritizedItems = new List<Item>();
|
||||
|
||||
public static readonly Identifier AllowCleanupTag = "allowcleanup".ToIdentifier();
|
||||
|
||||
protected override int MaxTargets => 100;
|
||||
|
||||
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, Item prioritizedItem = null, float priorityModifier = 1)
|
||||
@@ -83,9 +81,8 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsValidContainer(Item container, Character character, bool allowUnloading = true) =>
|
||||
allowUnloading &&
|
||||
container.HasTag(AllowCleanupTag) &&
|
||||
public static bool IsValidContainer(Item container, Character character) =>
|
||||
container.HasTag(Tags.AllowCleanup) &&
|
||||
container.HasAccess(character) &&
|
||||
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
|
||||
container.GetComponent<ItemContainer>() != null &&
|
||||
@@ -103,15 +100,18 @@ namespace Barotrauma
|
||||
// In a character inventory
|
||||
return false;
|
||||
}
|
||||
if (!IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
if (!allowUnloading) { return false; }
|
||||
if (!IsValidContainer(item.Container, character)) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
//something (e.g. a pet) was eating the item within the last second - don't clean up
|
||||
if (item.LastEatenTime > Timing.TotalTimeUnpaused - 1.0) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
if (wire.Connections.Any()) { return false; }
|
||||
if (wire.Connections.Any(c => c != null)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+172
-182
@@ -46,7 +46,6 @@ namespace Barotrauma
|
||||
_weapon = value;
|
||||
_weaponComponent = null;
|
||||
hasAimed = false;
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
}
|
||||
}
|
||||
private ItemComponent _weaponComponent;
|
||||
@@ -55,14 +54,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Weapon == null) { return null; }
|
||||
if (_weaponComponent == null)
|
||||
{
|
||||
_weaponComponent =
|
||||
Weapon.GetComponent<RangedWeapon>() ??
|
||||
Weapon.GetComponent<MeleeWeapon>() ??
|
||||
Weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||
}
|
||||
return _weaponComponent;
|
||||
return _weaponComponent ?? GetWeaponComponent(Weapon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,13 +272,13 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case CombatMode.Arrest:
|
||||
if (HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out _, requireEquipped: true))
|
||||
if (HumanAIController.HasItem(Enemy, Tags.HandLockerItem, out _, requireEquipped: true))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
else if (Enemy.IsKnockedDown &&
|
||||
!objectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>() &&
|
||||
!HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out _, requireEquipped: false))
|
||||
!HumanAIController.HasItem(character, Tags.HandLockerItem, out _, requireEquipped: false))
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
@@ -348,8 +340,10 @@ namespace Barotrauma
|
||||
if (character.LockHands || Enemy == null)
|
||||
{
|
||||
Weapon = null;
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
return false;
|
||||
}
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
|
||||
if (checkWeaponsTimer < 0)
|
||||
{
|
||||
checkWeaponsTimer = checkWeaponsInterval;
|
||||
@@ -375,7 +369,7 @@ namespace Barotrauma
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
}
|
||||
if (Reload(seekAmmo: false))
|
||||
if (Reload(seekAmmo: isAllowedToSeekWeapons))
|
||||
{
|
||||
// All good, we can use the weapon.
|
||||
break;
|
||||
@@ -407,7 +401,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
bool isAllowedToSeekWeapons = character.CurrentHull != null && !IsEnemyCloserThan(300) && character.IsOnPlayerTeam && IsOffensiveOrArrest;
|
||||
if (!isAllowedToSeekWeapons)
|
||||
{
|
||||
if (WeaponComponent == null)
|
||||
@@ -416,7 +409,7 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && (WeaponComponent == null || WeaponComponent.CombatPriority < goodWeaponPriority))
|
||||
else if (seekAmmunitionObjective == null && (WeaponComponent == null || (WeaponComponent.CombatPriority < goodWeaponPriority)))
|
||||
{
|
||||
// Poor weapon equipped -> try to find better.
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
@@ -431,27 +424,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (Weapon != null && (i == Weapon || i.Prefab.Identifier == Weapon.Prefab.Identifier)) { return 0; }
|
||||
if (i.IsOwnedBy(character)) { return 0; }
|
||||
var mw = i.GetComponent<MeleeWeapon>();
|
||||
var rw = i.GetComponent<RangedWeapon>();
|
||||
float priority = 0;
|
||||
if (mw != null)
|
||||
if (GetWeaponComponent(i) is ItemComponent ic)
|
||||
{
|
||||
priority = mw.CombatPriority / 100;
|
||||
}
|
||||
else if (rw != null)
|
||||
{
|
||||
priority = rw.CombatPriority / 100;
|
||||
}
|
||||
if (i.HasTag("stunner"))
|
||||
{
|
||||
if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
priority *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
priority = GetWeaponPriority(ic, prioritizeMelee: false, isCloseToEnemy: false, out _) / 100;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
@@ -477,6 +453,7 @@ namespace Barotrauma
|
||||
if (!CheckWeapon(seekAmmo: false))
|
||||
{
|
||||
Weapon = null;
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
}
|
||||
}
|
||||
return Weapon != null;
|
||||
@@ -521,111 +498,164 @@ namespace Barotrauma
|
||||
|
||||
private Item FindWeapon(out ItemComponent weaponComponent) => GetWeapon(FindWeaponsFromInventory(), out weaponComponent);
|
||||
|
||||
private static ItemComponent GetWeaponComponent(Item item) =>
|
||||
item.GetComponent<MeleeWeapon>() ??
|
||||
item.GetComponent<RangedWeapon>() ??
|
||||
item.GetComponent<RepairTool>() ??
|
||||
item.GetComponent<Holdable>() as ItemComponent;
|
||||
|
||||
private float GetWeaponPriority(ItemComponent weapon, bool prioritizeMelee, bool isCloseToEnemy, out float lethalDmg)
|
||||
{
|
||||
lethalDmg = -1;
|
||||
float priority = weapon.CombatPriority;
|
||||
if (weapon is RepairTool repairTool)
|
||||
{
|
||||
switch (repairTool.UsableIn)
|
||||
{
|
||||
case RepairTool.UseEnvironment.Air:
|
||||
if (character.InWater) { return 0; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.Water:
|
||||
if (!character.InWater) { return 0; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.None:
|
||||
return 0;
|
||||
case RepairTool.UseEnvironment.Both:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prioritizeMelee && weapon is MeleeWeapon)
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
if (weapon.IsEmpty(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && isCloseToEnemy)
|
||||
{
|
||||
// Ignore weapons that don't have any ammunition (-> Don't seek ammo).
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reduce the priority for weapons that don't have proper ammunition loaded.
|
||||
if (character.HasEquippedItem(Weapon, predicate: CharacterInventory.IsHandSlotType))
|
||||
{
|
||||
// Yet prefer the equipped weapon.
|
||||
priority *= 0.75f;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority *= 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Enemy.Params.Health.StunImmunity)
|
||||
{
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
else if (Enemy.IsKnockedDown)
|
||||
{
|
||||
// Enemy is stunned, reduce the priority of stunner weapons.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float max = lethalDmg + 1;
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
priority = max;
|
||||
}
|
||||
else
|
||||
{
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
priority = Math.Clamp(priority - Math.Max(diff * 2, 0), min: 1, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
// Enemy is not stunned, increase the priority of stunner weapons and decrease the priority of lethal weapons.
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
if (diff < 0)
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (weapon is MeleeWeapon && weapon.Item.HasTag(Tags.StunnerItem) && (Enemy.Params.Health.StunImmunity || !CanMeleeStunnerStun(weapon)))
|
||||
{
|
||||
// Cannot do stun damage -> use the melee damage to determine the priority.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
priority = attack?.GetTotalDamage() ?? priority / 2;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
private float ApproximateStunDamage(ItemComponent weapon, Attack attack)
|
||||
{
|
||||
// Try to reduce the priority using the actual damage values and status effects.
|
||||
// This is an approximation, because we can't check the status effect conditions here.
|
||||
// The result might be incorrect if there is a high stun effect that's only applied in certain conditions.
|
||||
var statusEffects = attack.StatusEffects.Where(se => !se.HasConditions && se.type == ActionType.OnUse && se.HasRequiredItems(character));
|
||||
if (weapon.statusEffectLists != null && weapon.statusEffectLists.TryGetValue(ActionType.OnUse, out List<StatusEffect> hitEffects))
|
||||
{
|
||||
statusEffects = statusEffects.Concat(hitEffects);
|
||||
}
|
||||
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 == AfflictionPrefab.StunType);
|
||||
if (stunAffliction != null)
|
||||
{
|
||||
stunAmount = stunAffliction.Strength;
|
||||
}
|
||||
return stunAmount;
|
||||
});
|
||||
return attack.Stun + afflictionsStun + effectsStun;
|
||||
}
|
||||
|
||||
private bool CanMeleeStunnerStun(ItemComponent weapon)
|
||||
{
|
||||
// If there's an item container that takes a battery,
|
||||
// assume that it's required for the stun effect
|
||||
// as we can't check the status effect conditions here.
|
||||
var mobileBatteryTag = Tags.MobileBattery;
|
||||
var containers = weapon.Item.Components.Where(ic =>
|
||||
ic is ItemContainer container &&
|
||||
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
|
||||
// If there's no such container, assume that the melee weapon can stun without a battery.
|
||||
return containers.None() || containers.Any(container =>
|
||||
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
|
||||
}
|
||||
|
||||
private Item GetWeapon(IEnumerable<ItemComponent> weaponList, out ItemComponent weaponComponent)
|
||||
{
|
||||
weaponComponent = null;
|
||||
float bestPriority = 0;
|
||||
float lethalDmg = -1;
|
||||
bool isAllowedToSeekWeapons = !IsEnemyCloserThan(300);
|
||||
bool isCloseToEnemy = IsEnemyCloserThan(300);
|
||||
bool prioritizeMelee = IsEnemyCloserThan(50) || EnemyAIController.IsLatchedTo(Enemy, character);
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (weapon is RepairTool repairTool)
|
||||
{
|
||||
switch (repairTool.UsableIn)
|
||||
{
|
||||
case RepairTool.UseEnvironment.Air:
|
||||
if (character.InWater) { continue; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.Water:
|
||||
if (!character.InWater) { continue; }
|
||||
break;
|
||||
case RepairTool.UseEnvironment.None:
|
||||
continue;
|
||||
case RepairTool.UseEnvironment.Both:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prioritizeMelee)
|
||||
{
|
||||
if (weapon is MeleeWeapon)
|
||||
{
|
||||
priority *= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
if (weapon.IsEmpty(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
||||
{
|
||||
// Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo).
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Halve the priority for weapons that don't have proper ammunition loaded.
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float max = lethalDmg + 1;
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
{
|
||||
priority = max;
|
||||
}
|
||||
else
|
||||
{
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
priority = Math.Clamp(priority - Math.Max(diff * 2, 0), min: 1, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
// Enemy is not stunned, increase the priority of stunner weapons and decrease the priority of lethal weapons.
|
||||
if (weapon.Item.HasTag("stunner"))
|
||||
{
|
||||
priority *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
if (diff < 0)
|
||||
{
|
||||
priority /= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (weapon is MeleeWeapon && weapon.Item.HasTag("stunner") && !CanMeleeStunnerStun(weapon))
|
||||
{
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
priority = attack?.GetTotalDamage() ?? priority / 2;
|
||||
}
|
||||
float priority = GetWeaponPriority(weapon, prioritizeMelee, isCloseToEnemy, out lethalDmg);
|
||||
if (priority > bestPriority)
|
||||
{
|
||||
weaponComponent = weapon;
|
||||
@@ -636,7 +666,7 @@ namespace Barotrauma
|
||||
if (bestPriority < 1) { return null; }
|
||||
if (Mode == CombatMode.Arrest)
|
||||
{
|
||||
if (weaponComponent.Item.HasTag("stunner"))
|
||||
if (weaponComponent.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
isLethalWeapon = false;
|
||||
}
|
||||
@@ -654,44 +684,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
return weaponComponent.Item;
|
||||
|
||||
float ApproximateStunDamage(ItemComponent weapon, Attack attack)
|
||||
{
|
||||
// Try to reduce the priority using the actual damage values and status effects.
|
||||
// This is an approximation, because we can't check the status effect conditions here.
|
||||
// The result might be incorrect if there is a high stun effect that's only applied in certain conditions.
|
||||
var statusEffects = attack.StatusEffects.Where(se => !se.HasConditions && se.type == ActionType.OnUse && se.HasRequiredItems(character));
|
||||
if (weapon.statusEffectLists != null && weapon.statusEffectLists.TryGetValue(ActionType.OnUse, out List<StatusEffect> hitEffects))
|
||||
{
|
||||
statusEffects = statusEffects.Concat(hitEffects);
|
||||
}
|
||||
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 == AfflictionPrefab.StunType);
|
||||
if (stunAffliction != null)
|
||||
{
|
||||
stunAmount = stunAffliction.Strength;
|
||||
}
|
||||
return stunAmount;
|
||||
});
|
||||
return attack.Stun + afflictionsStun + effectsStun;
|
||||
}
|
||||
|
||||
bool CanMeleeStunnerStun(ItemComponent weapon)
|
||||
{
|
||||
// If there's an item container that takes a battery,
|
||||
// assume that it's required for the stun effect
|
||||
// as we can't check the status effect conditions here.
|
||||
var mobileBatteryTag = "mobilebattery".ToIdentifier();
|
||||
var containers = weapon.Item.Components.Where(ic =>
|
||||
ic is ItemContainer container &&
|
||||
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
|
||||
// If there's no such container, assume that the melee weapon can stun without a battery.
|
||||
return containers.None() || containers.Any(container =>
|
||||
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetLethalDamage(ItemComponent weapon)
|
||||
@@ -771,13 +763,13 @@ namespace Barotrauma
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!character.HasEquippedItem(Weapon, predicate: IsHandSlotType))
|
||||
if (!character.HasEquippedItem(Weapon, predicate: CharacterInventory.IsHandSlotType))
|
||||
{
|
||||
//clear aim and shoot inputs so the bot doesn't immediately fire the weapon if it was previously e.g. using a scooter
|
||||
character.ClearInput(InputType.Aim);
|
||||
character.ClearInput(InputType.Shoot);
|
||||
Weapon.TryInteract(character, forceSelectKey: true);
|
||||
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
|
||||
var slots = Weapon.AllowedSlots.Where(s => CharacterInventory.IsHandSlotType(s));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
SetAimTimer(Rand.Range(0.2f, 0.4f) / AimSpeed);
|
||||
@@ -791,8 +783,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
static bool IsHandSlotType(InvSlotType s) => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand);
|
||||
}
|
||||
|
||||
private float findHullTimer;
|
||||
@@ -926,7 +916,7 @@ namespace Barotrauma
|
||||
if (followTargetObjective == null) { return; }
|
||||
if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown)
|
||||
{
|
||||
if (HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out _))
|
||||
if (HumanAIController.HasItem(character, Tags.HandLockerItem, out _))
|
||||
{
|
||||
if (!arrestingRegistered)
|
||||
{
|
||||
@@ -986,7 +976,7 @@ namespace Barotrauma
|
||||
foreach (var item in Enemy.Inventory.AllItemsMod)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && item.StolenDuringRound ||
|
||||
item.HasTag("weapon") ||
|
||||
item.HasTag(Tags.Weapon) ||
|
||||
item.GetComponent<MeleeWeapon>() != null ||
|
||||
item.GetComponent<RangedWeapon>() != null)
|
||||
{
|
||||
@@ -997,9 +987,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//prefer using handcuffs already on the enemy's inventory
|
||||
if (!HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems))
|
||||
if (!HumanAIController.HasItem(Enemy, Tags.HandLockerItem, out IEnumerable<Item> matchingItems))
|
||||
{
|
||||
HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out matchingItems);
|
||||
HumanAIController.HasItem(character, Tags.HandLockerItem, out matchingItems);
|
||||
}
|
||||
|
||||
if (matchingItems.Any() &&
|
||||
@@ -1079,7 +1069,7 @@ namespace Barotrauma
|
||||
if (ammunitionIdentifiers != null)
|
||||
{
|
||||
// Try reload ammunition from inventory
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag(Tags.MobileRadio);
|
||||
Item ammunition = character.Inventory.FindItem(i => i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||
if (ammunition != null)
|
||||
{
|
||||
@@ -1205,7 +1195,7 @@ namespace Barotrauma
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
// 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);
|
||||
var pickedBodies = Submarine.PickBodies(Weapon.SimPosition, Submarine.GetRelativeSimPosition(from: Weapon, to: Enemy), myBodies, Physics.CollisionCharacter);
|
||||
foreach (var body in pickedBodies)
|
||||
{
|
||||
Character target = null;
|
||||
@@ -1248,7 +1238,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
Weapon.Use(deltaTime, user: character);
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
@@ -1265,7 +1255,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -1275,7 +1265,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+6
-2
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
class AIObjectiveContainItem: AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "contain item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
@@ -109,7 +110,7 @@ namespace Barotrauma
|
||||
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
||||
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character) && container.ShouldBeContained(item, out _);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -226,7 +227,10 @@ namespace Barotrauma
|
||||
AllowToFindDivingGear = AllowToFindDivingGear,
|
||||
AllowDangerousPressure = AllowDangerousPressure,
|
||||
TargetCondition = ConditionLevel,
|
||||
ItemFilter = (Item potentialItem) => RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem),
|
||||
ItemFilter = (Item potentialItem) =>
|
||||
{
|
||||
return (RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem)) && container.ShouldBeContained(potentialItem, out _);
|
||||
},
|
||||
ItemCount = ItemCount,
|
||||
TakeWholeStack = MoveWholeStack
|
||||
}, onAbandon: () =>
|
||||
|
||||
+6
-5
@@ -9,11 +9,12 @@ namespace Barotrauma
|
||||
class AIObjectiveDecontainItem : AIObjective
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "decontain item".ToIdentifier();
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
private readonly string[] itemIdentifiers;
|
||||
private readonly Identifier[] itemIdentifiers;
|
||||
private readonly ItemContainer sourceContainer;
|
||||
private readonly ItemContainer targetContainer;
|
||||
private readonly Item targetItem;
|
||||
@@ -52,16 +53,16 @@ namespace Barotrauma
|
||||
this.targetContainer = targetContainer;
|
||||
}
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, objectiveManager, sourceContainer, targetContainer, priorityModifier) { }
|
||||
public AIObjectiveDecontainItem(Character character, Identifier itemIdentifier, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: this(character, new Identifier[] { itemIdentifier }, objectiveManager, sourceContainer, targetContainer, priorityModifier) { }
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
public AIObjectiveDecontainItem(Character character, Identifier[] itemIdentifiers, AIObjectiveManager objectiveManager, ItemContainer sourceContainer, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
itemIdentifiers[i] = itemIdentifiers[i];
|
||||
}
|
||||
this.sourceContainer = sourceContainer;
|
||||
this.targetContainer = targetContainer;
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ namespace Barotrauma
|
||||
escapeProgress += Rand.Range(2, 5);
|
||||
if (escapeProgress > 15)
|
||||
{
|
||||
Item handcuffs = character.Inventory.FindItemByTag("handlocker".ToIdentifier());
|
||||
Item handcuffs = character.Inventory.FindItemByTag(Tags.HandLockerItem);
|
||||
if (handcuffs != null)
|
||||
{
|
||||
handcuffs.Drop(character);
|
||||
|
||||
+6
-5
@@ -15,6 +15,8 @@ namespace Barotrauma
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private readonly Hull targetHull;
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
@@ -30,8 +32,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
bool isOrder = objectiveManager.HasOrder<AIObjectiveExtinguishFires>();
|
||||
@@ -176,19 +177,19 @@ namespace Barotrauma
|
||||
getExtinguisherObjective = null;
|
||||
gotoObjective = null;
|
||||
sinTime = 0;
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-28
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private readonly Identifier gearTag;
|
||||
|
||||
@@ -22,34 +23,20 @@ namespace Barotrauma
|
||||
|
||||
public const float MIN_OXYGEN = 10;
|
||||
|
||||
public static readonly Identifier HEAVY_DIVING_GEAR = "deepdiving".ToIdentifier();
|
||||
public static readonly Identifier LIGHT_DIVING_GEAR = "lightdiving".ToIdentifier();
|
||||
/// <summary>
|
||||
/// Diving gear that's suitable for wearing indoors (-> the bots don't try to unequip it when they don't need diving gear)
|
||||
/// </summary>
|
||||
public static readonly Identifier DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors".ToIdentifier();
|
||||
public static readonly Identifier OXYGEN_SOURCE = "oxygensource".ToIdentifier();
|
||||
|
||||
protected override bool CheckObjectiveSpecific() =>
|
||||
targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head);
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTag = needsDivingSuit ? HEAVY_DIVING_GEAR : LIGHT_DIVING_GEAR;
|
||||
gearTag = needsDivingSuit ? Tags.HeavyDivingGear : Tags.LightDivingGear;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(gearTag, true));
|
||||
if (targetItem == null && gearTag == LIGHT_DIVING_GEAR)
|
||||
if (targetItem == null && gearTag == Tags.LightDivingGear)
|
||||
{
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true));
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(Tags.HeavyDivingGear, true));
|
||||
}
|
||||
if (targetItem == null ||
|
||||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) &&
|
||||
@@ -74,7 +61,7 @@ namespace Barotrauma
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref getDivingGear);
|
||||
if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
if (gearTag == Tags.HeavyDivingGear && HumanAIController.HasItem(character, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
foreach (Item mask in masks)
|
||||
{
|
||||
@@ -95,10 +82,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min))
|
||||
if (HumanAIController.HasItem(character, Tags.OxygenSource, out _, conditionPercentage: min))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogswappingoxygentank").Value, null, 0, "swappingoxygentank".ToIdentifier(), 30.0f);
|
||||
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min, recursive: true).Count == 1)
|
||||
if (character.Inventory.FindAllItems(i => i.HasTag(Tags.OxygenSource) && i.Condition > min, recursive: true).Count == 1)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoglastoxygentank").Value, null, 0.0f, "dialoglastoxygentank".ToIdentifier(), 30.0f);
|
||||
}
|
||||
@@ -109,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
var container = targetItem.GetComponent<ItemContainer>();
|
||||
var objective = new AIObjectiveContainItem(character, OXYGEN_SOURCE, container, objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
var objective = new AIObjectiveContainItem(character, Tags.OxygenSource, container, objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
@@ -119,7 +106,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (container.HasSubContainers)
|
||||
{
|
||||
objective.TargetSlot = container.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
|
||||
objective.TargetSlot = container.FindSuitableSubContainerIndex(Tags.OxygenSource);
|
||||
}
|
||||
// Only remove the oxygen source being replaced
|
||||
objective.RemoveExistingPredicate = i => objective.IsInTargetSlot(i);
|
||||
@@ -132,7 +119,7 @@ namespace Barotrauma
|
||||
// Try to seek any oxygen sources, even if they have minimal amount of oxygen.
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
{
|
||||
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
return new AIObjectiveContainItem(character, Tags.OxygenSource, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
@@ -142,7 +129,7 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f))
|
||||
if (remainingTanks > 0 && !HumanAIController.HasItem(character, Tags.OxygenSource, out _, conditionPercentage: 0.01f))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogcantfindtoxygen").Value, null, 0, "cantfindoxygen".ToIdentifier(), 30.0f);
|
||||
}
|
||||
@@ -158,7 +145,7 @@ namespace Barotrauma
|
||||
int ReportOxygenTankCount()
|
||||
{
|
||||
if (character.Submarine != Submarine.MainSub) { return 1; }
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1);
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.OxygenSource) && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfOxygenTanks").Value, null, 0.0f, "outofoxygentanks".ToIdentifier(), 30.0f);
|
||||
@@ -177,7 +164,7 @@ namespace Barotrauma
|
||||
{
|
||||
return
|
||||
item != null &&
|
||||
item.HasTag(OXYGEN_SOURCE) &&
|
||||
item.HasTag(Tags.OxygenSource) &&
|
||||
item.Condition > 0 &&
|
||||
(oxygenSourceSlotIndex == null || item.ParentInventory.IsInSlot(item, oxygenSourceSlotIndex.Value));
|
||||
}
|
||||
@@ -188,7 +175,7 @@ namespace Barotrauma
|
||||
targetItem = item;
|
||||
if (targetItem != null)
|
||||
{
|
||||
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
|
||||
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(Tags.OxygenSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -212,7 +199,7 @@ namespace Barotrauma
|
||||
// When we are venturing outside of our sub, let's just suppose that we have enough oxygen with us and optimize it so that we don't keep switching off half used tanks.
|
||||
float min = 0.01f;
|
||||
float minOxygen = character.IsInFriendlySub ? MIN_OXYGEN : min;
|
||||
if (minOxygen > min && character.Inventory.AllItems.Any(i => i.HasTag("oxygensource") && i.ConditionPercentage >= minOxygen))
|
||||
if (minOxygen > min && character.Inventory.AllItems.Any(i => i.HasTag(Tags.OxygenSource) && i.ConditionPercentage >= minOxygen))
|
||||
{
|
||||
// There's a valid oxygen tank in the inventory -> no need to swap the tank too early.
|
||||
minOxygen = min;
|
||||
|
||||
+15
-12
@@ -40,24 +40,21 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
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))
|
||||
objectiveManager.Objectives.Any(o => (o is AIObjectiveCombat || o is AIObjectiveReturn) && o.Priority > 0))
|
||||
&& ((!character.IsLowInOxygen && character.IsImmuneToPressure)|| HumanAIController.HasDivingSuit(character)) ? 0 : AIObjectiveManager.EmergencyObjectivePriority - 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
|
||||
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)))
|
||||
bool isSuffocatingInDivingSuit = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
||||
static bool IsSuffocatingWithoutDivingGear(Character c) => c.IsLowInOxygen && c.AnimController.HeadInWater && !HumanAIController.HasDivingGear(c, requireOxygenTank: true);
|
||||
if (isSuffocatingInDivingSuit ||
|
||||
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)) ||
|
||||
(!objectiveManager.HasActiveObjective<AIObjectiveFindDivingGear>() && IsSuffocatingWithoutDivingGear(character)))
|
||||
{
|
||||
Priority = AIObjectiveManager.MaxObjectivePriority;
|
||||
}
|
||||
@@ -215,7 +212,7 @@ namespace Barotrauma
|
||||
AllowGoingOutside =
|
||||
character.IsProtectedFromPressure ||
|
||||
character.CurrentHull == null ||
|
||||
character.CurrentHull.IsTaggedAirlock() ||
|
||||
character.CurrentHull.IsAirlock ||
|
||||
character.CurrentHull.LeadsOutside(character)
|
||||
},
|
||||
onCompleted: () =>
|
||||
@@ -258,6 +255,13 @@ namespace Barotrauma
|
||||
}
|
||||
if (subObjectives.Any(so => so.CanBeCompleted)) { return; }
|
||||
UpdateSimpleEscape(deltaTime);
|
||||
if (cannotFindSafeHull && !character.IsInFriendlySub && objectiveManager.Objectives.None(o => o is AIObjectiveReturn))
|
||||
{
|
||||
if (OrderPrefab.Prefabs.TryGet("return".ToIdentifier(), out OrderPrefab orderPrefab))
|
||||
{
|
||||
objectiveManager.AddObjective(new AIObjectiveReturn(character, character, objectiveManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,8 +437,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: could also target gaps that get us inside?
|
||||
if (potentialHull.IsTaggedAirlock())
|
||||
if (potentialHull.IsAirlock)
|
||||
{
|
||||
hullSafety = 100;
|
||||
hullIsAirlock = true;
|
||||
|
||||
+17
-16
@@ -12,7 +12,9 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fix leak".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Gap Leak { get; private set; }
|
||||
|
||||
@@ -35,8 +37,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
float coopMultiplier = 1;
|
||||
@@ -94,6 +95,7 @@ namespace Barotrauma
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var weldingTool = character.Inventory.FindItemByTag("weldingequipment".ToIdentifier(), true);
|
||||
var repairTool = weldingTool?.GetComponent<RepairTool>();
|
||||
if (weldingTool == null)
|
||||
{
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment".ToIdentifier(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC),
|
||||
@@ -110,17 +112,25 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (weldingTool.OwnInventory == null)
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"{weldingTool}\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f))
|
||||
if (weldingTool.OwnInventory == null && repairTool.requiredItems.Any(r => r.Key == RelatedItem.RelationType.Contained))
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel".ToIdentifier(), weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"{weldingTool}\" has no proper inventory");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (weldingTool.OwnInventory != null && weldingTool.OwnInventory.AllItems.None(i => i.HasTag(Tags.WeldingFuel) && i.Condition > 0.0f))
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, Tags.WeldingFuel, weldingTool.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
RemoveExisting = true
|
||||
},
|
||||
@@ -138,7 +148,7 @@ namespace Barotrauma
|
||||
void ReportWeldingFuelTankCount()
|
||||
{
|
||||
if (character.Submarine != Submarine.MainSub) { return; }
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("weldingfuel") && i.Condition > 1);
|
||||
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.WeldingFuel) && i.Condition > 1);
|
||||
if (remainingOxygenTanks == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfWeldingFuel").Value, null, 0.0f, "outofweldingfuel".ToIdentifier(), 30.0f);
|
||||
@@ -152,15 +162,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (subObjectives.Any()) { return; }
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 toLeak = Leak.WorldPosition - character.AnimController.AimSourceWorldPos;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(toLeak.X) < 100 && toLeak.Y < 0.0f && toLeak.Y > -150)
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "fix leaks".ToIdentifier();
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
|
||||
private Hull PrioritizedHull { get; set; }
|
||||
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Hull prioritizedHull = null) : base(character, objectiveManager, priorityModifier)
|
||||
|
||||
+40
-10
@@ -15,6 +15,7 @@ namespace Barotrauma
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -158,11 +159,6 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (IdentifiersOrTags != null && !isDoneSeeking)
|
||||
{
|
||||
if (checkInventory)
|
||||
@@ -271,15 +267,49 @@ namespace Barotrauma
|
||||
|
||||
Inventory itemInventory = targetItem.ParentInventory;
|
||||
var slots = itemInventory?.FindIndices(targetItem);
|
||||
var droppedStack = TargetItem.DroppedStack.ToList();
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
|
||||
{
|
||||
if (TakeWholeStack && slots != null)
|
||||
if (TakeWholeStack)
|
||||
{
|
||||
foreach (int slot in slots)
|
||||
//taking the whole stack in this context means "as many items that can fit in one of the bot's slots",
|
||||
//and the stack means either a stack of items in an inventory slot or a "dropped stack"
|
||||
//so we need a bit of extra logic here
|
||||
int maxStackSize = 0;
|
||||
int takenItemCount = 1;
|
||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||
{
|
||||
foreach (Item item in itemInventory.GetItemsAt(slot).ToList())
|
||||
maxStackSize = Math.Max(maxStackSize, character.Inventory.HowManyCanBePut(targetItem.Prefab, i, condition: null));
|
||||
}
|
||||
if (slots != null)
|
||||
{
|
||||
foreach (int slot in slots)
|
||||
{
|
||||
HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true);
|
||||
foreach (Item item in itemInventory.GetItemsAt(slot).ToList())
|
||||
{
|
||||
if (HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true))
|
||||
{
|
||||
takenItemCount++;
|
||||
if (takenItemCount >= maxStackSize) { break; }
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var item in droppedStack)
|
||||
{
|
||||
if (item == TargetItem) { continue; }
|
||||
if (HumanAIController.TakeItem(item, character.Inventory, equip: false, storeUnequipped: true))
|
||||
{
|
||||
takenItemCount++;
|
||||
if (takenItemCount >= maxStackSize) { break; }
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,7 +441,7 @@ namespace Barotrauma
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (item.Container != null)
|
||||
{
|
||||
if (item.Container.HasTag("donttakeitems")) { continue; }
|
||||
if (item.Container.HasTag(Tags.DontTakeItems)) { continue; }
|
||||
if (ignoredItems.Contains(item.Container)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null)
|
||||
{
|
||||
|
||||
+41
-47
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public bool AllowStealing { get; set; }
|
||||
public bool TakeWholeStack { get; set; }
|
||||
@@ -40,55 +41,48 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
if (subObjectivesCreated) { return; }
|
||||
foreach (Identifier tag in gearTags)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
foreach (Identifier tag in gearTags)
|
||||
{
|
||||
if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; }
|
||||
int count = gearTags.Count(t => t == tag);
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
TakeWholeStack = TakeWholeStack,
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireNonEmpty = RequireNonEmpty,
|
||||
ItemCount = count,
|
||||
SpeakIfFails = RequireAllItems
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
achievedItems.Add(item);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item != null)
|
||||
{
|
||||
achievedItems.Remove(item);
|
||||
}
|
||||
RemoveSubObjective(ref getItem);
|
||||
if (RequireAllItems)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
if (subObjectives.Any(so => so is AIObjectiveGetItem getItem && getItem.IdentifiersOrTags.Contains(tag))) { continue; }
|
||||
int count = gearTags.Count(t => t == tag);
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory && count <= 1)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
TakeWholeStack = TakeWholeStack,
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireNonEmpty = RequireNonEmpty,
|
||||
ItemCount = count,
|
||||
SpeakIfFails = RequireAllItems
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
achievedItems.Add(item);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item != null)
|
||||
{
|
||||
achievedItems.Remove(item);
|
||||
}
|
||||
RemoveSubObjective(ref getItem);
|
||||
if (RequireAllItems)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+1
-1
@@ -810,7 +810,7 @@ namespace Barotrauma
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
SteeringManager?.Reset();
|
||||
if (Target != null)
|
||||
{
|
||||
character.AnimController.TargetDir = Target.WorldPosition.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
+3
-1
@@ -382,7 +382,9 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != currentHull || !item.HasTag("chair")) { continue; }
|
||||
if (item.CurrentHull != currentHull || !item.HasTag(Tags.ChairItem)) { continue; }
|
||||
//not possible in vanilla game, but a mod might have holdable/attachable chairs
|
||||
if (item.ParentInventory != null || item.body is { Enabled: true }) { continue; }
|
||||
var controller = item.GetComponent<Controller>();
|
||||
if (controller == null || controller.User != null) { continue; }
|
||||
item.TryInteract(character, forceSelectKey: true);
|
||||
|
||||
+4
-3
@@ -17,6 +17,8 @@ namespace Barotrauma
|
||||
set => throw new Exception("Trying to set the value for AIObjectiveLoadItem.IsLoop from: " + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private AIObjectiveLoadItems.ItemCondition TargetItemCondition { get; }
|
||||
private Item Container { get; }
|
||||
private ItemContainer ItemContainer { get; }
|
||||
@@ -161,8 +163,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
else if (!AIObjectiveLoadItems.IsValidTarget(Container, character, targetCondition: TargetItemCondition))
|
||||
@@ -299,7 +300,7 @@ namespace Barotrauma
|
||||
if (rootInventoryOwner is Character owner && owner != character) { return false; }
|
||||
if (rootInventoryOwner is Item parentItem)
|
||||
{
|
||||
if (parentItem.HasTag("donttakeitems")) { return false; }
|
||||
if (parentItem.HasTag(Tags.DontTakeItems)) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (!character.HasItem(item) && !CanEquip(item, allowWearing: false)) { return false; }
|
||||
|
||||
+27
-32
@@ -44,6 +44,8 @@ namespace Barotrauma
|
||||
public override bool CanBeCompleted => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
protected virtual bool ResetWhenClearingIgnoreList => true;
|
||||
protected virtual bool ForceOrderPriority => true;
|
||||
@@ -117,51 +119,44 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
if (character.LockHands)
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
if (InverseTargetEvaluation)
|
||||
{
|
||||
targetValue = 100 - targetValue;
|
||||
}
|
||||
var currentSubObjective = CurrentSubObjective;
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
||||
{
|
||||
// If the priority is higher than the target value, let's just use it.
|
||||
// The priority calculation is more precise, but it takes into account things like distances,
|
||||
// so it's better not to use it if it's lower than the rougher targetValue.
|
||||
targetValue = currentSubObjective.Priority;
|
||||
}
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
if (InverseTargetEvaluation)
|
||||
if (objectiveManager.IsOrder(this))
|
||||
{
|
||||
targetValue = 100 - targetValue;
|
||||
}
|
||||
var currentSubObjective = CurrentSubObjective;
|
||||
if (currentSubObjective != null && currentSubObjective.Priority > targetValue)
|
||||
{
|
||||
// If the priority is higher than the target value, let's just use it.
|
||||
// The priority calculation is more precise, but it takes into account things like distances,
|
||||
// so it's better not to use it if it's lower than the rougher targetValue.
|
||||
targetValue = currentSubObjective.Priority;
|
||||
}
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1)
|
||||
{
|
||||
Priority = 0;
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (objectiveManager.IsOrder(this))
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
||||
{
|
||||
Priority = ForceOrderPriority ? objectiveManager.GetOrderPriority(this) : targetValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
||||
{
|
||||
// Allow higher prio
|
||||
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||
}
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
// Allow higher prio
|
||||
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||
}
|
||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||
Priority = MathHelper.Lerp(0, max, value);
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
|
||||
+3
-2
@@ -163,7 +163,8 @@ namespace Barotrauma
|
||||
}
|
||||
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) &&
|
||||
Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC && !character.IsFriendlyNPCTurnedHostile)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
@@ -539,7 +540,7 @@ namespace Barotrauma
|
||||
case "cleanupitems":
|
||||
if (order.TargetEntity is Item targetItem)
|
||||
{
|
||||
if (targetItem.HasTag("allowcleanup") && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
|
||||
if (targetItem.HasTag(Tags.AllowCleanup) && targetItem.ParentInventory == null && targetItem.OwnInventory != null)
|
||||
{
|
||||
// Target all items inside the container
|
||||
newObjective = new AIObjectiveCleanupItems(character, this, targetItem.OwnInventory.AllItems, priorityModifier);
|
||||
|
||||
+3
-3
@@ -14,6 +14,7 @@ namespace Barotrauma
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
public override bool PrioritizeIfSubObjectivesActive => component != null && (component is Reactor || component is Turret);
|
||||
|
||||
private readonly ItemComponent component, controller;
|
||||
@@ -47,10 +48,9 @@ namespace Barotrauma
|
||||
protected override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.IsOrder(this);
|
||||
if (!IsAllowed || character.LockHands)
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
if (!isOrder && component.Item.ConditionPercentage <= 0)
|
||||
|
||||
+2
-7
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool KeepDivingGearOnAlsoWhenInactive => true;
|
||||
public override bool PrioritizeIfSubObjectivesActive => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private AIObjectiveGetItem getSingleItemObjective;
|
||||
private AIObjectiveGetItems getAllItemsObjective;
|
||||
@@ -60,8 +61,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
HandleNonAllowed();
|
||||
return Priority;
|
||||
}
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
@@ -75,11 +75,6 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
if (FindAllItems && targetItem == null)
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override Identifier Identifier { get; set; } = "pump water".ToIdentifier();
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
private List<Pump> pumpList;
|
||||
|
||||
@@ -54,7 +55,7 @@ namespace Barotrauma
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || pump.Item.Submarine == null || pump.Item.CurrentHull == null) { continue; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
if (pump.Item.HasTag("ballast")) { continue; }
|
||||
if (pump.Item.HasTag(Tags.Ballast)) { continue; }
|
||||
pumpList.Add(pump);
|
||||
}
|
||||
}
|
||||
|
||||
+48
-33
@@ -10,8 +10,9 @@ namespace Barotrauma
|
||||
{
|
||||
public override Identifier Identifier { get; set; } = "repair item".ToIdentifier();
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
public override bool KeepDivingGearOn => Item?.CurrentHull == null;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
@@ -36,10 +37,13 @@ namespace Barotrauma
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed || Item.IgnoreByAI(character))
|
||||
if (!IsAllowed) { HandleNonAllowed(); }
|
||||
if (Item.IgnoreByAI(character))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
if (Abandon)
|
||||
{
|
||||
if (IsRepairing())
|
||||
{
|
||||
Item.Repairables.ForEach(r => r.StopRepairing(character));
|
||||
@@ -136,32 +140,35 @@ namespace Barotrauma
|
||||
}
|
||||
if (repairTool != null)
|
||||
{
|
||||
if (repairTool.Item.OwnInventory == null)
|
||||
if (repairTool.requiredItems.TryGetValue(RelatedItem.RelationType.Contained, out var requiredItems))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"" + repairTool + "\" has no proper inventory");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
RelatedItem item = null;
|
||||
Item fuel = null;
|
||||
foreach (RelatedItem requiredItem in repairTool.requiredItems[RelatedItem.RelationType.Contained])
|
||||
{
|
||||
item = requiredItem;
|
||||
fuel = repairTool.Item.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||
if (fuel != null) { break; }
|
||||
}
|
||||
if (fuel == null)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (repairTool.Item.OwnInventory == null)
|
||||
{
|
||||
RemoveExisting = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref refuelObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: AIObjectiveRepairItem failed - the item \"{repairTool}\" has no proper inventory.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
RelatedItem item = null;
|
||||
Item fuel = null;
|
||||
foreach (RelatedItem requiredItem in requiredItems)
|
||||
{
|
||||
item = requiredItem;
|
||||
fuel = repairTool.Item.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||
if (fuel != null) { break; }
|
||||
}
|
||||
if (fuel == null)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, item.Identifiers, repairTool.Item.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
RemoveExisting = true
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref refuelObjective),
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
@@ -170,10 +177,8 @@ namespace Barotrauma
|
||||
if (waitTimer < WaitTimeBeforeRepair) { return; }
|
||||
|
||||
HumanAIController.FaceTarget(Item);
|
||||
if (repairTool != null)
|
||||
{
|
||||
OperateRepairTool(deltaTime);
|
||||
}
|
||||
|
||||
bool repairThroughRepairInterface = false;
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
{
|
||||
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
|
||||
@@ -185,10 +190,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.SelectedItem != Item)
|
||||
{
|
||||
if (Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true) ||
|
||||
Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true))
|
||||
if (Item.TryInteract(character, forceUseKey: true) ||
|
||||
Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
character.SelectedItem = Item;
|
||||
repairThroughRepairInterface = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -209,8 +215,17 @@ namespace Barotrauma
|
||||
{
|
||||
repairable.StartRepairing(character, Repairable.FixActions.Repair);
|
||||
}
|
||||
else
|
||||
{
|
||||
repairThroughRepairInterface = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!repairThroughRepairInterface && repairTool != null && !Abandon)
|
||||
{
|
||||
OperateRepairTool(deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
public Item PrioritizedItem { get; private set; }
|
||||
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowInFriendlySubs => true;
|
||||
|
||||
public readonly static float RequiredSuccessFactor = 0.4f;
|
||||
|
||||
|
||||
+66
-65
@@ -16,12 +16,13 @@ namespace Barotrauma
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AllowWhileHandcuffed => false;
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
const float CloseEnoughToTreat = 100.0f;
|
||||
|
||||
private readonly Character targetCharacter;
|
||||
public readonly Character Target;
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveContainItem replaceOxygenObjective;
|
||||
@@ -44,7 +45,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
this.targetCharacter = targetCharacter;
|
||||
Target = targetCharacter;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -61,55 +62,55 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands || targetCharacter == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
if (Target == null || Target.Removed || Target.IsDead)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
var otherRescuer = targetCharacter.SelectedBy;
|
||||
var otherRescuer = Target.SelectedBy;
|
||||
if (otherRescuer != null && otherRescuer != character)
|
||||
{
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical");
|
||||
return;
|
||||
}
|
||||
if (targetCharacter != character)
|
||||
if (Target != character)
|
||||
{
|
||||
if (targetCharacter.IsIncapacitated)
|
||||
if (Target.IsIncapacitated)
|
||||
{
|
||||
// Check if the character needs more oxygen
|
||||
if (!ignoreOxygen && character.SelectedCharacter == targetCharacter || character.CanInteractWith(targetCharacter))
|
||||
if (!ignoreOxygen && character.SelectedCharacter == Target || character.CanInteractWith(Target))
|
||||
{
|
||||
// Replace empty oxygen and welding fuel.
|
||||
if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out IEnumerable<Item> suits, requireEquipped: true))
|
||||
if (HumanAIController.HasItem(Target, Tags.HeavyDivingGear, out IEnumerable<Item> suits, requireEquipped: true))
|
||||
{
|
||||
Item suit = suits.FirstOrDefault();
|
||||
if (suit != null)
|
||||
{
|
||||
AIController.UnequipEmptyItems(character, suit);
|
||||
AIController.UnequipContainedItems(character, suit, it => it.HasTag("weldingfuel"));
|
||||
AIController.UnequipContainedItems(character, suit, it => it.HasTag(Tags.WeldingFuel));
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasItem(targetCharacter, AIObjectiveFindDivingGear.LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
else if (HumanAIController.HasItem(Target, Tags.LightDivingGear, out IEnumerable<Item> masks, requireEquipped: true))
|
||||
{
|
||||
Item mask = masks.FirstOrDefault();
|
||||
if (mask != null)
|
||||
{
|
||||
AIController.UnequipEmptyItems(character, mask);
|
||||
AIController.UnequipContainedItems(character, mask, it => it.HasTag("weldingfuel"));
|
||||
AIController.UnequipContainedItems(character, mask, it => it.HasTag(Tags.WeldingFuel));
|
||||
}
|
||||
}
|
||||
bool ShouldRemoveDivingSuit() => targetCharacter.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && targetCharacter.CurrentHull?.LethalPressure <= 0;
|
||||
bool ShouldRemoveDivingSuit() => Target.OxygenAvailable < CharacterHealth.InsufficientOxygenThreshold && Target.CurrentHull?.LethalPressure <= 0;
|
||||
if (ShouldRemoveDivingSuit())
|
||||
{
|
||||
suits.ForEach(suit => suit.Drop(character));
|
||||
}
|
||||
else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) && it.ConditionPercentage > 0)))
|
||||
else if (suits.Any() && suits.None(s => s.OwnInventory?.AllItems != null && s.OwnInventory.AllItems.Any(it => it.HasTag(Tags.OxygenSource) && it.ConditionPercentage > 0)))
|
||||
{
|
||||
// The target has a suit equipped with an empty oxygen tank.
|
||||
// Can't remove the suit, because the target needs it.
|
||||
// If we happen to have an extra oxygen tank in the inventory, let's swap it.
|
||||
Item spareOxygenTank = FindOxygenTank(targetCharacter) ?? FindOxygenTank(character);
|
||||
Item spareOxygenTank = FindOxygenTank(Target) ?? FindOxygenTank(character);
|
||||
if (spareOxygenTank != null)
|
||||
{
|
||||
Item suit = suits.FirstOrDefault();
|
||||
@@ -133,36 +134,36 @@ namespace Barotrauma
|
||||
|
||||
Item FindOxygenTank(Character c) =>
|
||||
c.Inventory.FindItem(i =>
|
||||
i.HasTag(AIObjectiveFindDivingGear.OXYGEN_SOURCE) &&
|
||||
i.HasTag(Tags.OxygenSource) &&
|
||||
i.ConditionPercentage > 1 &&
|
||||
i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag("diving")) == null,
|
||||
i.FindParentInventory(inv => inv.Owner is Item otherItem && otherItem.HasTag(Tags.DivingGear)) == null,
|
||||
recursive: true);
|
||||
}
|
||||
}
|
||||
if (character.Submarine != null && targetCharacter.CurrentHull != null)
|
||||
if (character.Submarine != null && Target.CurrentHull != null)
|
||||
{
|
||||
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
if (HumanAIController.GetHullSafety(Target.CurrentHull, Target) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
if (character.SelectedCharacter != Target)
|
||||
{
|
||||
if (HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
|
||||
if (HumanAIController.VisibleHulls.Contains(Target.CurrentHull) && Target.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
("[roomname]", targetCharacter.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundunconscioustarget{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[roomname]", Target.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundunconscioustarget{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
// Go to the target and select it
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
if (!character.CanInteractWith(Target))
|
||||
{
|
||||
RemoveSubObjective(ref replaceOxygenObjective);
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(Target, character, objectiveManager)
|
||||
{
|
||||
CloseEnough = CloseEnoughToTreat,
|
||||
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
|
||||
TargetName = targetCharacter.DisplayName
|
||||
TargetName = Target.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () =>
|
||||
@@ -173,7 +174,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(targetCharacter);
|
||||
character.SelectCharacter(Target);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -213,16 +214,16 @@ namespace Barotrauma
|
||||
|
||||
if (subObjectives.Any()) { return; }
|
||||
|
||||
if (targetCharacter != character && !character.CanInteractWith(targetCharacter))
|
||||
if (Target != character && !character.CanInteractWith(Target))
|
||||
{
|
||||
RemoveSubObjective(ref replaceOxygenObjective);
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
// Go to the target and select it
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(Target, character, objectiveManager)
|
||||
{
|
||||
CloseEnough = CloseEnoughToTreat,
|
||||
DialogueIdentifier = "dialogcannotreachpatient".ToIdentifier(),
|
||||
TargetName = targetCharacter.DisplayName
|
||||
TargetName = Target.DisplayName
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective),
|
||||
onAbandon: () =>
|
||||
@@ -234,14 +235,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// We can start applying treatment
|
||||
if (character != targetCharacter && character.SelectedCharacter != targetCharacter)
|
||||
if (character != Target && character.SelectedCharacter != Target)
|
||||
{
|
||||
if (targetCharacter.CurrentHull?.DisplayName != null)
|
||||
if (Target.CurrentHull?.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
("[roomname]", targetCharacter.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundwoundedtarget{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[roomname]", Target.CurrentHull.DisplayName, FormatCapitals.Yes)).Value,
|
||||
null, 1.0f, $"foundwoundedtarget{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
}
|
||||
GiveTreatment(deltaTime);
|
||||
@@ -253,7 +254,7 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Identifier, float> currentTreatmentSuitabilities = new Dictionary<Identifier, float>();
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (targetCharacter == null)
|
||||
if (Target == null)
|
||||
{
|
||||
string errorMsg = $"{character.Name}: Attempted to update a Rescue objective with no target!";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
@@ -263,10 +264,10 @@ namespace Barotrauma
|
||||
|
||||
SteeringManager.Reset();
|
||||
|
||||
if (!targetCharacter.IsPlayer)
|
||||
if (!Target.IsPlayer)
|
||||
{
|
||||
// If the target is a bot, don't let it move
|
||||
targetCharacter.AIController?.SteeringManager?.Reset();
|
||||
Target.AIController?.SteeringManager?.Reset();
|
||||
}
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
@@ -275,13 +276,13 @@ namespace Barotrauma
|
||||
}
|
||||
treatmentTimer = TreatmentDelay;
|
||||
|
||||
float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f;
|
||||
float cprSuitability = Target.Oxygen < 0.0f ? -Target.Oxygen * 100.0f : 0.0f;
|
||||
|
||||
//find which treatments are the most suitable to treat the character's current condition
|
||||
targetCharacter.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, user: character, normalize: false, predictFutureDuration: 10.0f);
|
||||
Target.CharacterHealth.GetSuitableTreatments(currentTreatmentSuitabilities, user: character, normalize: false, predictFutureDuration: 10.0f);
|
||||
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
|
||||
foreach (Affliction affliction in GetSortedAfflictions(Target))
|
||||
{
|
||||
if (affliction == null) { throw new Exception("Affliction was null"); }
|
||||
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
|
||||
@@ -294,9 +295,9 @@ namespace Barotrauma
|
||||
{
|
||||
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
//allow taking items from the target's inventory too if the target is unconscious
|
||||
if (matchingItem == null && targetCharacter.IsIncapacitated)
|
||||
if (matchingItem == null && Target.IsIncapacitated)
|
||||
{
|
||||
matchingItem ??= targetCharacter.Inventory?.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
matchingItem ??= Target.Inventory?.FindItemByIdentifier(treatmentSuitability.Key, true);
|
||||
}
|
||||
if (matchingItem != null)
|
||||
{
|
||||
@@ -307,7 +308,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (bestItem != null)
|
||||
{
|
||||
if (targetCharacter != character) { character.SelectCharacter(targetCharacter); }
|
||||
if (Target != character) { character.SelectCharacter(Target); }
|
||||
ApplyTreatment(affliction, bestItem);
|
||||
//wait a bit longer after applying a treatment to wait for potential side-effects to manifest
|
||||
treatmentTimer = TreatmentDelay * 4;
|
||||
@@ -370,12 +371,12 @@ namespace Barotrauma
|
||||
("[treatment1]", itemListStr),
|
||||
("[treatment2]", itemNameList.Last()));
|
||||
}
|
||||
if (targetCharacter != character && character.IsOnPlayerTeam)
|
||||
if (Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
("[targetname]", Target.Name, FormatCapitals.No),
|
||||
("[treatmentlist]", itemListStr, FormatCapitals.Yes)).Value,
|
||||
null, 2.0f, $"listrequiredtreatments{targetCharacter.Name}".ToIdentifier(), 60.0f);
|
||||
null, 2.0f, $"listrequiredtreatments{Target.Name}".ToIdentifier(), 60.0f);
|
||||
}
|
||||
RemoveSubObjective(ref getItemObjective);
|
||||
TryAddSubObjective(ref getItemObjective,
|
||||
@@ -397,18 +398,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!targetCharacter.IsUnconscious)
|
||||
else if (!Target.IsUnconscious)
|
||||
{
|
||||
Abandon = true;
|
||||
//no suitable treatments found, not inside our own sub (= can't search for more treatments), the target isn't unconscious (= can't give CPR)
|
||||
SpeakCannotTreat();
|
||||
return;
|
||||
}
|
||||
if (character != targetCharacter)
|
||||
if (character != Target)
|
||||
{
|
||||
if (cprSuitability > 0.0f)
|
||||
{
|
||||
character.SelectCharacter(targetCharacter);
|
||||
character.SelectCharacter(Target);
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
performedCpr = true;
|
||||
}
|
||||
@@ -421,40 +422,40 @@ namespace Barotrauma
|
||||
|
||||
private void SpeakCannotTreat()
|
||||
{
|
||||
LocalizedString msg = character == targetCharacter ?
|
||||
LocalizedString msg = character == Target ?
|
||||
TextManager.Get("dialogcannottreatself") :
|
||||
TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", targetCharacter.DisplayName, FormatCapitals.No);
|
||||
TextManager.GetWithVariable("dialogcannottreatpatient", "[name]", Target.DisplayName, FormatCapitals.No);
|
||||
character.Speak(msg.Value, identifier: "cannottreatpatient".ToIdentifier(), minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
item.ApplyTreatment(character, Target, Target.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(Target) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, Target);
|
||||
if (isCompleted && Target != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
string textTag = performedCpr ? "DialogTargetResuscitated" : "DialogTargetHealed";
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", targetCharacter.Name)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{targetCharacter.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
string message = TextManager.GetWithVariable(textTag, "[targetname]", Target.Name)?.Value;
|
||||
character.Speak(message, delay: 1.0f, identifier: $"targethealed{Target.Name}".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed || targetCharacter == null)
|
||||
if (Target == null) { Abandon = true; }
|
||||
if (!IsAllowed) { HandleNonAllowed(); }
|
||||
if (Abandon)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == Target.CurrentHull && !HumanAIController.IsFriendly(character, c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
// Don't go into rooms that have enemies
|
||||
Priority = 0;
|
||||
@@ -462,18 +463,18 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
}
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y);
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - Target.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - Target.WorldPosition.Y);
|
||||
if (character.Submarine?.Info is { IsRuin: false })
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, horizontalDistance + verticalDistance));
|
||||
if (character.CurrentHull != null && targetCharacter.CurrentHull == character.CurrentHull)
|
||||
if (character.CurrentHull != null && Target.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) / 100;
|
||||
float vitalityFactor = 1 - AIObjectiveRescueAll.GetVitalityFactor(Target) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, AIObjectiveManager.EmergencyObjectivePriority, MathHelper.Clamp(devotion + (vitalityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
return Priority;
|
||||
|
||||
+54
-44
@@ -34,24 +34,29 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Filter(Character target)
|
||||
{
|
||||
if (!IsValidTarget(target, character, requireTreatableAfflictions: false)) { return false; }
|
||||
if (GetTreatableAfflictions(target).Any())
|
||||
if (!IsValidTarget(target, character, out bool ignoredasMinorWounds))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//the target might be at a low enough health to be considered a valid target,
|
||||
//but if all afflictions are below treatment thresholds, the bot won't (and shouldn't) treat them
|
||||
// -> make the bot speak to make it clear the bot intentionally ignores very minor injuries
|
||||
if (!charactersWithMinorInjuries.Contains(character))
|
||||
if (ignoredasMinorWounds)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
|
||||
null, 1.0f, $"notreatableafflictions{target.Name}".ToIdentifier(), 10.0f);
|
||||
charactersWithMinorInjuries.Add(character);
|
||||
//the target might be at a low enough health to be considered a valid target,
|
||||
//but if all afflictions are below treatment thresholds, the bot won't (and shouldn't) treat them
|
||||
// -> make the bot speak to make it clear the bot intentionally ignores very minor injuries
|
||||
if (character.IsOnPlayerTeam && target != character && !charactersWithMinorInjuries.Contains(target))
|
||||
{
|
||||
// But only speak about targets when we are not already actively treating, in which case we should be speaking about the current target.
|
||||
if (objectiveManager.GetFirstActiveObjective<AIObjectiveRescue>() == null)
|
||||
{
|
||||
charactersWithMinorInjuries.Add(target);
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
|
||||
delay: 1.0f,
|
||||
identifier: $"notreatableafflictions{target.Name}".ToIdentifier(),
|
||||
minDurationBetweenSimilar: 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
@@ -103,7 +108,7 @@ namespace Barotrauma
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold = false)
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold)
|
||||
{
|
||||
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
@@ -128,39 +133,50 @@ namespace Barotrauma
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveRescueAll, Character>(character, target);
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character, bool requireTreatableAfflictions = true)
|
||||
public static bool IsValidTarget(Character target, Character character, out bool ignoredAsMinorWounds)
|
||||
{
|
||||
ignoredAsMinorWounds = false;
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target.IsInstigator) { return false; }
|
||||
if (target.IsPet) { return false; }
|
||||
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
|
||||
bool isBelowTreatmentThreshold;
|
||||
float vitalityFactor;
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously, unless we are a medic
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (requireTreatableAfflictions && GetTreatableAfflictions(target).None())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!IsValidTargetForAI(target, humanAI)) { return false; }
|
||||
vitalityFactor = GetVitalityFactor(target);
|
||||
isBelowTreatmentThreshold = vitalityFactor < GetVitalityThreshold(humanAI.ObjectiveManager, character, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetVitalityFactor(target) >= vitalityThreshold) { return false; }
|
||||
vitalityFactor = GetVitalityFactor(target);
|
||||
isBelowTreatmentThreshold = vitalityFactor < vitalityThreshold;
|
||||
}
|
||||
bool hasTreatableAfflictions = GetTreatableAfflictions(target, ignoreTreatmentThreshold: false).Any();
|
||||
bool isValidTarget = isBelowTreatmentThreshold && hasTreatableAfflictions;
|
||||
if (!isValidTarget)
|
||||
{
|
||||
ignoredAsMinorWounds = hasTreatableAfflictions || vitalityFactor < 100;
|
||||
}
|
||||
return isValidTarget;
|
||||
}
|
||||
|
||||
private static bool IsValidTargetForAI(Character target, HumanAIController humanAI)
|
||||
{
|
||||
Character character = humanAI.Character;
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously, unless we are a medic
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
@@ -189,11 +205,5 @@ namespace Barotrauma
|
||||
}
|
||||
return character.GetDamageDoneByAttacker(target) <= 0;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
charactersWithMinorInjuries.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
if (ReturnTarget == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error with a Return objective: no suitable return target found");
|
||||
DebugConsole.AddSafeError("Error with a Return objective: no suitable return target found");
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Consider if this needs to be addressed
|
||||
Priority = 0;
|
||||
Priority = AIObjectiveManager.LowestOrderPriority - 1;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -91,7 +90,7 @@ namespace Barotrauma
|
||||
targetHull = d.Item.CurrentHull;
|
||||
break;
|
||||
}
|
||||
if (targetHull != null && !targetHull.IsTaggedAirlock())
|
||||
if (targetHull != null && !targetHull.IsAirlock)
|
||||
{
|
||||
// Target the closest airlock
|
||||
float closestDist = 0;
|
||||
@@ -99,7 +98,7 @@ namespace Barotrauma
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine != targetHull.Submarine) { continue; }
|
||||
if (!hull.IsTaggedAirlock()) { continue; }
|
||||
if (!hull.IsAirlock) { continue; }
|
||||
float dist = Vector2.DistanceSquared(targetHull.Position, hull.Position);
|
||||
if (airlock == null || closestDist <= 0 || dist < closestDist)
|
||||
{
|
||||
@@ -146,7 +145,7 @@ namespace Barotrauma
|
||||
bool targetIsAirlock = false;
|
||||
foreach (var hull in ReturnTarget.GetHulls(false))
|
||||
{
|
||||
bool hullIsAirlock = hull.IsTaggedAirlock();
|
||||
bool hullIsAirlock = hull.IsAirlock;
|
||||
if(hullIsAirlock || (!targetIsAirlock && hull.LeadsOutside(character)))
|
||||
{
|
||||
float distanceSquared = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
|
||||
@@ -85,6 +85,12 @@ namespace Barotrauma
|
||||
public readonly bool TargetAllCharacters;
|
||||
public bool IsReport => TargetAllCharacters && !MustSetTarget;
|
||||
|
||||
public bool IsVisibleAsReportButton =>
|
||||
IsReport && !Hidden && SymbolSprite != null &&
|
||||
(!TraitorModeOnly || GameMain.GameSession is { TraitorsEnabled: true });
|
||||
|
||||
public bool TraitorModeOnly;
|
||||
|
||||
public bool IsDismissal => Identifier == DismissalIdentifier;
|
||||
|
||||
public readonly float FadeOutTime;
|
||||
@@ -172,6 +178,7 @@ namespace Barotrauma
|
||||
ControllerTags = orderElement.GetAttributeIdentifierArray("controllertags", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
|
||||
AppropriateJobs = orderElement.GetAttributeIdentifierArray("appropriatejobs", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
TraitorModeOnly = orderElement.GetAttributeBool("TraitorModeOnly", false);
|
||||
PreferredJobs = orderElement.GetAttributeIdentifierArray("preferredjobs", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
Options = orderElement.GetAttributeIdentifierArray("options", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
HiddenOptions = orderElement.GetAttributeIdentifierArray("hiddenoptions", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
|
||||
@@ -34,7 +34,23 @@ namespace Barotrauma
|
||||
set { happiness = MathHelper.Clamp(value, 0.0f, MaxHappiness); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// At which point is the pet considered "unhappy" (playing unhappy sounds and showing the icon)
|
||||
/// </summary>
|
||||
public float UnhappyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// At which point is the pet considered "happy" (playing happy sounds and showing the icon)
|
||||
/// </summary>
|
||||
public float HappyThreshold { get; set; }
|
||||
|
||||
public float MaxHappiness { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// At which point is the pet considered "hungry" (playing unhappy sounds and showing the icon)
|
||||
/// </summary>
|
||||
public float HungryThreshold { get; set; }
|
||||
public float MaxHunger { get; set; }
|
||||
|
||||
public float HappinessDecreaseRate { get; set; }
|
||||
@@ -43,7 +59,7 @@ namespace Barotrauma
|
||||
public float PlayForce { get; set; }
|
||||
|
||||
public float PlayTimer { get; set; }
|
||||
private float? unstunY { get; set; }
|
||||
private float? UnstunY { get; set; }
|
||||
|
||||
public EnemyAIController AIController { get; private set; } = null;
|
||||
|
||||
@@ -136,7 +152,7 @@ namespace Barotrauma
|
||||
if (aggregate >= r && Items[i].Prefab != null)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "null") + ":PetProducedItem:" + pet.AIController.Character.SpeciesName + ":" + Items[i].Prefab.Identifier);
|
||||
Entity.Spawner.AddItemToSpawnQueue(Items[i].Prefab, pet.AIController.Character.WorldPosition);
|
||||
Entity.Spawner?.AddItemToSpawnQueue(Items[i].Prefab, pet.AIController.Character.WorldPosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -164,14 +180,18 @@ namespace Barotrauma
|
||||
AIController = aiController;
|
||||
AIController.Character.CanBeDragged = true;
|
||||
|
||||
MaxHappiness = element.GetAttributeFloat("maxhappiness", 100.0f);
|
||||
MaxHunger = element.GetAttributeFloat("maxhunger", 100.0f);
|
||||
MaxHappiness = element.GetAttributeFloat(nameof(MaxHappiness), 100.0f);
|
||||
UnhappyThreshold = element.GetAttributeFloat(nameof(UnhappyThreshold), MaxHappiness * 0.25f);
|
||||
HappyThreshold = element.GetAttributeFloat(nameof(HappyThreshold), MaxHappiness * 0.8f);
|
||||
|
||||
MaxHunger = element.GetAttributeFloat(nameof(MaxHunger), 100.0f);
|
||||
HungryThreshold = element.GetAttributeFloat(nameof(HungryThreshold), MaxHunger * 0.5f);
|
||||
|
||||
Happiness = MaxHappiness * 0.5f;
|
||||
Hunger = MaxHunger * 0.5f;
|
||||
|
||||
HappinessDecreaseRate = element.GetAttributeFloat("happinessdecreaserate", 0.1f);
|
||||
HungerIncreaseRate = element.GetAttributeFloat("hungerincreaserate", 0.25f);
|
||||
HappinessDecreaseRate = element.GetAttributeFloat(nameof(HappinessDecreaseRate), 0.1f);
|
||||
HungerIncreaseRate = element.GetAttributeFloat(nameof(HungerIncreaseRate), 0.25f);
|
||||
|
||||
PlayForce = element.GetAttributeFloat("playforce", 15.0f);
|
||||
|
||||
@@ -208,9 +228,9 @@ namespace Barotrauma
|
||||
|
||||
public StatusIndicatorType GetCurrentStatusIndicatorType()
|
||||
{
|
||||
if (Hunger > MaxHunger * 0.5f) { return StatusIndicatorType.Hungry; }
|
||||
if (Happiness > MaxHappiness * 0.8f) { return StatusIndicatorType.Happy; }
|
||||
if (Happiness < MaxHappiness * 0.25f) { return StatusIndicatorType.Sad; }
|
||||
if (Hunger > HungryThreshold) { return StatusIndicatorType.Hungry; }
|
||||
if (Happiness > HappyThreshold) { return StatusIndicatorType.Happy; }
|
||||
if (Happiness < UnhappyThreshold) { return StatusIndicatorType.Sad; }
|
||||
return StatusIndicatorType.None;
|
||||
}
|
||||
|
||||
@@ -264,12 +284,12 @@ namespace Barotrauma
|
||||
public void Play(Character player)
|
||||
{
|
||||
if (PlayTimer > 0.0f) { return; }
|
||||
if (Owner == null) { Owner = player; }
|
||||
Owner ??= player;
|
||||
PlayTimer = 5.0f;
|
||||
AIController.Character.IsRagdolled = true;
|
||||
Happiness += 10.0f;
|
||||
AIController.Character.AnimController.MainLimb.body.LinearVelocity += new Vector2(0, PlayForce);
|
||||
unstunY = AIController.Character.SimPosition.Y;
|
||||
UnstunY = AIController.Character.SimPosition.Y;
|
||||
#if CLIENT
|
||||
AIController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.9f);
|
||||
#endif
|
||||
@@ -297,16 +317,16 @@ namespace Barotrauma
|
||||
var character = AIController.Character;
|
||||
if (character?.Removed ?? true || character.IsDead) { return; }
|
||||
|
||||
if (unstunY.HasValue)
|
||||
if (UnstunY.HasValue)
|
||||
{
|
||||
if (PlayTimer > 4.0f)
|
||||
{
|
||||
float extent = character.AnimController.MainLimb.body.GetMaxExtent();
|
||||
if (character.SimPosition.Y < (unstunY.Value + extent * 3.0f) &&
|
||||
if (character.SimPosition.Y < (UnstunY.Value + extent * 3.0f) &&
|
||||
character.AnimController.MainLimb.body.LinearVelocity.Y < 0.0f)
|
||||
{
|
||||
character.IsRagdolled = false;
|
||||
unstunY = null;
|
||||
UnstunY = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -316,7 +336,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
character.IsRagdolled = false;
|
||||
unstunY = null;
|
||||
UnstunY = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,15 +382,11 @@ namespace Barotrauma
|
||||
{
|
||||
character.CharacterHealth.ApplyAffliction(character.AnimController.MainLimb, new Affliction(AfflictionPrefab.InternalDamage, 8.0f * deltaTime));
|
||||
}
|
||||
else if (Hunger < MaxHunger * 0.1f)
|
||||
{
|
||||
character.CharacterHealth.ReduceAllAfflictionsOnAllLimbs(8.0f * deltaTime);
|
||||
}
|
||||
|
||||
if (character.SelectedBy != null)
|
||||
{
|
||||
character.IsRagdolled = true;
|
||||
unstunY = character.SimPosition.Y;
|
||||
UnstunY = character.SimPosition.Y;
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemsToProduce.Count; i++)
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (TargetItemComponent is Turret turret)
|
||||
{
|
||||
if (!turret.CheckTurretAngle(entity.WorldPosition))
|
||||
if (!turret.IsWithinAimingRadius(entity.WorldPosition))
|
||||
{
|
||||
importance *= 0.1f;
|
||||
}
|
||||
|
||||
@@ -350,20 +350,20 @@ namespace Barotrauma
|
||||
|
||||
ShipIssueWorkers.Clear();
|
||||
|
||||
if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag(Tags.Reactor) && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
var order = new Order(OrderPrefab.Prefabs["operatereactor"], "powerup".ToIdentifier(), reactor.Item, reactor);
|
||||
ShipIssueWorkers.Add(new ShipIssueWorkerPowerUpReactor(this, order));
|
||||
}
|
||||
|
||||
if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && nav.GetComponent<Steering>() is Steering steeringComponent)
|
||||
if (CommandedSubmarine.GetItems(false).Find(i => i.HasTag(Tags.NavTerminal) && !i.NonInteractable) is Item nav && nav.GetComponent<Steering>() is Steering steeringComponent)
|
||||
{
|
||||
steering = steeringComponent;
|
||||
var order = new Order(OrderPrefab.Prefabs["steer"], "navigatetactical".ToIdentifier(), nav, steeringComponent);
|
||||
ShipIssueWorkers.Add(new ShipIssueWorkerSteer(this, order));
|
||||
}
|
||||
|
||||
foreach (Item item in CommandedSubmarine.GetItems(true).FindAll(i => i.HasTag("turret") && !i.HasTag("hardpoint")))
|
||||
foreach (Item item in CommandedSubmarine.GetItems(true).FindAll(i => i.HasTag(Tags.Turret) && !i.HasTag(Tags.Hardpoint)))
|
||||
{
|
||||
var order = new Order(OrderPrefab.Prefabs["operateweapons"], item, item.GetComponent<Turret>());
|
||||
ShipIssueWorkers.Add(new ShipIssueWorkerOperateWeapons(this, order));
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
turret.UpdateAutoOperate(deltaTime, friendlyTag);
|
||||
turret.UpdateAutoOperate(deltaTime, ignorePower: true, friendlyTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace Barotrauma
|
||||
|
||||
private static IEnumerable<T> GetThalamusEntities<T>(Submarine wreck, Identifier tag) where T : MapEntity => GetThalamusEntities(wreck, tag).Where(e => e is T).Select(e => e as T);
|
||||
|
||||
private static IEnumerable<MapEntity> GetThalamusEntities(Submarine wreck, Identifier tag) => MapEntity.mapEntityList.Where(e => e.Submarine == wreck && e.Prefab != null && IsThalamus(e.Prefab, tag));
|
||||
private static IEnumerable<MapEntity> GetThalamusEntities(Submarine wreck, Identifier tag) => MapEntity.MapEntityList.Where(e => e.Submarine == wreck && e.Prefab != null && IsThalamus(e.Prefab, tag));
|
||||
|
||||
private static bool IsThalamus(MapEntityPrefab entityPrefab, Identifier tag) => entityPrefab.HasSubCategory("thalamus") || entityPrefab.Tags.Contains(tag);
|
||||
|
||||
@@ -273,6 +273,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
|
||||
if (!IsClient)
|
||||
{
|
||||
if (!initialCellsSpawned) { SpawnInitialCells(); }
|
||||
}
|
||||
bool isSomeoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 2.0f;
|
||||
#if SERVER
|
||||
@@ -322,7 +326,6 @@ namespace Barotrauma
|
||||
OperateTurrets(deltaTime, Config.Entity);
|
||||
if (!IsClient)
|
||||
{
|
||||
if (!initialCellsSpawned) { SpawnInitialCells(); }
|
||||
UpdateReinforcements(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
public string Brain { get; private set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public string Spawner { get; private set; }
|
||||
public Identifier Spawner { get; private set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public string BrainRoomBackground { get; private set; }
|
||||
|
||||
@@ -286,17 +286,32 @@ namespace Barotrauma
|
||||
|
||||
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
useItemTimer = 0.5f;
|
||||
useItemTimer = 0.05f;
|
||||
StartUsingItem();
|
||||
|
||||
if (!allowMovement)
|
||||
{
|
||||
TargetMovement = Vector2.Zero;
|
||||
TargetDir = handWorldPos.X > character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
|
||||
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
|
||||
if (InWater)
|
||||
{
|
||||
TargetMovement = Vector2.Normalize(handWorldPos - character.WorldPosition) * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
|
||||
float sqrDist = Vector2.DistanceSquared(character.WorldPosition, handWorldPos);
|
||||
if (sqrDist > MathUtils.Pow(ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength), 2))
|
||||
{
|
||||
TargetMovement = GetTargetMovement(Vector2.Normalize(handWorldPos - character.WorldPosition));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float distX = Math.Abs(handWorldPos.X - character.WorldPosition.X);
|
||||
if (distX > ConvertUnits.ToDisplayUnits(upperArmLength + forearmLength))
|
||||
{
|
||||
TargetMovement = GetTargetMovement(Vector2.UnitX * Math.Sign(handWorldPos.X - character.WorldPosition.X));
|
||||
}
|
||||
}
|
||||
Vector2 GetTargetMovement(Vector2 dir)
|
||||
{
|
||||
return dir * GetCurrentSpeed(false) * Math.Max(character.SpeedMultiplier, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +323,15 @@ namespace Barotrauma
|
||||
handSimPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
|
||||
Vector2 refPos = rightShoulder?.WorldAnchorA ?? leftShoulder?.WorldAnchorA ?? MainLimb.SimPosition;
|
||||
Vector2 diff = handSimPos - refPos;
|
||||
float dist = diff.Length();
|
||||
float maxDist = ArmLength * 0.9f;
|
||||
if (dist > maxDist)
|
||||
{
|
||||
handSimPos = refPos + diff / dist * maxDist;
|
||||
}
|
||||
|
||||
var leftHand = GetLimb(LimbType.LeftHand);
|
||||
if (leftHand != null)
|
||||
{
|
||||
@@ -323,6 +347,16 @@ namespace Barotrauma
|
||||
rightHand.PullJointEnabled = true;
|
||||
rightHand.PullJointWorldAnchorB = handSimPos;
|
||||
}
|
||||
|
||||
//make the character crouch if using an item some distance below them (= on the floor)
|
||||
if (!inWater &&
|
||||
character.WorldPosition.Y - handWorldPos.Y > ConvertUnits.ToDisplayUnits(CurrentGroundedParams.TorsoPosition) / 4 &&
|
||||
this is HumanoidAnimController humanoidAnimController)
|
||||
{
|
||||
humanoidAnimController.Crouching = true;
|
||||
humanoidAnimController.ForceSelectAnimationType = AnimationType.Crouch;
|
||||
character.SetInput(InputType.Crouch, hit: false, held: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Grab(Vector2 rightHandPos, Vector2 leftHandPos)
|
||||
@@ -352,13 +386,8 @@ namespace Barotrauma
|
||||
|
||||
//calculate the handle positions
|
||||
Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
float horizontalOffset = ConvertUnits.ToSimUnits((item.Sprite.size.X / 2 - item.Sprite.Origin.X) * item.Scale);
|
||||
|
||||
//handlePos[0] = ConvertUnits.ToSimUnits(new Vector2(-45,25) * 0.5f);
|
||||
//handlePos[1] = ConvertUnits.ToSimUnits(new Vector2(-65,30) * 0.5f);
|
||||
|
||||
transformedHandlePos[0] = Vector2.Transform(new Vector2(handlePos[0].X + horizontalOffset, handlePos[0].Y), itemTransfrom);
|
||||
transformedHandlePos[1] = Vector2.Transform(new Vector2(handlePos[1].X + horizontalOffset, handlePos[1].Y), itemTransfrom);
|
||||
transformedHandlePos[0] = Vector2.Transform(handlePos[0], itemTransfrom);
|
||||
transformedHandlePos[1] = Vector2.Transform(handlePos[1], itemTransfrom);
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso) ?? MainLimb;
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
@@ -385,7 +414,9 @@ namespace Barotrauma
|
||||
if (aim && !isClimbing && !usingController && character.Stun <= 0.0f && itemPos != Vector2.Zero && !character.IsIncapacitated)
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
|
||||
Vector2 diff = holdable.Aimable ? (mousePos - AimSourceSimPos) * Dir : Vector2.UnitX;
|
||||
Vector2 diff = holdable.Aimable ?
|
||||
(mousePos - AimSourceSimPos) * Dir :
|
||||
MathUtils.RotatePoint(Vector2.UnitX, torsoRotation);
|
||||
holdAngle = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torsoRotation * Dir;
|
||||
holdAngle += GetAimWobble(rightHand, leftHand, item);
|
||||
itemAngle = torsoRotation + holdAngle * Dir;
|
||||
@@ -480,6 +511,16 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
float targetAngle = MathUtils.WrapAngleTwoPi(itemAngle + itemAngleRelativeToHoldAngle * Dir);
|
||||
float currentRotation = MathUtils.WrapAngleTwoPi(item.body.Rotation);
|
||||
float itemRotation = MathHelper.SmoothStep(currentRotation, targetAngle, deltaTime * 25);
|
||||
if (previousDirection != dir || Math.Abs(targetAngle - currentRotation) > MathHelper.Pi)
|
||||
{
|
||||
itemRotation = targetAngle;
|
||||
}
|
||||
item.SetTransform(currItemPos, itemRotation, setPrevTransform: false);
|
||||
previousDirection = dir;
|
||||
|
||||
if (holdable.Pusher != null)
|
||||
{
|
||||
if (character.Stun > 0.0f || character.IsIncapacitated)
|
||||
@@ -497,24 +538,11 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
holdable.Pusher.TargetPosition = currItemPos;
|
||||
holdable.Pusher.TargetRotation = holdAngle * Dir;
|
||||
|
||||
holdable.Pusher.TargetRotation = itemRotation;
|
||||
holdable.Pusher.MoveToTargetPosition(true);
|
||||
|
||||
currItemPos = holdable.Pusher.SimPosition;
|
||||
itemAngle = holdable.Pusher.Rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
float targetAngle = MathUtils.WrapAngleTwoPi(itemAngle + itemAngleRelativeToHoldAngle * Dir);
|
||||
float currentRotation = MathUtils.WrapAngleTwoPi(item.body.Rotation);
|
||||
float itemRotation = MathHelper.SmoothStep(currentRotation, targetAngle, deltaTime * 25);
|
||||
if (previousDirection != dir || Math.Abs(targetAngle - currentRotation) > MathHelper.Pi)
|
||||
{
|
||||
itemRotation = targetAngle;
|
||||
}
|
||||
item.SetTransform(currItemPos, itemRotation, setPrevTransform: false);
|
||||
previousDirection = dir;
|
||||
|
||||
if (!isClimbing && !character.IsIncapacitated && itemPos != Vector2.Zero && (aim || !holdable.UseHandRotationForHoldAngle))
|
||||
{
|
||||
|
||||
+20
-9
@@ -144,7 +144,7 @@ namespace Barotrauma
|
||||
set { HumanSwimFastParams = value as HumanSwimFastParams; }
|
||||
}
|
||||
|
||||
public bool Crouching;
|
||||
public bool Crouching { get; set; }
|
||||
|
||||
private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Barotrauma
|
||||
public HumanoidAnimController(Character character, string seed, HumanRagdollParams ragdollParams = null) : base(character, seed, ragdollParams)
|
||||
{
|
||||
// TODO: load from the character info file?
|
||||
movementLerp = RagdollParams.MainElement.GetAttributeFloat("movementlerp", 0.4f);
|
||||
movementLerp = RagdollParams?.MainElement?.GetAttributeFloat("movementlerp", 0.4f) ?? 0f;
|
||||
}
|
||||
|
||||
public override void Recreate(RagdollParams ragdollParams = null)
|
||||
@@ -243,19 +243,14 @@ namespace Barotrauma
|
||||
if (MainLimb == null) { return; }
|
||||
|
||||
levitatingCollider = !IsHanging;
|
||||
ColliderIndex = Crouching && !swimming ? 1 : 0;
|
||||
if ((character.SelectedItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
|
||||
(character.SelectedSecondaryItem?.GetComponent<Controller>()?.ControlCharacterPose ?? false) ||
|
||||
character.SelectedSecondaryItem?.GetComponent<Ladder>() != null ||
|
||||
(ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined))
|
||||
{
|
||||
Crouching = false;
|
||||
ColliderIndex = 0;
|
||||
}
|
||||
else if (!Crouching && ColliderIndex == 1)
|
||||
{
|
||||
Crouching = true;
|
||||
}
|
||||
ColliderIndex = Crouching && !swimming ? 1 : 0;
|
||||
|
||||
//stun (= disable the animations) if the ragdoll receives a large enough impact
|
||||
if (strongestImpact > 0.0f)
|
||||
@@ -417,6 +412,22 @@ namespace Barotrauma
|
||||
swimming = inWater;
|
||||
swimmingStateLockTimer = 0.5f;
|
||||
}
|
||||
if (character.SelectedItem?.Prefab is { GrabWhenSelected: true } &&
|
||||
character.SelectedItem.ParentInventory == null &&
|
||||
character.SelectedItem.body is not { Enabled: true } &&
|
||||
character.SelectedItem.GetComponent<Repairable>()?.CurrentFixer != character)
|
||||
{
|
||||
bool moving = character.IsKeyDown(InputType.Left) || character.IsKeyDown(InputType.Right);
|
||||
moving |= (character.InWater || character.IsClimbing) && (character.IsKeyDown(InputType.Up) || character.IsKeyDown(InputType.Down));
|
||||
if (!moving)
|
||||
{
|
||||
Vector2 handPos = character.SelectedItem.WorldPosition - Vector2.UnitY * ConvertUnits.ToDisplayUnits(ArmLength / 2);
|
||||
handPos.Y = Math.Max(handPos.Y, character.SelectedItem.WorldRect.Y - character.SelectedItem.WorldRect.Height);
|
||||
UpdateUseItem(
|
||||
allowMovement: false,
|
||||
handPos);
|
||||
}
|
||||
}
|
||||
if (swimming)
|
||||
{
|
||||
UpdateSwimming();
|
||||
@@ -616,7 +627,7 @@ namespace Barotrauma
|
||||
if (TorsoAngle.HasValue && !torso.Disabled)
|
||||
{
|
||||
float torsoAngle = TorsoAngle.Value;
|
||||
float herpesStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.SpaceHerpesType);
|
||||
float herpesStrength = character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.SpaceHerpesType);
|
||||
if (Crouching && !movingHorizontally && !Aiming) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
|
||||
torsoAngle -= herpesStrength / 150.0f;
|
||||
torso.body.SmoothRotate(torsoAngle * Dir, currentGroundedParams.TorsoTorque);
|
||||
|
||||
@@ -745,6 +745,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.DisableImpactDamageTimer > 0.0f) { return; }
|
||||
|
||||
if (f2.Body?.UserData is Item)
|
||||
{
|
||||
//no impact damage from items
|
||||
//items that can impact characters (melee weapons, projectiles) should handle the damage themselves
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 normal = localNormal;
|
||||
float impact = Vector2.Dot(velocity, -normal);
|
||||
if (f1.Body == Collider.FarseerBody || !Collider.Enabled)
|
||||
@@ -754,12 +761,14 @@ namespace Barotrauma
|
||||
|
||||
if (isNotRemote)
|
||||
{
|
||||
if (impact > ImpactTolerance)
|
||||
float impactTolerance = ImpactTolerance;
|
||||
if (character.Stun > 0.0f) { impactTolerance *= 0.5f; }
|
||||
if (impact > impactTolerance)
|
||||
{
|
||||
impactPos = ConvertUnits.ToDisplayUnits(impactPos);
|
||||
if (character.Submarine != null) impactPos += character.Submarine.Position;
|
||||
if (character.Submarine != null) { impactPos += character.Submarine.Position; }
|
||||
|
||||
float impactDamage = Math.Min((impact - ImpactTolerance) * ImpactDamageMultiplayer, character.MaxVitality * MaxImpactDamage);
|
||||
float impactDamage = GetImpactDamage(impact, impactTolerance);
|
||||
|
||||
var should = GameMain.LuaCs.Hook.Call<float?>("changeFallDamage", impactDamage, character, impactPos, velocity);
|
||||
|
||||
@@ -770,7 +779,7 @@ namespace Barotrauma
|
||||
|
||||
character.LastDamageSource = null;
|
||||
character.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), 0.0f, true);
|
||||
strongestImpact = Math.Max(strongestImpact, impact - ImpactTolerance);
|
||||
strongestImpact = Math.Max(strongestImpact, impact - impactTolerance);
|
||||
character.ApplyStatusEffects(ActionType.OnImpact, 1.0f);
|
||||
//briefly disable impact damage
|
||||
//otherwise the character will take damage multiple times when for example falling,
|
||||
@@ -784,6 +793,12 @@ namespace Barotrauma
|
||||
ImpactProjSpecific(impact, f1.Body);
|
||||
}
|
||||
|
||||
public float GetImpactDamage(float impact, float? impactTolerance = null)
|
||||
{
|
||||
float tolerance = impactTolerance ?? ImpactTolerance;
|
||||
return Math.Min((impact - tolerance) * ImpactDamageMultiplayer, character.MaxVitality * MaxImpactDamage);
|
||||
}
|
||||
|
||||
private readonly List<Limb> connectedLimbs = new List<Limb>();
|
||||
private readonly List<LimbJoint> checkedJoints = new List<LimbJoint>();
|
||||
public bool SeverLimbJoint(LimbJoint limbJoint)
|
||||
@@ -1031,7 +1046,12 @@ namespace Barotrauma
|
||||
|
||||
CurrentHull = newHull;
|
||||
character.Submarine = currentHull?.Submarine;
|
||||
character.AttachedProjectiles.ForEach(p => p?.Item?.UpdateTransform());
|
||||
foreach (var attachedProjectile in character.AttachedProjectiles)
|
||||
{
|
||||
attachedProjectile.Item.CurrentHull = currentHull;
|
||||
attachedProjectile.Item.Submarine = character.Submarine;
|
||||
attachedProjectile.Item.UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
private void PreventOutsideCollision()
|
||||
@@ -1331,6 +1351,11 @@ namespace Barotrauma
|
||||
if (Collider.LinearVelocity == Vector2.Zero)
|
||||
{
|
||||
character.IsRagdolled = true;
|
||||
if (character.IsBot)
|
||||
{
|
||||
// Seems to work without this on player controlled characters -> not sure if we should call it always or just for the bots.
|
||||
character.SetInput(InputType.Ragdoll, hit: false, held: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1789,12 +1814,6 @@ namespace Barotrauma
|
||||
Character.Latchers.ForEachMod(l => l?.DeattachFromBody(reset: true));
|
||||
Character.Latchers.Clear();
|
||||
|
||||
if (detachProjectiles)
|
||||
{
|
||||
character.AttachedProjectiles.ForEachMod(p => p?.Unstick());
|
||||
character.AttachedProjectiles.Clear();
|
||||
}
|
||||
|
||||
Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
|
||||
if (lerp)
|
||||
{
|
||||
@@ -1831,7 +1850,7 @@ namespace Barotrauma
|
||||
protected void TrySetLimbPosition(Limb limb, Vector2 original, Vector2 simPosition, float rotation, bool lerp = false, bool ignorePlatforms = true)
|
||||
{
|
||||
Vector2 movePos = simPosition;
|
||||
|
||||
Vector2 prevPosition = limb.body.SimPosition;
|
||||
if (Vector2.DistanceSquared(original, simPosition) > 0.0001f)
|
||||
{
|
||||
Category collisionCategory = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
@@ -1859,6 +1878,16 @@ namespace Barotrauma
|
||||
limb.PullJointWorldAnchorB = limb.PullJointWorldAnchorA;
|
||||
limb.PullJointEnabled = false;
|
||||
}
|
||||
foreach (var attachedProjectile in character.AttachedProjectiles)
|
||||
{
|
||||
if (attachedProjectile.IsAttachedTo(limb.body))
|
||||
{
|
||||
attachedProjectile.Item.SetTransform(
|
||||
attachedProjectile.Item.SimPosition + (movePos - prevPosition),
|
||||
attachedProjectile.Item.body.Rotation,
|
||||
findNewHull: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -210,8 +210,8 @@ namespace Barotrauma
|
||||
[Serialize(5f, IsPropertySaveable.Yes, description: "How fast the held weapon is swayed back and forth while aiming. Only affects monsters using ranged weapons (items)."), Editable]
|
||||
public float SwayFrequency { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "Legacy support. Use Afflictions.")]
|
||||
public float Stun { get; private set; }
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "Legacy functionality. Behaves otherwise the same as stuns defined as afflictions, but explosions only apply the stun once instead of dividing it between the limbs.")]
|
||||
public float Stun { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Can damage only Humans."), Editable]
|
||||
public bool OnlyHumans { get; set; }
|
||||
@@ -434,13 +434,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case "conditional":
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
Conditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
Conditionals.AddRange(PropertyConditional.FromXElement(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -544,8 +538,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
// TODO: do we need the conversion to list here? It generates garbage.
|
||||
var targets = targetCharacter.AnimController.Limbs.Cast<ISerializableEntity>().ToList();
|
||||
var targets = targetCharacter.AnimController.Limbs;
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetCharacter, targets);
|
||||
@@ -612,7 +605,10 @@ namespace Barotrauma
|
||||
|
||||
float penetration = Penetration;
|
||||
|
||||
float? penetrationValue = SourceItem?.GetComponent<RangedWeapon>()?.Penetration;
|
||||
RangedWeapon weapon =
|
||||
SourceItem?.GetComponent<RangedWeapon>() ??
|
||||
SourceItem?.GetComponent<Projectile>()?.Launcher?.GetComponent<RangedWeapon>();
|
||||
float? penetrationValue = weapon?.Penetration;
|
||||
if (penetrationValue.HasValue)
|
||||
{
|
||||
penetration += penetrationValue.Value;
|
||||
@@ -646,8 +642,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
// TODO: do we need the conversion to list here? It generates garbage.
|
||||
var targets = targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList();
|
||||
var targets = targetLimb.character.AnimController.Limbs;
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,13 @@ namespace Barotrauma
|
||||
}
|
||||
UpdateLimbLightSource(limb);
|
||||
}
|
||||
foreach (var item in HeldItems)
|
||||
{
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.Enabled = enabled;
|
||||
}
|
||||
}
|
||||
AnimController.Collider.Enabled = value;
|
||||
}
|
||||
}
|
||||
@@ -338,7 +345,7 @@ namespace Barotrauma
|
||||
public bool IsInstigator => CombatAction != null && CombatAction.IsInstigator;
|
||||
public CombatAction CombatAction;
|
||||
|
||||
public AnimController AnimController;
|
||||
public readonly AnimController AnimController;
|
||||
|
||||
private Vector2 cursorPosition;
|
||||
|
||||
@@ -389,6 +396,8 @@ namespace Barotrauma
|
||||
public bool IsMachine => Params.IsMachine;
|
||||
|
||||
public bool IsHusk => Params.Husk;
|
||||
public bool IsDisguisedAsHusk => CharacterHealth.GetAfflictionStrengthByType("disguiseashusk".ToIdentifier()) > 0;
|
||||
public bool IsHuskInfected => CharacterHealth.GetActiveAfflictionTags().Contains("huskinfected".ToIdentifier());
|
||||
|
||||
public bool IsMale => info?.IsMale ?? false;
|
||||
|
||||
@@ -784,7 +793,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (IsUnconscious) { return true; }
|
||||
return CharacterHealth.GetAllAfflictions().Any(a => a.Prefab.AfflictionType == AfflictionPrefab.ParalysisType && a.Strength >= a.Prefab.MaxStrength);
|
||||
return CharacterHealth.IsParalyzed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,7 +804,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsArrested
|
||||
{
|
||||
get { return IsHuman && HasEquippedItem("handlocker"); }
|
||||
get { return IsHuman && HasEquippedItem(Tags.HandLockerItem); }
|
||||
}
|
||||
|
||||
public bool IsPet
|
||||
@@ -855,6 +864,7 @@ namespace Barotrauma
|
||||
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 bool IsFlipped => AnimController.IsFlipped;
|
||||
|
||||
public float Bloodloss
|
||||
{
|
||||
@@ -868,7 +878,7 @@ namespace Barotrauma
|
||||
|
||||
public float Bleeding
|
||||
{
|
||||
get { return CharacterHealth.GetAfflictionStrength(AfflictionPrefab.BleedingType, true); }
|
||||
get { return CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.BleedingType, true); }
|
||||
}
|
||||
|
||||
private bool speechImpedimentSet;
|
||||
@@ -939,6 +949,8 @@ namespace Barotrauma
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.AutoHideCrewList();
|
||||
}
|
||||
|
||||
_selectedItem?.GetComponent<CircuitBox>()?.OnViewUpdateProjSpecific();
|
||||
}
|
||||
#endif
|
||||
if (prevSelectedItem != null && (_selectedItem == null || _selectedItem != prevSelectedItem) && itemSelectedTime > 0)
|
||||
@@ -1076,8 +1088,17 @@ namespace Barotrauma
|
||||
|
||||
public bool IsLowInOxygen => CharacterHealth.OxygenAmount < 100;
|
||||
|
||||
/// <summary>
|
||||
/// Godmoded characters cannot receive any afflictions whatsoever
|
||||
/// </summary>
|
||||
public bool GodMode = false;
|
||||
|
||||
public bool Unkillable
|
||||
{
|
||||
get { return CharacterHealth.Unkillable; }
|
||||
set { CharacterHealth.Unkillable = value; }
|
||||
}
|
||||
|
||||
public CampaignMode.InteractionType CampaignInteractionType;
|
||||
public Identifier MerchantIdentifier;
|
||||
|
||||
@@ -1314,9 +1335,9 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Params.VariantFile != null)
|
||||
if (Params.VariantFile != null && Params.MainElement is ContentXElement paramsMainElement)
|
||||
{
|
||||
var overrideElement = Params.VariantFile.Root.FromPackage(Params.MainElement.ContentPackage);
|
||||
var overrideElement = Params.VariantFile.Root.FromPackage(paramsMainElement.ContentPackage);
|
||||
// Only override if the override file contains matching elements
|
||||
if (overrideElement.GetChildElement("inventory") != null)
|
||||
{
|
||||
@@ -1490,7 +1511,7 @@ namespace Barotrauma
|
||||
var head = AnimController.GetLimb(LimbType.Head);
|
||||
if (head == null) { return; }
|
||||
// Note that if there are any other wearables on the head, they are removed here.
|
||||
head.OtherWearables.ForEach(w => w.Sprite.Remove());
|
||||
head.OtherWearables.ForEach(w => w.Sprite?.Remove());
|
||||
head.OtherWearables.Clear();
|
||||
|
||||
//if the element has not been set at this point, the character has no hair and the index should be zero (= no hair)
|
||||
@@ -1671,17 +1692,31 @@ namespace Barotrauma
|
||||
GameMain.LuaCs.Hook.Call("character.giveJobItems", this, spawnPoint);
|
||||
}
|
||||
|
||||
public void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent = false)
|
||||
|
||||
public void GiveIdCardTags(WayPoint spawnPoint, bool requireSpawnPointTagsNotGiven = true, bool createNetworkEvent = false)
|
||||
{
|
||||
if (info?.Job == null || spawnPoint == null) { return; }
|
||||
GiveIdCardTags(spawnPoint.ToEnumerable(), requireSpawnPointTagsNotGiven, createNetworkEvent);
|
||||
}
|
||||
|
||||
public void GiveIdCardTags(IEnumerable<WayPoint> spawnPoints, bool requireSpawnPointTagsNotGiven = true, bool createNetworkEvent = false)
|
||||
{
|
||||
if (info?.Job == null || spawnPoints == null) { return; }
|
||||
|
||||
foreach (Item item in Inventory.AllItems)
|
||||
{
|
||||
if (item?.GetComponent<IdCard>() == null) { continue; }
|
||||
foreach (string s in spawnPoint.IdCardTags)
|
||||
if (item?.GetComponent<IdCard>() is not IdCard idCard) { continue; }
|
||||
if (requireSpawnPointTagsNotGiven)
|
||||
{
|
||||
item.AddTag(s);
|
||||
if (idCard.SpawnPointTagsGiven) { continue; }
|
||||
}
|
||||
foreach (var spawnPoint in spawnPoints)
|
||||
{
|
||||
foreach (string s in spawnPoint.IdCardTags)
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
}
|
||||
idCard.SpawnPointTagsGiven = true;
|
||||
if (createNetworkEvent && GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ChangePropertyEventData(item.SerializableProperties[nameof(item.Tags).ToIdentifier()], item));
|
||||
@@ -1990,14 +2025,21 @@ namespace Barotrauma
|
||||
|
||||
if (AnimController is HumanoidAnimController humanAnimController)
|
||||
{
|
||||
humanAnimController.Crouching = humanAnimController.ForceSelectAnimationType == AnimationType.Crouch || IsKeyDown(InputType.Crouch);
|
||||
humanAnimController.Crouching =
|
||||
humanAnimController.ForceSelectAnimationType == AnimationType.Crouch ||
|
||||
IsKeyDown(InputType.Crouch);
|
||||
if (Screen.Selected is not { IsEditor: true })
|
||||
{
|
||||
humanAnimController.ForceSelectAnimationType = AnimationType.NotDefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!aiControlled &&
|
||||
!AnimController.IsUsingItem &&
|
||||
AnimController.Anim != AnimController.Animation.CPR &&
|
||||
(GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient || Controlled == this) &&
|
||||
(AnimController.OnGround || IsClimbing) && !AnimController.InWater)
|
||||
((!IsClimbing && AnimController.OnGround) || (IsClimbing && IsKeyDown(InputType.Aim))) &&
|
||||
!AnimController.InWater)
|
||||
{
|
||||
if (dontFollowCursor)
|
||||
{
|
||||
@@ -2194,14 +2236,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (!item.RequireAimToUse || IsKeyDown(InputType.Aim))
|
||||
{
|
||||
item.Use(deltaTime, this);
|
||||
item.Use(deltaTime, user: this);
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(InputType.Shoot) && item.IsShootable)
|
||||
{
|
||||
if (!item.RequireAimToUse || IsKeyDown(InputType.Aim))
|
||||
{
|
||||
item.Use(deltaTime, this);
|
||||
item.Use(deltaTime, user: this);
|
||||
}
|
||||
#if CLIENT
|
||||
else if (item.RequireAimToUse && !IsKeyDown(InputType.Aim))
|
||||
@@ -2221,7 +2263,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!SelectedCharacter.CanBeSelected ||
|
||||
(Vector2.DistanceSquared(SelectedCharacter.WorldPosition, WorldPosition) > MaxDragDistance * MaxDragDistance &&
|
||||
SelectedCharacter.GetDistanceToClosestLimb(SimPosition) > ConvertUnits.ToSimUnits(MaxDragDistance)))
|
||||
SelectedCharacter.GetDistanceToClosestLimb(GetRelativeSimPosition(selectedCharacter, WorldPosition)) > ConvertUnits.ToSimUnits(MaxDragDistance)))
|
||||
{
|
||||
DeselectCharacter();
|
||||
}
|
||||
@@ -2254,26 +2296,58 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
|
||||
public bool CanSeeCharacter(Character target)
|
||||
private Limb GetSeeingLimb()
|
||||
{
|
||||
return AnimController.GetLimb(LimbType.Head) ?? AnimController.GetLimb(LimbType.Torso) ?? AnimController.MainLimb;
|
||||
}
|
||||
|
||||
public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null, bool checkFacing = false)
|
||||
{
|
||||
seeingEntity ??= AnimController.SimplePhysicsEnabled ? this : GetSeeingLimb();
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
return IsCharacterVisible(targetCharacter, seeingEntity, checkFacing);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CheckVisibility(target, seeingEntity, checkFacing);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsTargetVisible(ISpatialEntity target, ISpatialEntity seeingEntity, bool checkFacing = false)
|
||||
{
|
||||
if (seeingEntity is Character seeingCharacter)
|
||||
{
|
||||
return seeingCharacter.CanSeeTarget(target, checkFacing: checkFacing);
|
||||
}
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
return IsCharacterVisible(targetCharacter, seeingEntity, checkFacing);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CheckVisibility(target, seeingEntity, checkFacing);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsCharacterVisible(Character target, ISpatialEntity seeingEntity, bool checkFacing = false)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
if (target == null) { return false; }
|
||||
if (target.Removed) { return false; }
|
||||
Limb seeingLimb = GetSeeingLimb();
|
||||
if (CanSeeTarget(target, seeingLimb)) { return true; }
|
||||
if (target == null || target.Removed) { return false; }
|
||||
if (CheckVisibility(target, seeingEntity, checkFacing)) { return true; }
|
||||
if (!target.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
//find the limbs that are furthest from the target's position (from the viewer's point of view)
|
||||
Limb leftExtremity = null, rightExtremity = null;
|
||||
float leftMostDot = 0.0f, rightMostDot = 0.0f;
|
||||
Vector2 dir = target.WorldPosition - WorldPosition;
|
||||
Vector2 dir = target.WorldPosition - seeingEntity.WorldPosition;
|
||||
Vector2 leftDir = new Vector2(dir.Y, -dir.X);
|
||||
Vector2 rightDir = new Vector2(-dir.Y, dir.X);
|
||||
foreach (Limb limb in target.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered || limb == target.AnimController.MainLimb) { continue; }
|
||||
if (limb.Hidden) { continue; }
|
||||
Vector2 limbDir = limb.WorldPosition - WorldPosition;
|
||||
Vector2 limbDir = limb.WorldPosition - seeingEntity.WorldPosition;
|
||||
float leftDot = Vector2.Dot(limbDir, leftDir);
|
||||
if (leftDot > leftMostDot)
|
||||
{
|
||||
@@ -2289,42 +2363,39 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (leftExtremity != null && CanSeeTarget(leftExtremity, seeingLimb)) { return true; }
|
||||
if (rightExtremity != null && CanSeeTarget(rightExtremity, seeingLimb)) { return true; }
|
||||
if (leftExtremity != null && CheckVisibility(leftExtremity, seeingEntity, checkFacing)) { return true; }
|
||||
if (rightExtremity != null && CheckVisibility(rightExtremity, seeingEntity, checkFacing)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Limb GetSeeingLimb()
|
||||
{
|
||||
return AnimController.GetLimb(LimbType.Head) ?? AnimController.GetLimb(LimbType.Torso) ?? AnimController.MainLimb;
|
||||
}
|
||||
|
||||
public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null)
|
||||
private static bool CheckVisibility(ISpatialEntity target, ISpatialEntity seeingEntity, bool checkFacing = false)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
if (target == null) { return false; }
|
||||
seeingEntity ??= AnimController.SimplePhysicsEnabled ? this : GetSeeingLimb() as ISpatialEntity;
|
||||
if (seeingEntity == null) { return false; }
|
||||
ISpatialEntity sourceEntity = seeingEntity ;
|
||||
// TODO: Could we just use the method below? If not, let's refactor it so that we can.
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - sourceEntity.WorldPosition);
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(target.WorldPosition - seeingEntity.WorldPosition);
|
||||
if (checkFacing && seeingEntity is Character seeingCharacter)
|
||||
{
|
||||
if (Math.Sign(diff.X) != seeingCharacter.AnimController.Dir) { return false; }
|
||||
}
|
||||
Body closestBody;
|
||||
//both inside the same sub (or both outside)
|
||||
//OR the we're inside, the other character outside
|
||||
if (target.Submarine == Submarine || target.Submarine == null)
|
||||
if (target.Submarine == seeingEntity.Submarine || target.Submarine == null)
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(sourceEntity.SimPosition, sourceEntity.SimPosition + diff);
|
||||
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
|
||||
}
|
||||
//we're outside, the other character inside
|
||||
else if (Submarine == null)
|
||||
else if (seeingEntity.Submarine == null)
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff);
|
||||
}
|
||||
//both inside different subs
|
||||
else
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(sourceEntity.SimPosition, sourceEntity.SimPosition + diff);
|
||||
closestBody = Submarine.CheckVisibility(seeingEntity.SimPosition, seeingEntity.SimPosition + diff);
|
||||
if (!IsBlocking(closestBody))
|
||||
{
|
||||
closestBody = Submarine.CheckVisibility(target.SimPosition, target.SimPosition - diff);
|
||||
@@ -2377,9 +2448,6 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasEquippedItem(string tagOrIdentifier, bool allowBroken = true, InvSlotType? slotType = null)
|
||||
=> HasEquippedItem(tagOrIdentifier.ToIdentifier(), allowBroken, slotType);
|
||||
|
||||
public bool HasEquippedItem(Identifier tagOrIdentifier, bool allowBroken = true, InvSlotType? slotType = null)
|
||||
{
|
||||
if (Inventory == null) { return false; }
|
||||
@@ -2401,7 +2469,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public Item GetEquippedItem(string tagOrIdentifier = null, InvSlotType? slotType = null)
|
||||
public Item GetEquippedItem(Identifier? tagOrIdentifier = null, InvSlotType? slotType = null)
|
||||
{
|
||||
if (Inventory == null) { return null; }
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
@@ -2416,7 +2484,10 @@ namespace Barotrauma
|
||||
}
|
||||
var item = Inventory.GetItemAt(i);
|
||||
if (item == null) { continue; }
|
||||
if (tagOrIdentifier == null || item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; }
|
||||
if (tagOrIdentifier == null || tagOrIdentifier.Value.IsEmpty || item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier.Value))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -2544,7 +2615,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
return !checkVisibility || CanSeeCharacter(c);
|
||||
return !checkVisibility || CanSeeTarget(c);
|
||||
}
|
||||
|
||||
public bool CanInteractWith(Item item, bool checkLinked = true)
|
||||
@@ -2594,7 +2665,10 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (MapEntity linked in item.linkedTo)
|
||||
{
|
||||
if (linked is Item linkedItem)
|
||||
if (linked is Item linkedItem &&
|
||||
//if the linked item is inside this container (a modder or sub builder doing smth really weird?)
|
||||
//don't check it here because it'd lead to an infinite loop
|
||||
linkedItem.ParentInventory?.Owner != item)
|
||||
{
|
||||
if (CanInteractWith(linkedItem, out float distToLinked, checkLinked: false))
|
||||
{
|
||||
@@ -2682,10 +2756,18 @@ namespace Barotrauma
|
||||
|
||||
if (SelectedSecondaryItem != null && !item.IsSecondaryItem)
|
||||
{
|
||||
//don't allow selecting another Controller if it'd try to turn the character in the opposite direction
|
||||
//(e.g. periscope that's facing the wrong way while sitting in a chair)
|
||||
if (item.GetComponent<Controller>() is { } controller && controller.Direction != 0 && controller.Direction != AnimController.Direction) { return false; }
|
||||
float threshold = ConvertUnits.ToSimUnits(cursorFollowMargin);
|
||||
if (AnimController.Direction == Direction.Left && SimPosition.X + threshold < itemPosition.X) { return false; }
|
||||
if (AnimController.Direction == Direction.Right && SimPosition.X - threshold > itemPosition.X) { return false; }
|
||||
|
||||
//if a Controller that controls the character's pose is selected,
|
||||
//don't allow selecting items that are behind the character's back
|
||||
if (SelectedSecondaryItem.GetComponent<Controller>() is { ControlCharacterPose: true } selectedController)
|
||||
{
|
||||
float threshold = ConvertUnits.ToSimUnits(cursorFollowMargin);
|
||||
if (AnimController.Direction == Direction.Left && SimPosition.X + threshold < itemPosition.X) { return false; }
|
||||
if (AnimController.Direction == Direction.Right && SimPosition.X - threshold > itemPosition.X) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger)
|
||||
@@ -2710,7 +2792,7 @@ namespace Barotrauma
|
||||
this.onCustomInteract = onCustomInteract;
|
||||
CustomInteractHUDText = hudText;
|
||||
}
|
||||
|
||||
|
||||
public void SelectCharacter(Character character)
|
||||
{
|
||||
if (character == null || character == this) { return; }
|
||||
@@ -2761,7 +2843,7 @@ namespace Barotrauma
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld)
|
||||
{
|
||||
FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null;
|
||||
if (FocusedCharacter != null && !CanSeeCharacter(FocusedCharacter)) { FocusedCharacter = null; }
|
||||
if (FocusedCharacter != null && !CanSeeTarget(FocusedCharacter)) { FocusedCharacter = null; }
|
||||
float aimAssist = GameSettings.CurrentConfig.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f);
|
||||
if (HeldItems.Any(it => it?.GetComponent<Wire>()?.IsActive ?? false))
|
||||
{
|
||||
@@ -2874,7 +2956,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
else if (!IsClimbing)
|
||||
{
|
||||
#if CLIENT
|
||||
if (Controlled == this)
|
||||
@@ -2922,9 +3004,9 @@ namespace Barotrauma
|
||||
CharacterHealth.OpenHealthWindow = null;
|
||||
#endif
|
||||
}
|
||||
else if (IsKeyHit(InputType.Health) && (SelectedItem != null || SelectedSecondaryItem != null))
|
||||
else if (IsKeyHit(InputType.Health) && SelectedItem != null)
|
||||
{
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
SelectedItem = null;
|
||||
}
|
||||
else if (focusedItem != null)
|
||||
{
|
||||
@@ -3126,6 +3208,14 @@ namespace Barotrauma
|
||||
//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 (PressureTimer > CharacterHealth.PressureKillDelay * 0.1f)
|
||||
{
|
||||
//after a brief delay, start doing increasing amounts of organ damage
|
||||
CharacterHealth.ApplyAffliction(
|
||||
targetLimb: AnimController.MainLimb,
|
||||
new Affliction(AfflictionPrefab.OrganDamage, PressureTimer / 10.0f * deltaTime));
|
||||
}
|
||||
|
||||
if (CharacterHealth.PressureKillDelay <= 0.0f)
|
||||
{
|
||||
PressureTimer = 100.0f;
|
||||
@@ -3195,47 +3285,48 @@ namespace Barotrauma
|
||||
|
||||
UpdateAIChatMessages(deltaTime);
|
||||
|
||||
if (GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true)
|
||||
bool wasRagdolled = IsRagdolled;
|
||||
if (IsForceRagdolled)
|
||||
{
|
||||
bool wasRagdolled = IsRagdolled;
|
||||
if (IsForceRagdolled)
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
}
|
||||
else if (this != Controlled)
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll);
|
||||
if (IsRagdolled && IsBot && GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
}
|
||||
else if (this != Controlled)
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll);
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
CheckTalents(AbilityEffectType.OnRagdoll);
|
||||
ClearInput(InputType.Ragdoll);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
CheckTalents(AbilityEffectType.OnRagdoll);
|
||||
}
|
||||
|
||||
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
|
||||
|
||||
@@ -3438,7 +3529,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private float despawnTimer;
|
||||
private void UpdateDespawn(float deltaTime, bool ignoreThresholds = false, bool createNetworkEvents = true)
|
||||
private void UpdateDespawn(float deltaTime, bool createNetworkEvents = true)
|
||||
{
|
||||
if (!EnableDespawn) { return; }
|
||||
|
||||
@@ -3449,7 +3540,7 @@ namespace Barotrauma
|
||||
|
||||
int subCorpseCount = 0;
|
||||
|
||||
if (Submarine != null && !ignoreThresholds)
|
||||
if (Submarine != null)
|
||||
{
|
||||
subCorpseCount = CharacterList.Count(c => c.IsDead && c.Submarine == Submarine);
|
||||
if (subCorpseCount < GameSettings.CurrentConfig.CorpsesPerSubDespawnThreshold) { return; }
|
||||
@@ -3483,6 +3574,11 @@ namespace Barotrauma
|
||||
despawnTimer += deltaTime * despawnPriority;
|
||||
if (despawnTimer < GameSettings.CurrentConfig.CorpseDespawnDelay) { return; }
|
||||
|
||||
Despawn();
|
||||
}
|
||||
|
||||
private void Despawn(bool createNetworkEvents = true)
|
||||
{
|
||||
Identifier despawnContainerId =
|
||||
IsHuman ?
|
||||
"despawncontainer".ToIdentifier() :
|
||||
@@ -3533,8 +3629,7 @@ namespace Barotrauma
|
||||
|
||||
public void DespawnNow(bool createNetworkEvents = true)
|
||||
{
|
||||
despawnTimer = GameSettings.CurrentConfig.CorpseDespawnDelay;
|
||||
UpdateDespawn(1.0f, ignoreThresholds: true, createNetworkEvents: createNetworkEvents);
|
||||
Despawn(createNetworkEvents);
|
||||
//update twice: first to spawn the duffel bag and move the items into it, then to remove the character
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
@@ -4278,7 +4373,7 @@ namespace Barotrauma
|
||||
if (Screen.Selected != GameMain.GameScreen) { return; }
|
||||
if (newStun > 0 && Params.Health.StunImmunity)
|
||||
{
|
||||
if (EmpVulnerability <= 0 || CharacterHealth.GetAfflictionStrength(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
|
||||
if (EmpVulnerability <= 0 || CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -4342,8 +4437,7 @@ namespace Barotrauma
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == limbType)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4353,8 +4447,7 @@ namespace Barotrauma
|
||||
Limb limb = AnimController.GetLimb(limbType);
|
||||
if (limb != null)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, this, limb);
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
|
||||
@@ -4363,12 +4456,20 @@ namespace Barotrauma
|
||||
Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
|
||||
if (limb != null)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, this, limb);
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, this, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
// Target all limbs
|
||||
foreach (var limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, character: this, limb);
|
||||
}
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
@@ -4392,6 +4493,12 @@ namespace Barotrauma
|
||||
{
|
||||
CharacterHealth.ApplyAfflictionStatusEffects(actionType);
|
||||
}
|
||||
|
||||
static void ApplyToLimb(ActionType actionType, float deltaTime, StatusEffect statusEffect, Character character, Limb limb)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, entity: character, target: limb);
|
||||
}
|
||||
}
|
||||
|
||||
private void Implode(bool isNetworkMessage = false)
|
||||
@@ -4452,7 +4559,7 @@ namespace Barotrauma
|
||||
|
||||
public void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage = false, bool log = true)
|
||||
{
|
||||
if (IsDead || CharacterHealth.Unkillable || GodMode) { return; }
|
||||
if (IsDead || CharacterHealth.Unkillable || GodMode || Removed) { return; }
|
||||
|
||||
HealthUpdateInterval = 0.0f;
|
||||
|
||||
@@ -4553,20 +4660,21 @@ namespace Barotrauma
|
||||
SelectedCharacter = null;
|
||||
|
||||
AnimController.ResetPullJoints();
|
||||
|
||||
foreach (var joint in AnimController.LimbJoints)
|
||||
if (AnimController.LimbJoints != null)
|
||||
{
|
||||
if (joint.revoluteJoint != null)
|
||||
foreach (var joint in AnimController.LimbJoints)
|
||||
{
|
||||
joint.revoluteJoint.MotorEnabled = false;
|
||||
if (joint.revoluteJoint != null)
|
||||
{
|
||||
joint.revoluteJoint.MotorEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.GameSession?.KillCharacter(this);
|
||||
}
|
||||
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log);
|
||||
|
||||
public void Revive(bool removeAllAfflictions = true)
|
||||
public void Revive(bool removeAfflictions = true)
|
||||
{
|
||||
if (Removed)
|
||||
{
|
||||
@@ -4577,20 +4685,21 @@ namespace Barotrauma
|
||||
aiTarget?.Remove();
|
||||
|
||||
aiTarget = new AITarget(this);
|
||||
if (removeAllAfflictions)
|
||||
if (removeAfflictions)
|
||||
{
|
||||
CharacterHealth.RemoveAllAfflictions();
|
||||
SetAllDamage(0.0f, 0.0f, 0.0f);
|
||||
Bloodloss = 0.0f;
|
||||
SetStun(0.0f, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterHealth.RemoveNegativeAfflictions();
|
||||
}
|
||||
SetAllDamage(0.0f, 0.0f, 0.0f);
|
||||
Oxygen = 100.0f;
|
||||
Bloodloss = 0.0f;
|
||||
SetStun(0.0f, true);
|
||||
isDead = false;
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
info.CauseOfDeath = null;
|
||||
}
|
||||
|
||||
foreach (LimbJoint joint in AnimController.LimbJoints)
|
||||
{
|
||||
var revoluteJoint = joint.revoluteJoint;
|
||||
@@ -4614,10 +4723,7 @@ namespace Barotrauma
|
||||
limb.IsSevered = false;
|
||||
}
|
||||
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
GameMain.GameSession.ReviveCharacter(this);
|
||||
}
|
||||
GameMain.GameSession?.ReviveCharacter(this);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
@@ -4732,7 +4838,7 @@ namespace Barotrauma
|
||||
{
|
||||
SpawnInventoryItemsRecursive(inventory, itemData, new List<Item>());
|
||||
}
|
||||
|
||||
|
||||
private void SpawnInventoryItemsRecursive(Inventory inventory, ContentXElement element, List<Item> extraDuffelBags)
|
||||
{
|
||||
foreach (var itemElement in element.Elements())
|
||||
@@ -4757,21 +4863,6 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure there's no other item in the slot
|
||||
//this should not happen normally, but can occur if the character is accidentally given new job items while also loading previous items in the campaign
|
||||
for (int i = 0; i < inventory.Capacity; i++)
|
||||
{
|
||||
if (slotIndices.Contains(i))
|
||||
{
|
||||
var existingItem = inventory.GetItemAt(i);
|
||||
if (existingItem != null && existingItem != newItem && (((MapEntity)existingItem).Prefab != ((MapEntity)newItem).Prefab || existingItem.Prefab.MaxStackSize == 1))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while loading character inventory data. The slot {i} was already occupied by the item \"{existingItem.Name} ({existingItem.ID})\" when loading the item \"{newItem.Name} ({newItem.ID})\"");
|
||||
existingItem.Drop(null, createNetworkEvent: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool canBePutInOriginalInventory = true;
|
||||
if (slotIndices[0] >= inventory.Capacity)
|
||||
{
|
||||
@@ -4853,6 +4944,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
foreach (var circuitBox in newItem.GetComponents<CircuitBox>())
|
||||
{
|
||||
circuitBox.MarkServerRequiredInitialization();
|
||||
}
|
||||
#endif
|
||||
|
||||
int itemContainerIndex = 0;
|
||||
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
|
||||
foreach (var childInvElement in itemElement.Elements())
|
||||
@@ -4941,41 +5039,7 @@ namespace Barotrauma
|
||||
return visibleHulls;
|
||||
}
|
||||
|
||||
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 = to.SimPosition;
|
||||
if (worldPos.HasValue)
|
||||
{
|
||||
Vector2 wp = worldPos.Value;
|
||||
if (to.Submarine != null)
|
||||
{
|
||||
wp -= to.Submarine.Position;
|
||||
}
|
||||
targetPos = ConvertUnits.ToSimUnits(wp);
|
||||
}
|
||||
if (from.Submarine == null && to.Submarine != null)
|
||||
{
|
||||
// outside and targeting inside
|
||||
targetPos += to.Submarine.SimPosition;
|
||||
}
|
||||
else if (from.Submarine != null && to.Submarine == null)
|
||||
{
|
||||
// inside and targeting outside
|
||||
targetPos -= from.Submarine.SimPosition;
|
||||
}
|
||||
else if (from.Submarine != to.Submarine)
|
||||
{
|
||||
if (from.Submarine != null && to.Submarine != null)
|
||||
{
|
||||
// both inside, but in different subs
|
||||
Vector2 diff = from.Submarine.SimPosition - to.Submarine.SimPosition;
|
||||
targetPos -= diff;
|
||||
}
|
||||
}
|
||||
return targetPos;
|
||||
}
|
||||
public Vector2 GetRelativeSimPosition(ISpatialEntity target, Vector2? worldPos = null) => Submarine.GetRelativeSimPosition(this, target, worldPos);
|
||||
|
||||
public bool IsCaptain => HasJob("captain");
|
||||
public bool IsEngineer => HasJob("engineer");
|
||||
@@ -4986,6 +5050,8 @@ namespace Barotrauma
|
||||
public bool IsWatchman => HasJob("watchman");
|
||||
public bool IsVip => HasJob("prisoner");
|
||||
public bool IsPrisoner => HasJob("prisoner");
|
||||
public bool IsKiller => HasJob("killer");
|
||||
|
||||
public Color? UniqueNameColor { get; set; } = null;
|
||||
|
||||
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
|
||||
@@ -5072,6 +5138,7 @@ namespace Barotrauma
|
||||
|
||||
public bool HasTalent(Identifier identifier)
|
||||
{
|
||||
if (info == null) { return false; }
|
||||
return info.UnlockedTalents.Contains(identifier);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,12 +37,18 @@ namespace Barotrauma
|
||||
public EventType EventType { get; }
|
||||
}
|
||||
|
||||
public struct InventoryStateEventData : IEventData
|
||||
public readonly struct InventoryStateEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.InventoryState;
|
||||
public readonly Range SlotRange;
|
||||
|
||||
public InventoryStateEventData(Range slotRange)
|
||||
{
|
||||
SlotRange = slotRange;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ControlEventData : IEventData
|
||||
public readonly struct ControlEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Control;
|
||||
public readonly Client Owner;
|
||||
|
||||
@@ -440,10 +440,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (handleBuff)
|
||||
{
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
if (AfflictionPrefab.Prefabs.TryGet("disguised", out AfflictionPrefab afflictionPrefab))
|
||||
{
|
||||
Character.CharacterHealth.ApplyAffliction(head, AfflictionPrefab.List.FirstOrDefault(a => a.Identifier == "disguised").Instantiate(100f));
|
||||
Character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(100f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,11 +463,7 @@ namespace Barotrauma
|
||||
|
||||
if (handleBuff)
|
||||
{
|
||||
var head = Character.AnimController.GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
Character.CharacterHealth.ReduceAfflictionOnLimb(head, "disguised".ToIdentifier(), 100f);
|
||||
}
|
||||
Character.CharacterHealth.ReduceAfflictionOnAllLimbs("disguised".ToIdentifier(), 100f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,11 +678,15 @@ namespace Barotrauma
|
||||
FacialHairColors = CharacterConfigElement.GetAttributeTupleArray("facialhaircolors", new (Color, float)[] { (Color.WhiteSmoke, 100f) }).ToImmutableArray();
|
||||
SkinColors = CharacterConfigElement.GetAttributeTupleArray("skincolors", new (Color, float)[] { (new Color(255, 215, 200, 255), 100f) }).ToImmutableArray();
|
||||
|
||||
var headPreset = Prefab.Heads.GetRandom(randSync);
|
||||
var headPreset = Prefab?.Heads.GetRandom(randSync);
|
||||
if (headPreset == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to find a head preset!");
|
||||
}
|
||||
Head = new HeadInfo(this, headPreset);
|
||||
SetAttachments(randSync);
|
||||
SetColors(randSync);
|
||||
|
||||
|
||||
Job = job ?? ((jobPrefab == null) ? Job.Random(Rand.RandSync.Unsynced) : new Job(jobPrefab, randSync, variant));
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
@@ -1089,6 +1088,7 @@ namespace Barotrauma
|
||||
|
||||
private void LoadHeadSprite()
|
||||
{
|
||||
if (Ragdoll?.MainElement == null) { return; }
|
||||
foreach (var limbElement in Ragdoll.MainElement.Elements())
|
||||
{
|
||||
if (!limbElement.GetAttributeString("type", string.Empty).Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
@@ -1386,7 +1386,7 @@ namespace Barotrauma
|
||||
// Replace the name tag of any existing id cards or duffel bags
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
if (!item.HasTag("identitycard") && !item.HasTag("despawncontainer")) { continue; }
|
||||
if (!item.HasTag("identitycard".ToIdentifier()) && !item.HasTag("despawncontainer".ToIdentifier())) { continue; }
|
||||
foreach (var tag in item.Tags.Split(','))
|
||||
{
|
||||
var splitTag = tag.Split(":");
|
||||
@@ -1446,7 +1446,7 @@ namespace Barotrauma
|
||||
if (MinReputationToHire.factionId != default)
|
||||
{
|
||||
charElement.Add(
|
||||
new XAttribute("factionId", Name),
|
||||
new XAttribute("factionId", MinReputationToHire.factionId),
|
||||
new XAttribute("minreputation", MinReputationToHire.reputation));
|
||||
}
|
||||
|
||||
@@ -1527,7 +1527,10 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
targetAvailableInNextLevel = !isOutside && GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null && (isOnConnectedLinkedSub || entitySub == Submarine.MainSub);
|
||||
targetAvailableInNextLevel =
|
||||
!isOutside &&
|
||||
GameMain.GameSession?.Campaign is not { SwitchedSubsThisRound: true } &&
|
||||
(isOnConnectedLinkedSub || entitySub == Submarine.MainSub);
|
||||
if (!targetAvailableInNextLevel)
|
||||
{
|
||||
if (!order.Prefab.CanBeGeneralized)
|
||||
@@ -1652,8 +1655,12 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
var targetType = (Order.OrderTargetType)orderElement.GetAttributeInt("targettype", 0);
|
||||
int orderGiverInfoId = orderElement.GetAttributeInt("ordergiver", -1);
|
||||
var orderGiver = orderGiverInfoId >= 0 ? Character.CharacterList.FirstOrDefault(c => c.Info?.GetIdentifier() == orderGiverInfoId) : null;
|
||||
Character orderGiver = null;
|
||||
if (orderElement.GetAttribute("ordergiver") is XAttribute orderGiverIdAttribute)
|
||||
{
|
||||
int orderGiverInfoId = orderGiverIdAttribute.GetAttributeInt(0);
|
||||
orderGiver = Character.CharacterList.FirstOrDefault(c => c.Info?.GetIdentifier() == orderGiverInfoId);
|
||||
}
|
||||
Entity targetEntity = null;
|
||||
switch (targetType)
|
||||
{
|
||||
@@ -1717,6 +1724,7 @@ namespace Barotrauma
|
||||
{
|
||||
targetId = GetOffsetId(parentSub, targetId);
|
||||
targetEntity = Entity.FindEntityByID(targetId);
|
||||
return targetEntity != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1730,8 +1738,8 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.AddWarning($"Trying to load a previously saved order ({orderIdentifier}). Can't find the parent sub of the target entity. The order doesn't require a target so a more generic version of the order will be loaded instead.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return orders;
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace Barotrauma
|
||||
public static IEnumerable<ContentXElement> ConfigElements => Prefabs.Select(p => p.ConfigElement);
|
||||
|
||||
public static readonly Identifier HumanSpeciesName = "human".ToIdentifier();
|
||||
public static readonly Identifier HumanGroup = "human".ToIdentifier();
|
||||
|
||||
public static CharacterFile HumanConfigFile => HumanPrefab.ContentFile as CharacterFile;
|
||||
public static CharacterPrefab HumanPrefab => FindBySpeciesName(HumanSpeciesName);
|
||||
|
||||
|
||||
+1
-1
@@ -603,7 +603,6 @@ namespace Barotrauma
|
||||
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"];
|
||||
@@ -613,6 +612,7 @@ namespace Barotrauma
|
||||
public static AfflictionPrefab OxygenLow => Prefabs["oxygenlow"];
|
||||
public static AfflictionPrefab Bloodloss => Prefabs["bloodloss"];
|
||||
public static AfflictionPrefab Pressure => Prefabs["pressure"];
|
||||
public static AfflictionPrefab OrganDamage => Prefabs["organdamage"];
|
||||
public static AfflictionPrefab Stun => Prefabs[StunType];
|
||||
public static AfflictionPrefab RadiationSickness => Prefabs["radiationsickness"];
|
||||
|
||||
|
||||
@@ -161,6 +161,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How much vitality the character would have if it was alive?
|
||||
/// E.g. a character killed by disconnection or with console commands may not have any vitality-reducing afflictions despite being dead
|
||||
/// </summary>
|
||||
public float VitalityDisregardingDeath => vitality;
|
||||
|
||||
public float HealthPercentage => MathUtils.Percentage(Vitality, MaxVitality);
|
||||
|
||||
public float MaxVitality
|
||||
@@ -234,6 +240,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsParalyzed { get; private set; }
|
||||
|
||||
public float StunTimer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -407,7 +415,17 @@ namespace Barotrauma
|
||||
return strength;
|
||||
}
|
||||
|
||||
public float GetAfflictionStrength(Identifier afflictionType, bool allowLimbAfflictions = true)
|
||||
public float GetAfflictionStrengthByType(Identifier afflictionType, bool allowLimbAfflictions = true)
|
||||
{
|
||||
return GetAfflictionStrength(afflictionType, afflictionidentifier: Identifier.Empty, allowLimbAfflictions);
|
||||
}
|
||||
|
||||
public float GetAfflictionStrengthByIdentifier(Identifier afflictionIdentifier, bool allowLimbAfflictions = true)
|
||||
{
|
||||
return GetAfflictionStrength(afflictionType: Identifier.Empty, afflictionIdentifier, allowLimbAfflictions);
|
||||
}
|
||||
|
||||
public float GetAfflictionStrength(Identifier afflictionType, Identifier afflictionidentifier, bool allowLimbAfflictions = true)
|
||||
{
|
||||
float strength = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
@@ -415,7 +433,8 @@ namespace Barotrauma
|
||||
if (!allowLimbAfflictions && kvp.Value != null) { continue; }
|
||||
var affliction = kvp.Key;
|
||||
if (affliction.Strength < affliction.Prefab.ActivationThreshold) { continue; }
|
||||
if (affliction.Prefab.AfflictionType == afflictionType)
|
||||
if ((affliction.Prefab.AfflictionType == afflictionType || afflictionType.IsEmpty) &&
|
||||
(affliction.Prefab.Identifier == afflictionidentifier || afflictionidentifier.IsEmpty))
|
||||
{
|
||||
strength += affliction.Strength;
|
||||
}
|
||||
@@ -725,7 +744,7 @@ namespace Barotrauma
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == AfflictionPrefab.StunType)
|
||||
{
|
||||
if (Character.EmpVulnerability <= 0 || GetAfflictionStrength(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
|
||||
if (Character.EmpVulnerability <= 0 || GetAfflictionStrengthByType(AfflictionPrefab.EMPType, allowLimbAfflictions: false) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -969,6 +988,7 @@ namespace Barotrauma
|
||||
public void CalculateVitality()
|
||||
{
|
||||
Vitality = MaxVitality;
|
||||
IsParalyzed = false;
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
@@ -982,6 +1002,12 @@ namespace Barotrauma
|
||||
}
|
||||
Vitality -= vitalityDecrease;
|
||||
affliction.CalculateDamagePerSecond(vitalityDecrease);
|
||||
|
||||
if (affliction.Strength >= affliction.Prefab.MaxStrength &&
|
||||
affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
{
|
||||
IsParalyzed = true;
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (IsUnconscious)
|
||||
|
||||
@@ -134,7 +134,13 @@ namespace Barotrauma
|
||||
foreach (XElement itemElement in spawnItems.GetChildElements("Item"))
|
||||
{
|
||||
InitializeJobItem(character, itemElement, spawnPoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.GameSession is { TraitorsEnabled: true } && character.IsSecurity)
|
||||
{
|
||||
var traitorGuidelineItem = ItemPrefab.Prefabs.Find(ip => ip.Tags.Contains(Tags.TraitorGuidelinesForSecurity));
|
||||
Entity.Spawner.AddItemToSpawnQueue(traitorGuidelineItem, character.Inventory);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeJobItem(Character character, XElement itemElement, WayPoint spawnPoint = null, Item parentItem = null)
|
||||
@@ -144,7 +150,7 @@ namespace Barotrauma
|
||||
{
|
||||
string itemName = itemElement.Attribute("name").Value;
|
||||
DebugConsole.ThrowError("Error in Job config (" + Name + ") - use item identifiers instead of names to configure the items.");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
itemPrefab = MapEntityPrefab.FindByName(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to spawn \"" + Name + "\" with the item \"" + itemName + "\". Matching item prefab not found.");
|
||||
|
||||
@@ -591,7 +591,10 @@ namespace Barotrauma
|
||||
public bool IsDead => character.IsDead;
|
||||
public float Health => character.Health;
|
||||
public float HealthPercentage => character.HealthPercentage;
|
||||
public bool IsHuman => character.IsHuman;
|
||||
|
||||
public AIState AIState => character.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
|
||||
public bool IsFlipped => character.AnimController.IsFlipped;
|
||||
|
||||
public bool CanBeSeveredAlive
|
||||
{
|
||||
@@ -881,7 +884,9 @@ namespace Barotrauma
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
ApplyStatusEffects(ActionType.Always, deltaTime);
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||
|
||||
if (InWater)
|
||||
{
|
||||
body.ApplyWaterForces();
|
||||
@@ -1223,6 +1228,7 @@ namespace Barotrauma
|
||||
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
||||
foreach (StatusEffect statusEffect in statusEffectList)
|
||||
{
|
||||
statusEffect.sourceBody = body;
|
||||
if (statusEffect.type == ActionType.OnDamaged)
|
||||
{
|
||||
if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; }
|
||||
@@ -1241,65 +1247,64 @@ namespace Barotrauma
|
||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(actionType, deltaTime, character, targets);
|
||||
}
|
||||
else
|
||||
else if (statusEffect.targetLimbs != null)
|
||||
{
|
||||
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.Contained) && character.Inventory is { } inventory)
|
||||
foreach (var limbType in statusEffect.targetLimbs)
|
||||
{
|
||||
foreach (Item item in inventory.AllItems)
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
if (statusEffect.TargetIdentifiers != null &&
|
||||
!statusEffect.TargetIdentifiers.Contains(item.Prefab.Identifier) &&
|
||||
statusEffect.TargetIdentifiers.None(id => item.HasTag(id)))
|
||||
// Target all matching limbs
|
||||
foreach (var limb in ragdoll.Limbs)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (statusEffect.TargetSlot > -1)
|
||||
{
|
||||
if (inventory.FindIndex(item) != statusEffect.TargetSlot) { continue; }
|
||||
}
|
||||
targets.Add(item);
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, character, character, WorldPosition);
|
||||
}
|
||||
else if (statusEffect.targetLimbs != null)
|
||||
{
|
||||
foreach (var limbType in statusEffect.targetLimbs)
|
||||
{
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
// Target all matching limbs
|
||||
foreach (var limb in ragdoll.Limbs)
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == limbType)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == limbType)
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, character, limb);
|
||||
}
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb) || statusEffect.HasTargetType(StatusEffect.TargetType.Character) || statusEffect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
// Target just the first matching limb
|
||||
Limb limb = ragdoll.GetLimb(limbType);
|
||||
if (limb != null)
|
||||
{
|
||||
// Target just the first matching limb
|
||||
Limb limb = ragdoll.GetLimb(limbType);
|
||||
statusEffect.Apply(actionType, deltaTime, character, limb);
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb))
|
||||
{
|
||||
// Target just the last matching limb
|
||||
Limb limb = ragdoll.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
|
||||
if (limb != null)
|
||||
{
|
||||
// Target just the last matching limb
|
||||
Limb limb = ragdoll.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden);
|
||||
statusEffect.Apply(actionType, deltaTime, character, limb);
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
// Target all limbs
|
||||
foreach (var limb in ragdoll.Limbs)
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, character, this, WorldPosition);
|
||||
if (limb.IsSevered) { continue; }
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, character, limb);
|
||||
}
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, character, character, WorldPosition);
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
ApplyToLimb(actionType, deltaTime, statusEffect, character, limb: this);
|
||||
}
|
||||
}
|
||||
static void ApplyToLimb(ActionType actionType, float deltaTime, StatusEffect statusEffect, Character character, Limb limb)
|
||||
{
|
||||
statusEffect.sourceBody = limb.body;
|
||||
statusEffect.Apply(actionType, deltaTime, entity: character, target: limb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,14 @@ namespace Barotrauma
|
||||
|
||||
protected override string GetName() => "Character Config File";
|
||||
|
||||
public override ContentXElement MainElement => base.MainElement.IsOverride() ? base.MainElement.FirstElement() : base.MainElement;
|
||||
public override ContentXElement MainElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (base.MainElement == null) { return null; }
|
||||
return base.MainElement.IsOverride() ? base.MainElement.FirstElement() : base.MainElement;
|
||||
}
|
||||
}
|
||||
|
||||
public static XElement CreateVariantXml(XElement variantXML, XElement baseXML)
|
||||
{
|
||||
@@ -182,6 +189,11 @@ namespace Barotrauma
|
||||
{
|
||||
UpdatePath(File.Path);
|
||||
doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (MainElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Main element null! Failed to load character params.");
|
||||
return false;
|
||||
}
|
||||
Identifier variantOf = MainElement.VariantOf();
|
||||
if (!variantOf.IsEmpty)
|
||||
{
|
||||
@@ -230,6 +242,11 @@ namespace Barotrauma
|
||||
|
||||
protected void CreateSubParams()
|
||||
{
|
||||
if (MainElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Main element null, cannot create sub params!");
|
||||
return;
|
||||
}
|
||||
SubParams.Clear();
|
||||
var healthElement = MainElement.GetChildElement("health");
|
||||
if (healthElement != null)
|
||||
@@ -745,7 +762,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!TryGetTarget(targetCharacter.SpeciesName, out target))
|
||||
{
|
||||
target = targets.FirstOrDefault(t => string.Equals(t.Tag, targetCharacter.Params.Group.ToString(), StringComparison.OrdinalIgnoreCase));
|
||||
target = targets.FirstOrDefault(t => t.Tag == targetCharacter.Params.Group);
|
||||
}
|
||||
return target != null;
|
||||
}
|
||||
@@ -791,7 +808,7 @@ namespace Barotrauma
|
||||
public override string Name => "Target";
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Can be an item tag, species name or something else. Examples: decoy, provocative, light, dead, human, crawler, wall, nasonov, sonar, door, stronger, weaker, light, human, room..."), Editable()]
|
||||
public string Tag { get; private set; }
|
||||
public Identifier Tag { get; private set; }
|
||||
|
||||
[Serialize(AIState.Idle, IsPropertySaveable.Yes), Editable]
|
||||
public AIState State { get; set; }
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Barotrauma
|
||||
element ??= MainElement;
|
||||
if (element == null)
|
||||
{
|
||||
DebugConsole.ThrowError("[EditableParams] The XML element is null!");
|
||||
DebugConsole.ThrowError("[EditableParams] The XML element is null! Failed to save the parameters.");
|
||||
return false;
|
||||
}
|
||||
SerializableProperty.SerializeProperties(this, element, true);
|
||||
@@ -82,7 +82,16 @@ namespace Barotrauma
|
||||
{
|
||||
UpdatePath(file);
|
||||
doc = XMLExtensions.TryLoadXml(Path);
|
||||
if (doc == null) { return false; }
|
||||
if (doc == null)
|
||||
{
|
||||
DebugConsole.ThrowError("[EditableParams] The document is null! Failed to load the parameters.");
|
||||
return false;
|
||||
}
|
||||
if (MainElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("[EditableParams] The main element is null! Failed to load the parameters.");
|
||||
return false;
|
||||
}
|
||||
IsLoaded = Deserialize(MainElement);
|
||||
OriginalElement = new XElement(MainElement).FromPackage(MainElement.ContentPackage);
|
||||
return IsLoaded;
|
||||
|
||||
+14
-8
@@ -315,20 +315,26 @@ namespace Barotrauma
|
||||
protected void CreateColliders()
|
||||
{
|
||||
Colliders.Clear();
|
||||
for (int i = 0; i < MainElement.GetChildElements("collider").Count(); i++)
|
||||
if (MainElement?.GetChildElements("collider") is { } colliderElements)
|
||||
{
|
||||
var element = MainElement.GetChildElements("collider").ElementAt(i);
|
||||
string name = i > 0 ? "Secondary Collider" : "Main Collider";
|
||||
Colliders.Add(new ColliderParams(element, this, name));
|
||||
for (int i = 0; i < colliderElements.Count(); i++)
|
||||
{
|
||||
var element = colliderElements.ElementAt(i);
|
||||
string name = i > 0 ? "Secondary Collider" : "Main Collider";
|
||||
Colliders.Add(new ColliderParams(element, this, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void CreateLimbs()
|
||||
{
|
||||
Limbs.Clear();
|
||||
foreach (var element in MainElement.GetChildElements("limb"))
|
||||
if (MainElement?.GetChildElements("limb") is { } childElements)
|
||||
{
|
||||
Limbs.Add(new LimbParams(element, this));
|
||||
foreach (var element in childElements)
|
||||
{
|
||||
Limbs.Add(new LimbParams(element, this));
|
||||
}
|
||||
}
|
||||
Limbs = Limbs.OrderBy(l => l.ID).ToList();
|
||||
}
|
||||
@@ -430,8 +436,8 @@ namespace Barotrauma
|
||||
copy.Serialize();
|
||||
Memento.Store(copy);
|
||||
}
|
||||
public void Undo() => RevertTo(Memento.Undo() as RagdollParams);
|
||||
public void Redo() => RevertTo(Memento.Redo() as RagdollParams);
|
||||
public void Undo() => RevertTo(Memento.Undo());
|
||||
public void Redo() => RevertTo(Memento.Redo());
|
||||
public void ClearHistory() => Memento.Clear();
|
||||
|
||||
private void RevertTo(RagdollParams source)
|
||||
|
||||
+2
-2
@@ -22,13 +22,13 @@ namespace Barotrauma.Abilities
|
||||
private static readonly List<WeaponType> WeaponTypeValues = Enum.GetValues(typeof(WeaponType)).Cast<WeaponType>().ToList();
|
||||
|
||||
private readonly string itemIdentifier;
|
||||
private readonly string[] tags;
|
||||
private readonly Identifier[] tags;
|
||||
private readonly WeaponType weapontype;
|
||||
private readonly bool ignoreNonHarmfulAttacks;
|
||||
public AbilityConditionAttackData(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", string.Empty);
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
tags = conditionElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
|
||||
ignoreNonHarmfulAttacks = conditionElement.GetAttributeBool("ignorenonharmfulattacks", false);
|
||||
|
||||
string weaponTypeStr = conditionElement.GetAttributeString("weapontype", "Any");
|
||||
|
||||
+2
-8
@@ -19,15 +19,9 @@ namespace Barotrauma.Abilities
|
||||
|
||||
foreach (XElement subElement in conditionElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase))
|
||||
if (subElement.NameAsIdentifier() == "conditional")
|
||||
{
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
conditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
conditionals.AddRange(PropertyConditional.FromXElement(subElement));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -41,10 +41,10 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
|
||||
|
||||
foreach (var (factionIdentifier, amount) in mission.ReputationRewards)
|
||||
foreach (var reputationReward in mission.ReputationRewards)
|
||||
{
|
||||
if (amount <= 0) { continue; }
|
||||
if (GetMatchingFaction(factionIdentifier) is { } faction &&
|
||||
if (reputationReward.Amount <= 0) { continue; }
|
||||
if (GetMatchingFaction(reputationReward.FactionIdentifier) is { } faction &&
|
||||
Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
return CheckMissionType();
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionAllyHasTalent : AbilityConditionDataless
|
||||
{
|
||||
private readonly Identifier talentIdentifier;
|
||||
|
||||
public AbilityConditionAllyHasTalent(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
talentIdentifier = conditionElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
}
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
foreach (Character crewCharacter in Character.GetFriendlyCrew(characterTalent.Character))
|
||||
{
|
||||
if (crewCharacter.HasTalent(talentIdentifier)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -5,12 +5,12 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasItem : AbilityConditionDataless
|
||||
{
|
||||
private readonly string[] tags;
|
||||
private readonly Identifier[] tags;
|
||||
readonly bool requireAll;
|
||||
|
||||
public AbilityConditionHasItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>());
|
||||
tags = conditionElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
|
||||
requireAll = conditionElement.GetAttributeBool("requireall", false);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (requireAll)
|
||||
{
|
||||
foreach (string tag in tags)
|
||||
foreach (Identifier tag in tags)
|
||||
{
|
||||
if (character.GetEquippedItem(tag) == null) { return false; }
|
||||
}
|
||||
@@ -31,7 +31,7 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string tag in tags)
|
||||
foreach (Identifier tag in tags)
|
||||
{
|
||||
if (character.GetEquippedItem(tag) != null) { return true; }
|
||||
}
|
||||
|
||||
+4
-4
@@ -5,13 +5,13 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
class AbilityConditionHasStatusTag : AbilityConditionDataless
|
||||
{
|
||||
private readonly string tag;
|
||||
private readonly Identifier tag;
|
||||
|
||||
|
||||
public AbilityConditionHasStatusTag(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
tag = conditionElement.GetAttributeString("tag", "");
|
||||
if (string.IsNullOrEmpty(tag))
|
||||
tag = conditionElement.GetAttributeIdentifier("tag", Identifier.Empty);
|
||||
if (tag.IsEmpty)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.");
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(tag))
|
||||
if (!tag.IsEmpty)
|
||||
{
|
||||
return
|
||||
StatusEffect.DurationList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag)) ||
|
||||
|
||||
+1
-2
@@ -12,8 +12,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override bool MatchesConditionSpecific()
|
||||
{
|
||||
bool result = character.HasTalent(talentIdentifier);
|
||||
return result;
|
||||
return character.HasTalent(talentIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -27,8 +27,8 @@ internal sealed class AbilityConditionHoldingItem : AbilityConditionDataless
|
||||
return false;
|
||||
|
||||
static bool HasItemInHand(Character character, Identifier? tagOrIdentifier) =>
|
||||
character.GetEquippedItem(tagOrIdentifier?.Value, InvSlotType.RightHand) is not null ||
|
||||
character.GetEquippedItem(tagOrIdentifier?.Value, InvSlotType.LeftHand) is not null;
|
||||
character.GetEquippedItem(tagOrIdentifier, InvSlotType.RightHand) is not null ||
|
||||
character.GetEquippedItem(tagOrIdentifier, InvSlotType.LeftHand) is not null;
|
||||
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -37,10 +37,11 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character?.Submarine is null) { return; }
|
||||
if (Character is null) { return; }
|
||||
|
||||
foreach (Item item in Character.Submarine.GetItems(true))
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine?.TeamID != Character.TeamID) { continue; }
|
||||
if (item.HasTag(tags) || tags.Contains(item.Prefab.Identifier))
|
||||
{
|
||||
item.StatManager.ApplyStat(stat, stackable, value, CharacterTalent);
|
||||
|
||||
+2
-2
@@ -66,12 +66,12 @@
|
||||
{
|
||||
foreach (Character c in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
c?.Info.ChangeSavedStatValue(statType, value, identifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
|
||||
c?.Info?.ChangeSavedStatValue(statType, value, identifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Character?.Info.ChangeSavedStatValue(statType, value, identifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
|
||||
Character?.Info?.ChangeSavedStatValue(statType, value, identifier, removeOnDeath, maxValue: maxValue, setValue: setValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
IEnumerable<Character> enemyCharacters = Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.None);
|
||||
IEnumerable<Character> enemyCharacters = Character.CharacterList.Where(c => !Character.IsFriendly(c));
|
||||
|
||||
int timesGiven = 0;
|
||||
foreach (Character enemyCharacter in enemyCharacters)
|
||||
@@ -27,7 +27,6 @@ namespace Barotrauma.Abilities
|
||||
if (enemyCharacter.Submarine == null || enemyCharacter.Submarine != Submarine.MainSub) { continue; }
|
||||
if (enemyCharacter.IsDead) { continue; }
|
||||
if (!enemyCharacter.LockHands) { continue; }
|
||||
if (timesGiven > max) { continue; }
|
||||
Character.GiveMoney(moneyAmount);
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(moneyAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier.Value);
|
||||
foreach (Character character in Character.GetFriendlyCrew(Character))
|
||||
@@ -35,6 +34,7 @@ namespace Barotrauma.Abilities
|
||||
character.Info?.GiveExperience(experienceAmount);
|
||||
}
|
||||
timesGiven++;
|
||||
if (max > 0 && timesGiven >= max) { break; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-12
@@ -5,32 +5,43 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityRegenerateLoot : CharacterAbility
|
||||
{
|
||||
/// <summary>
|
||||
/// Chance for the loot to be regenerated. We can't use <see cref="AbilityConditionServerRandom"/> for this,
|
||||
/// because it'd allow the player to reopen the container until the ability is executed successfully
|
||||
/// </summary>
|
||||
private readonly float randomChance;
|
||||
|
||||
// separate random chance used for the ability itself to prevent the player
|
||||
// from opening/reopening a container until it spawns loot
|
||||
private readonly float randomChance;
|
||||
|
||||
/// <summary>
|
||||
/// Chance for an individual loot item to be generated.
|
||||
/// </summary>
|
||||
private readonly float randomChancePerItem = 1.0f;
|
||||
|
||||
// not maintained through death, so it's possible for players to respawn and re-loot chests
|
||||
// seems like a minor issue for now
|
||||
private readonly List<Item> openedContainers = new List<Item>();
|
||||
private readonly HashSet<Item> openedContainers = new HashSet<Item>();
|
||||
|
||||
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
randomChance = abilityElement.GetAttributeFloat("randomchance", 1f);
|
||||
randomChance = abilityElement.GetAttributeFloat(nameof(randomChance), 1f);
|
||||
randomChancePerItem = abilityElement.GetAttributeFloat(nameof(randomChancePerItem), 1f);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityItem)?.Item is Item item)
|
||||
{
|
||||
if (openedContainers.Contains(item)) { return; }
|
||||
openedContainers.Add(item);
|
||||
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
|
||||
if ((abilityObject as IAbilityItem)?.Item is not Item item) { return; }
|
||||
if (openedContainers.Contains(item)) { return; }
|
||||
|
||||
if (item.GetComponent<ItemContainer>() is ItemContainer itemContainer)
|
||||
{
|
||||
AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer);
|
||||
}
|
||||
openedContainers.Add(item);
|
||||
if (randomChance < Rand.Range(0f, 1f, Rand.RandSync.Unsynced)) { return; }
|
||||
|
||||
if (item.GetComponent<ItemContainer>() is ItemContainer itemContainer)
|
||||
{
|
||||
AutoItemPlacer.RegenerateLoot(item.Submarine, itemContainer, skipItemProbability: 1.0f - randomChancePerItem);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,10 +5,10 @@ namespace Barotrauma.Abilities
|
||||
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
|
||||
{
|
||||
// this should just be its own class, misleading to inherit here
|
||||
private readonly string tag;
|
||||
private readonly Identifier tag;
|
||||
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
tag = abilityElement.GetAttributeString("tag", "");
|
||||
tag = abilityElement.GetAttributeIdentifier("tag", Identifier.Empty);
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
@@ -37,7 +37,7 @@ namespace Barotrauma.Abilities
|
||||
ApplyEffectSpecific(closestCharacter);
|
||||
}
|
||||
|
||||
static bool SelectedItemHasTag(Character character, string tag) =>
|
||||
static bool SelectedItemHasTag(Character character, Identifier tag) =>
|
||||
(character.SelectedItem != null && character.SelectedItem.HasTag(tag)) ||
|
||||
(character.SelectedSecondaryItem != null && character.SelectedSecondaryItem.HasTag(tag));
|
||||
}
|
||||
|
||||
@@ -133,6 +133,14 @@ namespace Barotrauma
|
||||
|
||||
if (IsTalentLocked(talentIdentifier)) { return false; }
|
||||
|
||||
if (character.Info.GetUnlockedTalentsInTree().Contains(talentIdentifier))
|
||||
{
|
||||
//if the character already has the talent, it must be viable?
|
||||
//needed for backwards compatibility, otherwise if we remove e.g. a tier 1 or tier 2 talent,
|
||||
//all the already-unlocked higher-tier talents will be considered invalid which'll break the talent selection
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var subTree in talentTree!.TalentSubTrees)
|
||||
{
|
||||
if (subTree.AllTalentIdentifiers.Contains(talentIdentifier) && subTree.HasMaxTalents(selectedTalents)) { return false; }
|
||||
@@ -143,11 +151,12 @@ namespace Barotrauma
|
||||
{
|
||||
return !talentOptionStage.HasMaxTalents(selectedTalents) && TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
|
||||
}
|
||||
//if a previous stage hasn't been completed, this talent can't be selected yet
|
||||
bool optionStageCompleted = talentOptionStage.HasEnoughTalents(selectedTalents);
|
||||
if (!optionStageCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user