Faction Test 100.13.0.0
This commit is contained in:
@@ -352,6 +352,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
|
||||
{
|
||||
if (myTeam == otherTeam) { return true; }
|
||||
return myTeam switch
|
||||
{
|
||||
// NPCs are friendly to the same team and the friendly NPCs
|
||||
CharacterTeamType.None or CharacterTeamType.Team1 or CharacterTeamType.Team2 => otherTeam == CharacterTeamType.FriendlyNPC,
|
||||
// Friendly NPCs are friendly to both player teams
|
||||
CharacterTeamType.FriendlyNPC => otherTeam == CharacterTeamType.Team1 || otherTeam == CharacterTeamType.Team2,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(Character me, Character other) => IsOnFriendlyTeam(me.TeamID, other.TeamID);
|
||||
|
||||
public void ReequipUnequipped()
|
||||
{
|
||||
foreach (var item in unequippedItems)
|
||||
|
||||
@@ -354,6 +354,10 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "owner";
|
||||
}
|
||||
else if (targetCharacter.AIController is HumanAIController && !IsOnFriendlyTeam(Character, targetCharacter))
|
||||
{
|
||||
targetingTag = "hostile";
|
||||
}
|
||||
else if (AIParams.TryGetTarget(targetCharacter, out CharacterParams.TargetParams tP))
|
||||
{
|
||||
targetingTag = tP.Tag;
|
||||
@@ -364,7 +368,7 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "husk";
|
||||
}
|
||||
else if (!Character.IsFriendly(targetCharacter))
|
||||
else if (!Character.IsSameSpeciesOrGroup(targetCharacter))
|
||||
{
|
||||
if (enemy.CombatStrength > CombatStrength)
|
||||
{
|
||||
@@ -689,12 +693,9 @@ namespace Barotrauma
|
||||
return a.Damage >= selectedTargetingParams.Threshold;
|
||||
}
|
||||
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
|
||||
//if the attacker has the same targeting tag as the character we're protecting, we can't change the TargetState
|
||||
//otherwise e.g. a pet that's set to follow humans would start attacking all humans (and other pets, since they're considered part of the same group) when a hostile human attacks it
|
||||
//TODO: a way for pets to differentiate hostile and friendly humans?
|
||||
if (attacker?.AiTarget != null && targetCharacter.SpeciesName != GetTargetingTag(attacker.AiTarget) && !attacker.IsFriendly(targetCharacter))
|
||||
if (attacker?.AiTarget != null && !Character.IsSameSpeciesOrGroup(attacker) && !targetCharacter.IsSameSpeciesOrGroup(attacker))
|
||||
{
|
||||
// Attack the character that attacked the target we are protecting
|
||||
// Can't retaliate on characters of same species or group because that would make us hostile to all friendly characters in the same group.
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
State = AIState.Attack;
|
||||
|
||||
@@ -1514,9 +1514,18 @@ namespace Barotrauma
|
||||
startPos.X += MathHelper.Clamp(Character.AnimController.TargetMovement.X, -1.0f, 1.0f);
|
||||
|
||||
//do a raycast upwards to find any walls
|
||||
float minCeilingDist = Character.AnimController.Collider.Height / 2 + Character.AnimController.Collider.Radius + 0.1f;
|
||||
if (!Character.AnimController.TryGetCollider(0, out PhysicsBody mainCollider))
|
||||
{
|
||||
mainCollider = Character.AnimController.Collider;
|
||||
}
|
||||
float margin = 0.1f;
|
||||
if (shouldCrouch)
|
||||
{
|
||||
margin *= 2;
|
||||
}
|
||||
float minCeilingDist = mainCollider.Height / 2 + mainCollider.Radius + margin;
|
||||
|
||||
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return !(fixture.Body.UserData is Submarine); }) != null;
|
||||
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return fixture.Body.UserData is not Submarine; }) != null;
|
||||
}
|
||||
|
||||
public bool AllowCampaignInteraction()
|
||||
@@ -1589,7 +1598,27 @@ namespace Barotrauma
|
||||
(!requireEquipped || character.HasEquippedItem(i)) &&
|
||||
(predicate == null || predicate(i)), recursive, matchingItems);
|
||||
items = matchingItems;
|
||||
return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.OwnInventory == null || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage)));
|
||||
foreach (var item in matchingItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
|
||||
if (containedTag.IsEmpty || item.OwnInventory == null)
|
||||
{
|
||||
//no contained items required, this item's ok
|
||||
return true;
|
||||
}
|
||||
var suitableSlot = item.GetComponent<ItemContainer>().FindSuitableSubContainerIndex(containedTag);
|
||||
if (suitableSlot == null)
|
||||
{
|
||||
//no restrictions on the suitable slot
|
||||
return item.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage);
|
||||
}
|
||||
else
|
||||
{
|
||||
return item.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage && it.ParentInventory.IsInSlot(it, suitableSlot.Value));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void StructureDamaged(Structure structure, float damageAmount, Character character)
|
||||
@@ -2016,11 +2045,9 @@ namespace Barotrauma
|
||||
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
|
||||
{
|
||||
bool sameTeam = me.TeamID == other.TeamID;
|
||||
bool friendlyTeam = IsOnFriendlyTeam(me, other);
|
||||
bool teamGood = sameTeam || friendlyTeam && !onlySameTeam;
|
||||
bool teamGood = sameTeam || !onlySameTeam && IsOnFriendlyTeam(me, other);
|
||||
if (!teamGood) { return false; }
|
||||
bool speciesGood = other.IsPet || other.SpeciesName == me.SpeciesName || CharacterParams.CompareGroup(me.Group, other.Group);
|
||||
if (!speciesGood) { return false; }
|
||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||
if (me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
||||
@@ -2029,30 +2056,14 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!sameTeam && me.TeamID == CharacterTeamType.None && other.IsPet)
|
||||
{
|
||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
|
||||
{
|
||||
if (myTeam == otherTeam) { return true; }
|
||||
|
||||
switch (myTeam)
|
||||
{
|
||||
case CharacterTeamType.None:
|
||||
case CharacterTeamType.Team1:
|
||||
case CharacterTeamType.Team2:
|
||||
// Only friendly to the same team and friendly NPCs
|
||||
return otherTeam == CharacterTeamType.FriendlyNPC;
|
||||
case CharacterTeamType.FriendlyNPC:
|
||||
// Friendly NPCs are friendly to both teams
|
||||
return otherTeam == CharacterTeamType.Team1 || otherTeam == CharacterTeamType.Team2;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsOnFriendlyTeam(Character me, Character other) => IsOnFriendlyTeam(me.TeamID, other.TeamID);
|
||||
|
||||
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
|
||||
|
||||
public static bool IsTrueForAllCrewMembers(Character character, Func<HumanAIController, bool> predicate)
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.GameSession.RoundDuration < 120.0f &&
|
||||
speaker?.CurrentHull != null &&
|
||||
GameMain.GameSession.Map?.CurrentLocation?.Reputation?.Value >= 0.0f &&
|
||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||
{
|
||||
|
||||
+6
-2
@@ -98,7 +98,7 @@ namespace Barotrauma
|
||||
int containedItemCount = 0;
|
||||
foreach (Item it in container.Inventory.AllItems)
|
||||
{
|
||||
if (CheckItem(it))
|
||||
if (CheckItem(it) && IsInTargetSlot(it))
|
||||
{
|
||||
containedItemCount++;
|
||||
}
|
||||
@@ -118,7 +118,11 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
ItemToContain = item ?? character.Inventory.FindItem(i => CheckItem(i) && i.Container != container.Item, recursive: true);
|
||||
ItemToContain = item ?? character.Inventory.FindItem(it =>
|
||||
CheckItem(it) &&
|
||||
//ignore items already in the container, unless we're trying to place to a specific slot, and the item's not in it
|
||||
(it.Container != container.Item || (TargetSlot.HasValue && it.Container.OwnInventory.FindIndex(it) != TargetSlot)),
|
||||
recursive: true);
|
||||
if (ItemToContain != null)
|
||||
{
|
||||
if (!character.CanInteractWith(ItemToContain, checkLinked: false))
|
||||
|
||||
+37
-8
@@ -1,5 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Barotrauma
|
||||
private AIObjectiveGetItem getDivingGear;
|
||||
private AIObjectiveContainItem getOxygen;
|
||||
private Item targetItem;
|
||||
private int? oxygenSourceSlotIndex;
|
||||
|
||||
public const float MIN_OXYGEN = 10;
|
||||
|
||||
@@ -43,12 +44,15 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
targetItem = character.Inventory.FindItemByTag(gearTag, true);
|
||||
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(gearTag, true));
|
||||
if (targetItem == null && gearTag == LIGHT_DIVING_GEAR)
|
||||
{
|
||||
targetItem = character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true);
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true));
|
||||
}
|
||||
if (targetItem == null || !character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) && targetItem.ContainedItems.Any(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 0))
|
||||
if (targetItem == null ||
|
||||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) &&
|
||||
targetItem.ContainedItems.Any(it => IsSuitableContainedOxygenSource(it)))
|
||||
{
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
{
|
||||
@@ -84,7 +88,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float min = GetMinOxygen(character);
|
||||
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => it != null && it.HasTag(OXYGEN_SOURCE) && it.Condition > min))
|
||||
if (targetItem.OwnInventory != null && targetItem.OwnInventory.AllItems.None(it => IsSuitableContainedOxygenSource(it)))
|
||||
{
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
{
|
||||
@@ -93,7 +97,7 @@ namespace Barotrauma
|
||||
if (HumanAIController.HasItem(character, OXYGEN_SOURCE, 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).Count == 1)
|
||||
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min, recursive: true).Count == 1)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoglastoxygentank").Value, null, 0.0f, "dialoglastoxygentank".ToIdentifier(), 30.0f);
|
||||
}
|
||||
@@ -109,7 +113,8 @@ namespace Barotrauma
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
ConditionLevel = MIN_OXYGEN,
|
||||
RemoveExistingWhenNecessary = true
|
||||
RemoveExistingWhenNecessary = true,
|
||||
TargetSlot = oxygenSourceSlotIndex
|
||||
};
|
||||
if (container.HasSubContainers)
|
||||
{
|
||||
@@ -167,12 +172,36 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSuitableContainedOxygenSource(Item item)
|
||||
{
|
||||
return
|
||||
item != null &&
|
||||
item.HasTag(OXYGEN_SOURCE) &&
|
||||
item.Condition > 0 &&
|
||||
(oxygenSourceSlotIndex == null || item.ParentInventory.IsInSlot(item, oxygenSourceSlotIndex.Value));
|
||||
}
|
||||
|
||||
private void TrySetTargetItem(Item item)
|
||||
{
|
||||
if (targetItem == item) { return; }
|
||||
targetItem = item;
|
||||
if (targetItem != null)
|
||||
{
|
||||
oxygenSourceSlotIndex = targetItem.GetComponent<ItemContainer>()?.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
|
||||
}
|
||||
else
|
||||
{
|
||||
oxygenSourceSlotIndex = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
getDivingGear = null;
|
||||
getOxygen = null;
|
||||
targetItem = null;
|
||||
oxygenSourceSlotIndex = null;
|
||||
}
|
||||
|
||||
public static float GetMinOxygen(Character character)
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ namespace Barotrauma
|
||||
Priority = 100;
|
||||
}
|
||||
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
|
||||
character.Submarine != null && !HumanAIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
|
||||
character.Submarine != null && !AIController.IsOnFriendlyTeam(character.TeamID, character.Submarine.TeamID))
|
||||
{
|
||||
// Ordered to follow, hold position, or return back to main sub inside a hostile sub
|
||||
// -> ignore find safety unless we need to find a diving gear
|
||||
|
||||
+1
-1
@@ -413,7 +413,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
|
||||
@@ -412,7 +412,8 @@ namespace Barotrauma
|
||||
private int CalculateCellCount(int minValue, int maxValue)
|
||||
{
|
||||
if (maxValue == 0) { return 0; }
|
||||
float t = MathUtils.InverseLerp(0, 100, Level.Loaded.Difficulty * Config.AgentSpawnCountDifficultyMultiplier);
|
||||
float difficulty = Level.Loaded?.Difficulty ?? 0.0f;
|
||||
float t = MathUtils.InverseLerp(0, 100, difficulty * Config.AgentSpawnCountDifficultyMultiplier);
|
||||
return (int)Math.Round(MathHelper.Lerp(minValue, maxValue, t));
|
||||
}
|
||||
|
||||
@@ -422,7 +423,8 @@ namespace Barotrauma
|
||||
float delay = Config.AgentSpawnDelay;
|
||||
float min = delay;
|
||||
float max = delay * 6;
|
||||
float t = Level.Loaded.Difficulty * Config.AgentSpawnDelayDifficultyMultiplier * Rand.Range(1 - randomFactor, 1 + randomFactor);
|
||||
float difficulty = Level.Loaded?.Difficulty ?? 0.0f;
|
||||
float t = difficulty * Config.AgentSpawnDelayDifficultyMultiplier * Rand.Range(1 - randomFactor, 1 + randomFactor);
|
||||
return MathHelper.Lerp(max, min, MathUtils.InverseLerp(0, 100, t));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
public bool IsAiming => wasAiming;
|
||||
public bool IsAimingMelee => wasAimingMelee;
|
||||
|
||||
protected bool Aiming => aiming || aimingMelee || LockFlippingUntil > Timing.TotalTime && character.IsKeyDown(InputType.Aim);
|
||||
protected bool Aiming => aiming || aimingMelee || FlipLockTime > Timing.TotalTime && character.IsKeyDown(InputType.Aim);
|
||||
|
||||
public float ArmLength => upperArmLength + forearmLength;
|
||||
|
||||
@@ -278,7 +278,11 @@ namespace Barotrauma
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
public bool IsAboveFloor => GetHeightFromFloor() > -0.1f;
|
||||
|
||||
public float LockFlippingUntil;
|
||||
public float FlipLockTime { get; private set; }
|
||||
public void LockFlipping(float time = 0.2f)
|
||||
{
|
||||
FlipLockTime = (float)Timing.TotalTime + time;
|
||||
}
|
||||
|
||||
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
|
||||
@@ -1023,7 +1023,7 @@ namespace Barotrauma
|
||||
foreach (Limb l in Limbs)
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
if (!l.DoesFlip) { continue; }
|
||||
if (!l.DoesFlip) { continue; }
|
||||
if (RagdollParams.IsSpritesheetOrientationHorizontal)
|
||||
{
|
||||
//horizontally aligned limbs need to be flipped 180 degrees
|
||||
@@ -1043,7 +1043,7 @@ namespace Barotrauma
|
||||
if (l.IsSevered) { continue; }
|
||||
|
||||
float rotation = l.body.Rotation;
|
||||
if (l.DoesFlip)
|
||||
if (l.DoesMirror)
|
||||
{
|
||||
if (RagdollParams.IsSpritesheetOrientationHorizontal)
|
||||
{
|
||||
|
||||
+10
-4
@@ -431,7 +431,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (Timing.TotalTime > LockFlippingUntil && TargetDir != dir && !IsStuck)
|
||||
if (Timing.TotalTime > FlipLockTime && TargetDir != dir && !IsStuck)
|
||||
{
|
||||
Flip();
|
||||
}
|
||||
@@ -1723,7 +1723,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (target.AnimController.Dir > 0 == WorldPosition.X > target.WorldPosition.X)
|
||||
{
|
||||
target.AnimController.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
|
||||
target.AnimController.LockFlipping(0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1822,16 +1822,22 @@ namespace Barotrauma
|
||||
|
||||
public override void Flip()
|
||||
{
|
||||
if (Character == null || Character.Removed)
|
||||
{
|
||||
LogAccessedRemovedCharacterError();
|
||||
return;
|
||||
}
|
||||
|
||||
base.Flip();
|
||||
|
||||
WalkPos = -WalkPos;
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
Vector2 difference;
|
||||
if (torso == null) { return; }
|
||||
|
||||
Matrix torsoTransform = Matrix.CreateRotationZ(torso.Rotation);
|
||||
|
||||
Vector2 difference;
|
||||
foreach (Item heldItem in character.HeldItems)
|
||||
{
|
||||
if (heldItem?.body != null && !heldItem.Removed && heldItem.GetComponent<Holdable>() != null)
|
||||
|
||||
@@ -57,17 +57,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (limbs == null)
|
||||
{
|
||||
if (!accessRemovedCharacterErrorShown)
|
||||
{
|
||||
string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this);
|
||||
errorMsg += '\n' + Environment.StackTrace.CleanupStackTrace();
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Ragdoll.Limbs:AccessRemoved",
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
"Attempted to access a potentially removed ragdoll. Character: " + character.SpeciesName + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
accessRemovedCharacterErrorShown = true;
|
||||
}
|
||||
LogAccessedRemovedCharacterError();
|
||||
return Array.Empty<Limb>();
|
||||
}
|
||||
return limbs;
|
||||
@@ -158,6 +148,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetCollider(int index, out PhysicsBody collider)
|
||||
{
|
||||
collider = null;
|
||||
try
|
||||
{
|
||||
collider = this.collider?[index];
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int ColliderIndex
|
||||
{
|
||||
get
|
||||
@@ -881,7 +885,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb == null || limb.IsSevered || !limb.DoesFlip) { continue; }
|
||||
if (limb == null || limb.IsSevered || !limb.DoesMirror) { continue; }
|
||||
limb.Dir = Dir;
|
||||
limb.MouthPos = new Vector2(-limb.MouthPos.X, limb.MouthPos.Y);
|
||||
limb.MirrorPullJoint();
|
||||
@@ -1436,6 +1440,21 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void LogAccessedRemovedCharacterError()
|
||||
{
|
||||
if (!accessRemovedCharacterErrorShown)
|
||||
{
|
||||
string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this);
|
||||
errorMsg += '\n' + Environment.StackTrace.CleanupStackTrace();
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Ragdoll:AccessRemoved",
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
"Attempted to access a potentially removed ragdoll. Character: " + character.SpeciesName + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
accessRemovedCharacterErrorShown = true;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime, Camera cam);
|
||||
|
||||
partial void Splash(Limb limb, Hull limbHull);
|
||||
|
||||
@@ -498,37 +498,52 @@ namespace Barotrauma
|
||||
DamageParticles(deltaTime, worldPosition);
|
||||
|
||||
var attackResult = target?.AddDamage(attacker, worldPosition, this, deltaTime, playSound) ?? new AttackResult();
|
||||
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
|
||||
var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure;
|
||||
var additionalEffectType = ActionType.OnUse;
|
||||
if (targetCharacter != null && targetCharacter.IsDead)
|
||||
{
|
||||
effectType = ActionType.OnEating;
|
||||
additionalEffectType = ActionType.OnEating;
|
||||
}
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.sourceBody = sourceBody;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This) || effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
// TODO: do we want to apply the effect at the world position or the entity positions in each cases? -> go through also other cases where status effects are applied
|
||||
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity, worldPosition);
|
||||
var t = sourceLimb ?? attacker as ISerializableEntity;
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, attacker, t, worldPosition);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, attacker, t, worldPosition);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Parent))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, attacker, attacker);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, attacker, attacker);
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetCharacter, targetCharacter);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetCharacter, attackResult.HitLimb);
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetCharacter, attackResult.HitLimb);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, targetCharacter, attackResult.HitLimb);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetCharacter, targetCharacter.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
// TODO: do we need the conversion to list here? It generates garbage.
|
||||
var targets = targetCharacter.AnimController.Limbs.Cast<ISerializableEntity>().ToList();
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetCharacter, targets);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, targetCharacter, targets);
|
||||
}
|
||||
}
|
||||
if (target is Entity targetEntity)
|
||||
@@ -538,18 +553,30 @@ namespace Barotrauma
|
||||
{
|
||||
targets.Clear();
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effectType, deltaTime, targetEntity, targets);
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetEntity, targets);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, targetEntity, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetEntity, attacker, worldPosition);
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetEntity, targetEntity as ISerializableEntity, worldPosition);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, targetEntity, targetEntity as ISerializableEntity, worldPosition);
|
||||
}
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(attacker.Inventory.AllItems);
|
||||
effect.Apply(effectType, deltaTime, attacker, targets);
|
||||
if (additionalEffectType != ActionType.OnEating)
|
||||
{
|
||||
effect.Apply(conditionalEffectType, deltaTime, attacker, targets);
|
||||
}
|
||||
effect.Apply(additionalEffectType, deltaTime, attacker, targets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,47 +612,52 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration);
|
||||
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
|
||||
var conditionalEffectType = attackResult.Damage > 0.0f ? ActionType.OnSuccess : ActionType.OnFailure;
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.sourceBody = sourceBody;
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This) || effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
|
||||
effect.Apply(conditionalEffectType, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, attacker, sourceLimb ?? attacker as ISerializableEntity);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Parent))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, attacker, attacker);
|
||||
effect.Apply(conditionalEffectType, deltaTime, attacker, attacker);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, attacker, attacker);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character);
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targetLimb.character);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targetLimb.character);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb);
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targetLimb);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targetLimb);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList());
|
||||
// TODO: do we need the conversion to list here? It generates garbage.
|
||||
var targets = targetLimb.character.AnimController.Limbs.Cast<ISerializableEntity>().ToList();
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, attacker, worldPosition);
|
||||
effect.Apply(conditionalEffectType, deltaTime, targetLimb.character, targets);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(attacker.Inventory.AllItems);
|
||||
effect.Apply(effectType, deltaTime, attacker, targets);
|
||||
effect.Apply(conditionalEffectType, deltaTime, attacker, targets);
|
||||
effect.Apply(ActionType.OnUse, deltaTime, attacker, targets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -365,9 +365,15 @@ namespace Barotrauma
|
||||
public readonly CharacterPrefab Prefab;
|
||||
|
||||
public readonly CharacterParams Params;
|
||||
|
||||
public Identifier SpeciesName => Params?.SpeciesName ?? "null".ToIdentifier();
|
||||
|
||||
public Identifier Group => HumanPrefab is HumanPrefab humanPrefab && !humanPrefab.Group.IsEmpty ? humanPrefab.Group : Params.Group;
|
||||
|
||||
public bool IsHumanoid => Params.Humanoid;
|
||||
|
||||
public bool IsMachine => Params.IsMachine;
|
||||
|
||||
public bool IsHusk => Params.Husk;
|
||||
|
||||
public bool IsMale => info?.IsMale ?? false;
|
||||
@@ -1613,7 +1619,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\".");
|
||||
}
|
||||
else if (humanPrefab.GiveItems(this, Submarine, spawnPoint))
|
||||
else if (humanPrefab.GiveItems(this, spawnPoint?.Submarine ?? Submarine, spawnPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1752,7 +1758,7 @@ namespace Barotrauma
|
||||
float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed);
|
||||
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
|
||||
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
|
||||
SpeedMultiplier = greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier);
|
||||
SpeedMultiplier = Math.Max(0.0f, greatestPositiveSpeedMultiplier - (1f - greatestNegativeSpeedMultiplier));
|
||||
targetMovement *= SpeedMultiplier;
|
||||
// Reset, status effects will set the value before the next update
|
||||
ResetSpeedMultiplier();
|
||||
@@ -3881,8 +3887,8 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Affliction affliction in attackResult.Afflictions)
|
||||
{
|
||||
if (affliction.Strength == 0.0f) continue;
|
||||
sb.Append($" {affliction.Prefab.Name}: {affliction.Strength}");
|
||||
if (Math.Abs(affliction.Strength) <= 0.1f) { continue;}
|
||||
sb.Append($" {affliction.Prefab.Name}: {affliction.Strength.ToString("0.0")}");
|
||||
}
|
||||
}
|
||||
GameServer.Log(sb.ToString(), ServerLog.MessageType.Attack);
|
||||
@@ -4481,7 +4487,10 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
//ensure we apply any pending inventory updates to drop any items that need to be dropped when the character despawns
|
||||
Inventory?.ApplyReceivedState();
|
||||
if (GameMain.Client?.ClientPeer is { IsActive: true })
|
||||
{
|
||||
Inventory?.ApplyReceivedState();
|
||||
}
|
||||
#endif
|
||||
|
||||
base.Remove();
|
||||
@@ -5197,15 +5206,13 @@ namespace Barotrauma
|
||||
|
||||
public void RemoveAbilityResistance(TalentResistanceIdentifier identifier) => abilityResistances.Remove(identifier);
|
||||
|
||||
/// <summary>
|
||||
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
|
||||
/// </summary>
|
||||
public bool IsFriendly(Character other) => IsFriendly(this, other);
|
||||
|
||||
/// <summary>
|
||||
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
|
||||
/// </summary>
|
||||
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || CharacterParams.CompareGroup(me.Group, other.Group);
|
||||
public static bool IsFriendly(Character me, Character other) => AIController.IsOnFriendlyTeam(me, other) && IsSameSpeciesOrGroup(me, other);
|
||||
|
||||
public bool IsSameSpeciesOrGroup(Character other) => IsSameSpeciesOrGroup(this, other);
|
||||
|
||||
public static bool IsSameSpeciesOrGroup(Character me, Character other) => other.SpeciesName == me.SpeciesName || CharacterParams.CompareGroup(me.Group, other.Group);
|
||||
|
||||
public void StopClimbing()
|
||||
{
|
||||
|
||||
@@ -779,9 +779,10 @@ 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();
|
||||
|
||||
Head.SkinColor = infoElement.GetAttributeColor("skincolor", Color.White);
|
||||
Head.HairColor = infoElement.GetAttributeColor("haircolor", Color.White);
|
||||
Head.FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White);
|
||||
//default to transparent color, it's invalid and will be replaced with a random one in CheckColors
|
||||
Head.SkinColor = infoElement.GetAttributeColor("skincolor", Color.Transparent);
|
||||
Head.HairColor = infoElement.GetAttributeColor("haircolor", Color.Transparent);
|
||||
Head.FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.Transparent);
|
||||
CheckColors();
|
||||
|
||||
TryLoadNameAndTitle(npcIdentifier);
|
||||
|
||||
@@ -146,12 +146,6 @@ namespace Barotrauma
|
||||
{
|
||||
return minVitality;
|
||||
}
|
||||
|
||||
if (Character.HasAbilityFlag(AbilityFlags.CanNotDieToAfflictions))
|
||||
{
|
||||
return Math.Max(vitality, MinVitality + 1);
|
||||
}
|
||||
|
||||
return vitality;
|
||||
|
||||
}
|
||||
@@ -587,6 +581,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void KillIfOutOfVitality()
|
||||
{
|
||||
if (Vitality <= MinVitality &&
|
||||
!Character.HasAbilityFlag(AbilityFlags.CanNotDieToAfflictions))
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly static List<Affliction> afflictionsToRemove = new List<Affliction>();
|
||||
private readonly static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate = new List<KeyValuePair<Affliction, LimbHealth>>();
|
||||
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
||||
@@ -611,7 +614,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality) { Kill(); }
|
||||
KillIfOutOfVitality();
|
||||
}
|
||||
|
||||
public float GetLimbDamage(Limb limb, string afflictionType = null)
|
||||
@@ -729,10 +732,7 @@ namespace Barotrauma
|
||||
existingAffliction.Duration = existingAffliction.Prefab.Duration;
|
||||
if (newAffliction.Source != null) { existingAffliction.Source = newAffliction.Source; }
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
KillIfOutOfVitality();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -746,10 +746,7 @@ namespace Barotrauma
|
||||
Character.HealthUpdateInterval = 0.0f;
|
||||
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
KillIfOutOfVitality();
|
||||
#if CLIENT
|
||||
if (OpenHealthWindow != this && limbHealth != null)
|
||||
{
|
||||
@@ -844,11 +841,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
CalculateVitality();
|
||||
|
||||
if (Vitality <= MinVitality)
|
||||
{
|
||||
Kill();
|
||||
}
|
||||
KillIfOutOfVitality();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,7 +872,11 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateOxygen(float deltaTime)
|
||||
{
|
||||
if (!Character.NeedsOxygen) { return; }
|
||||
if (!Character.NeedsOxygen)
|
||||
{
|
||||
oxygenLowAffliction.Strength = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
float oxygenlowResistance = GetResistance(oxygenLowAffliction.Prefab);
|
||||
float prevOxygen = OxygenAmount;
|
||||
@@ -1025,17 +1022,18 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
||||
private List<Affliction> GetAllAfflictions(bool mergeSameAfflictions)
|
||||
private List<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||
{
|
||||
allAfflictions.Clear();
|
||||
if (!mergeSameAfflictions)
|
||||
{
|
||||
allAfflictions.AddRange(afflictions.Keys);
|
||||
allAfflictions.AddRange(predicate == null ? afflictions.Keys : afflictions.Keys.Where(predicate));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Affliction affliction in afflictions.Keys)
|
||||
{
|
||||
if (predicate != null && !predicate(affliction)) { continue; }
|
||||
var existingAffliction = allAfflictions.Find(a => a.Prefab == affliction.Prefab);
|
||||
if (existingAffliction == null)
|
||||
{
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial class Limb : ISerializableEntity, ISpatialEntity
|
||||
{
|
||||
//how long it takes for severed limbs to fade out
|
||||
@@ -215,7 +215,7 @@ namespace Barotrauma
|
||||
|
||||
//the physics body of the limb
|
||||
public PhysicsBody body;
|
||||
|
||||
|
||||
public Vector2 StepOffset => ConvertUnits.ToSimUnits(Params.StepOffset) * ragdoll.RagdollParams.JointScale;
|
||||
|
||||
public Hull Hull;
|
||||
@@ -249,7 +249,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool isSevered;
|
||||
private float severedFadeOutTimer;
|
||||
|
||||
@@ -269,7 +269,7 @@ namespace Barotrauma
|
||||
mouthPos = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public readonly Attack attack;
|
||||
public List<DamageModifier> DamageModifiers { get; private set; } = new List<DamageModifier>();
|
||||
|
||||
@@ -282,39 +282,73 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (character.AnimController.CurrentAnimationParams is GroundedMovementParams)
|
||||
if (character?.AnimController.CurrentAnimationParams is GroundedMovementParams && IsLeg)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.LeftThigh:
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.RightLeg:
|
||||
case LimbType.RightThigh:
|
||||
// Legs always has to flip
|
||||
return true;
|
||||
}
|
||||
// Legs always has to flip when not swimming
|
||||
return true;
|
||||
}
|
||||
return Params.Flip;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoesMirror
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsLeg)
|
||||
{
|
||||
// Legs always has to mirror
|
||||
return true;
|
||||
}
|
||||
return DoesFlip;
|
||||
}
|
||||
}
|
||||
|
||||
public float SteerForce => Params.SteerForce;
|
||||
|
||||
public Vector2 DebugTargetPos;
|
||||
public Vector2 DebugRefPos;
|
||||
|
||||
public bool IsLowerBody =>
|
||||
type == LimbType.LeftLeg ||
|
||||
type == LimbType.RightLeg ||
|
||||
type == LimbType.LeftFoot ||
|
||||
type == LimbType.RightFoot ||
|
||||
type == LimbType.Tail ||
|
||||
type == LimbType.Legs ||
|
||||
type == LimbType.RightThigh ||
|
||||
type == LimbType.LeftThigh ||
|
||||
type == LimbType.Waist;
|
||||
public bool IsLowerBody
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightLeg:
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.Tail:
|
||||
case LimbType.Legs:
|
||||
case LimbType.LeftThigh:
|
||||
case LimbType.RightThigh:
|
||||
case LimbType.Waist:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLeg
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.LeftThigh:
|
||||
case LimbType.RightFoot:
|
||||
case LimbType.RightLeg:
|
||||
case LimbType.RightThigh:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSevered
|
||||
{
|
||||
|
||||
+4
-3
@@ -75,13 +75,14 @@ namespace Barotrauma.Abilities
|
||||
if (wt == WeaponType.Any || !weapontype.HasFlag(wt)) { continue; }
|
||||
switch (wt)
|
||||
{
|
||||
// it is possible that an item that has both a melee and a projectile component will return true
|
||||
// even when not used as a melee/ranged weapon respectively
|
||||
// attackdata should contain data regarding whether the attack is melee or not
|
||||
case WeaponType.Melee:
|
||||
//if the item has an active projectile component (has been fired), don't consider it a melee weapon
|
||||
if (item?.GetComponent<Projectile>() is { IsActive: true }) { continue; }
|
||||
if (item?.GetComponent<MeleeWeapon>() != null) { return true; }
|
||||
break;
|
||||
case WeaponType.Ranged:
|
||||
//if the item has a melee weapon component that's being used now, don't consider it a projectile
|
||||
if (item?.GetComponent<MeleeWeapon>() is { Hitting: true }) { continue; }
|
||||
if (item?.GetComponent<Projectile>() != null) { return true; }
|
||||
break;
|
||||
case WeaponType.HandheldRanged:
|
||||
|
||||
+8
@@ -17,6 +17,14 @@ namespace Barotrauma.Abilities
|
||||
tags = abilityElement.GetAttributeIdentifierImmutableHashSet("tags", ImmutableHashSet<Identifier>.Empty);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (addingFirstTime)
|
||||
{
|
||||
VerifyState(conditionsMatched: true, timeSinceLastUpdate: 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched)
|
||||
|
||||
+5
@@ -13,6 +13,11 @@
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
VerifyState(conditionsMatched: true, timeSinceLastUpdate: 0.0f);
|
||||
}
|
||||
|
||||
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
|
||||
+4
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -17,6 +18,9 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (!addingFirstTime) { return; }
|
||||
|
||||
// do not run client-side in multiplayer
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
|
||||
JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet());
|
||||
if (apprentice is null)
|
||||
{
|
||||
|
||||
+10
@@ -49,6 +49,16 @@ namespace Barotrauma.Abilities
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (abilityEffectType)
|
||||
{
|
||||
case AbilityEffectType.OnDieToCharacter:
|
||||
if (characterAbilities.Any(a => a.RequiresAlive))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in talent {characterTalent}: an ability group has the type {AbilityEffectType.OnDieToCharacter}, but includes abilities that require the character to be alive, meaning they will never execute.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ActivateAbilityGroup(bool addingFirstTime)
|
||||
|
||||
Reference in New Issue
Block a user