Build 0.21.1.0
This commit is contained in:
@@ -351,6 +351,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)
|
||||
|
||||
@@ -355,6 +355,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;
|
||||
@@ -365,7 +369,7 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "husk";
|
||||
}
|
||||
else if (!Character.IsFriendly(targetCharacter))
|
||||
else if (!Character.IsSameSpeciesOrGroup(targetCharacter))
|
||||
{
|
||||
if (enemy.CombatStrength > CombatStrength)
|
||||
{
|
||||
@@ -687,12 +691,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.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.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)
|
||||
|
||||
+1
-1
@@ -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++;
|
||||
}
|
||||
|
||||
+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));
|
||||
|
||||
@@ -371,7 +371,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));
|
||||
}
|
||||
|
||||
@@ -381,7 +382,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)
|
||||
{
|
||||
|
||||
@@ -994,7 +994,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
|
||||
@@ -1014,7 +1014,7 @@ namespace Barotrauma
|
||||
if (l.IsSevered) { continue; }
|
||||
|
||||
float rotation = l.body.Rotation;
|
||||
if (l.DoesFlip)
|
||||
if (l.DoesMirror)
|
||||
{
|
||||
if (RagdollParams.IsSpritesheetOrientationHorizontal)
|
||||
{
|
||||
|
||||
+55
-21
@@ -150,8 +150,10 @@ namespace Barotrauma
|
||||
|
||||
private readonly float movementLerp;
|
||||
|
||||
private float cprAnimTimer;
|
||||
private float cprPump;
|
||||
private float cprAnimTimer,cprPump;
|
||||
|
||||
private float fallingProneAnimTimer;
|
||||
const float FallingProneAnimDuration = 1.0f;
|
||||
|
||||
private bool swimming;
|
||||
//time until the character can switch from walking to swimming or vice versa
|
||||
@@ -268,7 +270,8 @@ namespace Barotrauma
|
||||
if (deathAnimTimer < deathAnimDuration)
|
||||
{
|
||||
deathAnimTimer += deltaTime;
|
||||
UpdateDying(deltaTime);
|
||||
//the force/torque used to move the limbs goes from 1 to 0 during the death anim duration
|
||||
UpdateFallingProne(1.0f - deathAnimTimer / deathAnimDuration);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -278,6 +281,11 @@ namespace Barotrauma
|
||||
|
||||
if (!character.CanMove)
|
||||
{
|
||||
if (fallingProneAnimTimer < FallingProneAnimDuration)
|
||||
{
|
||||
fallingProneAnimTimer += deltaTime;
|
||||
UpdateFallingProne(1.0f);
|
||||
}
|
||||
levitatingCollider = false;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
@@ -291,18 +299,20 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
fallingProneAnimTimer = 0.0f;
|
||||
|
||||
//re-enable collider
|
||||
if (!Collider.Enabled)
|
||||
{
|
||||
var lowestLimb = FindLowestLimb();
|
||||
|
||||
|
||||
Collider.SetTransform(new Vector2(
|
||||
Collider.SimPosition.X,
|
||||
Math.Max(lowestLimb.SimPosition.Y + (Collider.radius + Collider.height / 2), Collider.SimPosition.Y)),
|
||||
Collider.Rotation);
|
||||
|
||||
Collider.FarseerBody.ResetDynamics();
|
||||
Collider.FarseerBody.LinearVelocity = MainLimb.LinearVelocity;
|
||||
Collider.Enabled = true;
|
||||
}
|
||||
|
||||
@@ -431,7 +441,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (Timing.TotalTime > LockFlippingUntil && TargetDir != dir && !IsStuck)
|
||||
if (Timing.TotalTime > FlipLockTime && TargetDir != dir && !IsStuck)
|
||||
{
|
||||
Flip();
|
||||
}
|
||||
@@ -1292,10 +1302,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDying(float deltaTime)
|
||||
void UpdateFallingProne(float strength)
|
||||
{
|
||||
//the force/torque used to move the limbs goes from 1 to 0 during the death anim duration
|
||||
float strength = 1.0f - deathAnimTimer / deathAnimDuration;
|
||||
if (strength <= 0.0f) { return; }
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
@@ -1319,6 +1328,19 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (torso == null) { return; }
|
||||
|
||||
//make the torso tip over
|
||||
//otherwise it tends to just drop straight down, pinning the characters legs in a weird pose
|
||||
if (!InWater)
|
||||
{
|
||||
//prefer tipping over in the same direction the torso is rotating
|
||||
//or moving
|
||||
//or lastly, in the direction it's facing if it's not moving/rotating
|
||||
float fallDirection = Math.Sign(torso.body.AngularVelocity - torso.body.LinearVelocity.X - Dir * 0.01f);
|
||||
float torque = MathF.Cos(torso.Rotation) * fallDirection * 5.0f * strength;
|
||||
torso.body.ApplyTorque(torque * torso.body.Mass);
|
||||
}
|
||||
|
||||
//attempt to make legs stay in a straight line with the torso to prevent the character from doing a split
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
@@ -1503,12 +1525,12 @@ namespace Barotrauma
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftForearm);
|
||||
if (targetLeftHand == null) targetLeftHand = target.AnimController.GetLimb(LimbType.Torso);
|
||||
if (targetLeftHand == null) targetLeftHand = target.AnimController.MainLimb;
|
||||
if (targetLeftHand == null) { targetLeftHand = target.AnimController.GetLimb(LimbType.Torso); }
|
||||
if (targetLeftHand == null) { targetLeftHand = target.AnimController.MainLimb; }
|
||||
|
||||
Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightForearm);
|
||||
if (targetRightHand == null) targetRightHand = target.AnimController.GetLimb(LimbType.Torso);
|
||||
if (targetRightHand == null) targetRightHand = target.AnimController.MainLimb;
|
||||
if (targetRightHand == null) { targetRightHand = target.AnimController.GetLimb(LimbType.Torso); }
|
||||
if (targetRightHand == null) { targetRightHand = target.AnimController.MainLimb; }
|
||||
|
||||
if (!target.AllowInput)
|
||||
{
|
||||
@@ -1644,18 +1666,24 @@ namespace Barotrauma
|
||||
pullLimb.PullJointEnabled = true;
|
||||
if (targetLimb.type == LimbType.Torso || targetLimb == target.AnimController.MainLimb)
|
||||
{
|
||||
Vector2 pullLimbAnchor = targetLimb.SimPosition;
|
||||
pullLimb.PullJointMaxForce = 5000.0f;
|
||||
if (!character.HasAbilityFlag(AbilityFlags.MoveNormallyWhileDragging))
|
||||
{
|
||||
targetMovement *= MathHelper.Clamp(Mass / target.Mass, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY;
|
||||
if (!MathUtils.IsValid(dragDir)) { dragDir = Vector2.UnitY; }
|
||||
|
||||
targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength);
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
float targetDist = Vector2.Distance(targetLimb.SimPosition, shoulderPos);
|
||||
Vector2 dragDir = (targetLimb.SimPosition - shoulderPos) / targetDist;
|
||||
if (!MathUtils.IsValid(dragDir)) { dragDir = -Vector2.UnitY; }
|
||||
if (!InWater)
|
||||
{
|
||||
//lerp the arm downwards when not swimming
|
||||
dragDir = Vector2.Lerp(dragDir, -Vector2.UnitY, 0.5f);
|
||||
}
|
||||
|
||||
Vector2 pullLimbAnchor = shoulderPos + dragDir * Math.Min(targetDist, (upperArmLength + forearmLength) * 2);
|
||||
targetAnchor = shoulderPos + dragDir * (upperArmLength + forearmLength);
|
||||
targetForce = 200.0f;
|
||||
if (target.Submarine != character.Submarine)
|
||||
{
|
||||
@@ -1723,7 +1751,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 +1850,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
|
||||
@@ -873,7 +877,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();
|
||||
@@ -1428,6 +1432,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);
|
||||
|
||||
@@ -492,37 +492,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)
|
||||
@@ -532,18 +547,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,47 +606,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly static List<Character> CharacterList = new List<Character>();
|
||||
|
||||
public const float MaxHighlightDistance = 150.0f;
|
||||
public const float MaxDragDistance = 200.0f;
|
||||
|
||||
partial void UpdateLimbLightSource(Limb limb);
|
||||
|
||||
private bool enabled = true;
|
||||
@@ -354,9 +357,15 @@ namespace Barotrauma
|
||||
public readonly CharacterPrefab Prefab;
|
||||
|
||||
public readonly CharacterParams Params;
|
||||
|
||||
public Identifier SpeciesName => Params?.SpeciesName ?? "null".ToIdentifier();
|
||||
|
||||
public Identifier Group => Params.Group;
|
||||
|
||||
public bool IsHumanoid => Params.Humanoid;
|
||||
|
||||
public bool IsMachine => Params.IsMachine;
|
||||
|
||||
public bool IsHusk => Params.Husk;
|
||||
|
||||
public bool IsMale => info?.IsMale ?? false;
|
||||
@@ -805,6 +814,11 @@ namespace Barotrauma
|
||||
public float HealthPercentage => CharacterHealth.HealthPercentage;
|
||||
public float MaxVitality => CharacterHealth.MaxVitality;
|
||||
public float MaxHealth => MaxVitality;
|
||||
|
||||
/// <summary>
|
||||
/// Was the character in full health at the beginning of the frame?
|
||||
/// </summary>
|
||||
public bool WasFullHealth => CharacterHealth.WasInFullHealth;
|
||||
public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
|
||||
public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached;
|
||||
public float EmpVulnerability => Params.Health.EmpVulnerability;
|
||||
@@ -1741,7 +1755,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();
|
||||
@@ -2201,7 +2215,9 @@ namespace Barotrauma
|
||||
|
||||
if (SelectedCharacter != null)
|
||||
{
|
||||
if (Vector2.DistanceSquared(SelectedCharacter.WorldPosition, WorldPosition) > 90000.0f || !SelectedCharacter.CanBeSelected)
|
||||
if (!SelectedCharacter.CanBeSelected ||
|
||||
(Vector2.DistanceSquared(SelectedCharacter.WorldPosition, WorldPosition) > MaxDragDistance * MaxDragDistance &&
|
||||
SelectedCharacter.GetDistanceToClosestLimb(SimPosition) > ConvertUnits.ToSimUnits(MaxDragDistance)))
|
||||
{
|
||||
DeselectCharacter();
|
||||
}
|
||||
@@ -2494,8 +2510,12 @@ namespace Barotrauma
|
||||
|
||||
if (!skipDistanceCheck)
|
||||
{
|
||||
maxDist = ConvertUnits.ToSimUnits(maxDist);
|
||||
if (Vector2.DistanceSquared(SimPosition, c.SimPosition) > maxDist * maxDist) { return false; }
|
||||
maxDist = Math.Max(ConvertUnits.ToSimUnits(maxDist), c.AnimController.Collider.GetMaxExtent());
|
||||
if (Vector2.DistanceSquared(SimPosition, c.SimPosition) > maxDist * maxDist &&
|
||||
Vector2.DistanceSquared(SimPosition, c.AnimController.MainLimb.SimPosition) > maxDist * maxDist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return checkVisibility ? CanSeeCharacter(c) : true;
|
||||
@@ -3138,56 +3158,57 @@ namespace Barotrauma
|
||||
|
||||
UpdateAIChatMessages(deltaTime);
|
||||
|
||||
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
|
||||
bool allowRagdoll = GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true;
|
||||
bool tooFastToUnragdoll = AnimController.Collider.LinearVelocity.LengthSquared() > 8.0f * 8.0f;
|
||||
bool wasRagdolled = false;
|
||||
bool selfRagdolled = false;
|
||||
|
||||
if (IsForceRagdolled)
|
||||
if (GameMain.NetworkMember?.ServerSettings?.AllowRagdollButton ?? true)
|
||||
{
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
}
|
||||
else if (this != Controlled)
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll);
|
||||
}
|
||||
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
|
||||
else if (allowRagdoll && (!IsRagdolled || !tooFastToUnragdoll))
|
||||
{
|
||||
if (ragdollingLockTimer > 0.0f)
|
||||
bool wasRagdolled = IsRagdolled;
|
||||
if (IsForceRagdolled)
|
||||
{
|
||||
SetInput(InputType.Ragdoll, false, true);
|
||||
ragdollingLockTimer -= deltaTime;
|
||||
IsRagdolled = IsForceRagdolled;
|
||||
}
|
||||
else if (this != Controlled)
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll);
|
||||
}
|
||||
else
|
||||
{
|
||||
wasRagdolled = IsRagdolled;
|
||||
IsRagdolled = selfRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.5f; }
|
||||
bool tooFastToUnragdoll = bodyMovingTooFast(AnimController.Collider) || bodyMovingTooFast(AnimController.MainLimb.body);
|
||||
bool bodyMovingTooFast(PhysicsBody body)
|
||||
{
|
||||
return
|
||||
body.LinearVelocity.LengthSquared() > 8.0f * 8.0f ||
|
||||
//falling down counts as going too fast
|
||||
(!InWater && body.LinearVelocity.Y < -5.0f);
|
||||
}
|
||||
if (ragdollingLockTimer > 0.0f)
|
||||
{
|
||||
ragdollingLockTimer -= deltaTime;
|
||||
}
|
||||
else if (!tooFastToUnragdoll)
|
||||
{
|
||||
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
|
||||
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.2f; }
|
||||
}
|
||||
if (IsRagdolled)
|
||||
{
|
||||
SetInput(InputType.Ragdoll, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasRagdolled && IsRagdolled)
|
||||
{
|
||||
if (selfRagdolled)
|
||||
if (!wasRagdolled && IsRagdolled)
|
||||
{
|
||||
CheckTalents(AbilityEffectType.OnSelfRagdoll);
|
||||
CheckTalents(AbilityEffectType.OnRagdoll);
|
||||
}
|
||||
// currently does not work when you are stunned, like it should
|
||||
CheckTalents(AbilityEffectType.OnRagdoll);
|
||||
}
|
||||
|
||||
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
|
||||
|
||||
//ragdoll button
|
||||
if (IsRagdolled || !CanMove)
|
||||
{
|
||||
if (AnimController is HumanoidAnimController humanAnimController)
|
||||
{
|
||||
humanAnimController.Crouching = false;
|
||||
}
|
||||
if (IsRagdolled) { AnimController.IgnorePlatforms = true; }
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedItem = SelectedSecondaryItem = null;
|
||||
return;
|
||||
@@ -3365,6 +3386,20 @@ namespace Barotrauma
|
||||
return distSqr;
|
||||
}
|
||||
|
||||
public float GetDistanceToClosestLimb(Vector2 simPos)
|
||||
{
|
||||
float closestDist = float.MaxValue;
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
float dist = Vector2.Distance(simPos, limb.SimPosition);
|
||||
dist -= limb.body.GetMaxExtent();
|
||||
closestDist = Math.Min(closestDist, dist);
|
||||
if (closestDist <= 0.0f) { return 0.0f; }
|
||||
}
|
||||
return closestDist;
|
||||
}
|
||||
|
||||
private float despawnTimer;
|
||||
private void UpdateDespawn(float deltaTime, bool ignoreThresholds = false, bool createNetworkEvents = true)
|
||||
{
|
||||
@@ -3885,8 +3920,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);
|
||||
@@ -4484,7 +4519,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();
|
||||
@@ -5200,15 +5238,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 || other.Params.CompareGroup(me.Params.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 || other.Params.CompareGroup(me.Params.Group);
|
||||
|
||||
public void StopClimbing()
|
||||
{
|
||||
|
||||
@@ -778,9 +778,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);
|
||||
|
||||
+12
@@ -602,6 +602,18 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < effects.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < effects.Count; j++)
|
||||
{
|
||||
var a = effects[i];
|
||||
var b = effects[j];
|
||||
if (a.MinStrength < b.MaxStrength && b.MinStrength < a.MaxStrength)
|
||||
{
|
||||
DebugConsole.AddWarning($"Affliction \"{Identifier}\" contains effects with overlapping strength ranges. Only one effect can be active at a time, meaning one of the effects won't work.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearEffects()
|
||||
|
||||
@@ -230,6 +230,11 @@ namespace Barotrauma
|
||||
|
||||
public float StunTimer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Was the character in full health at the beginning of the frame?
|
||||
/// </summary>
|
||||
public bool WasInFullHealth { get; private set; }
|
||||
|
||||
public Affliction PressureAffliction
|
||||
{
|
||||
get { return pressureAffliction; }
|
||||
@@ -772,6 +777,8 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
WasInFullHealth = vitality >= MaxVitality;
|
||||
|
||||
UpdateOxygen(deltaTime);
|
||||
|
||||
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
|
||||
@@ -878,7 +885,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;
|
||||
@@ -980,6 +991,8 @@ namespace Barotrauma
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
Character.Kill(type, affliction);
|
||||
|
||||
WasInFullHealth = false;
|
||||
#if CLIENT
|
||||
DisplayVitalityDelay = 0.0f;
|
||||
DisplayedVitality = Vitality;
|
||||
@@ -1024,17 +1037,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:
|
||||
|
||||
-1
@@ -28,7 +28,6 @@ namespace Barotrauma.Abilities
|
||||
conditionals.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-13
@@ -37,19 +37,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
if (category != MapEntityCategory.None)
|
||||
{
|
||||
if (!itemPrefab.Category.HasFlag(category)) { return false; }
|
||||
}
|
||||
|
||||
if (identifiers.Any())
|
||||
{
|
||||
if (!identifiers.Any(t => itemPrefab.Identifier == t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
|
||||
return MatchesItem(itemPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -57,5 +45,22 @@ namespace Barotrauma.Abilities
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesItem(ItemPrefab itemPrefab)
|
||||
{
|
||||
if (category != MapEntityCategory.None)
|
||||
{
|
||||
if (!itemPrefab.Category.HasFlag(category)) { return false; }
|
||||
}
|
||||
|
||||
if (identifiers.Any())
|
||||
{
|
||||
if (!identifiers.Any(t => itemPrefab.Identifier == t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !tags.Any() || tags.Any(t => itemPrefab.Tags.Any(p => t == p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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)
|
||||
|
||||
+19
-3
@@ -1,19 +1,35 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
internal sealed class CharacterAbilityRemoveRandomIngredient : CharacterAbility
|
||||
{
|
||||
public CharacterAbilityRemoveRandomIngredient(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement) { }
|
||||
private readonly AbilityConditionItem? condition;
|
||||
|
||||
public CharacterAbilityRemoveRandomIngredient(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
var conditionElement = abilityElement.GetChildElement(nameof(AbilityConditionItem));
|
||||
if (conditionElement != null)
|
||||
{
|
||||
condition = new AbilityConditionItem(CharacterTalent, conditionElement);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if (abilityObject is not Fabricator.AbilityFabricationItemIngredients { Items.Count: > 0 } ingredients) { return; }
|
||||
|
||||
int randomIndex = Rand.Int(ingredients.Items.Count, Rand.RandSync.Unsynced);
|
||||
ingredients.Items.RemoveAt(randomIndex);
|
||||
List<Item> applicableIngredients = condition == null ?
|
||||
ingredients.Items.ToList() :
|
||||
ingredients.Items.Where(it => condition.MatchesItem(it.Prefab)).ToList();
|
||||
if (applicableIngredients.None()) { return; }
|
||||
|
||||
ingredients.Items.Remove(applicableIngredients.GetRandom(Rand.RandSync.Unsynced));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -288,9 +288,7 @@ namespace Barotrauma
|
||||
|
||||
if (errorCatcher.Errors.Any())
|
||||
{
|
||||
yield return ContentPackageManager.LoadProgress.Failure(
|
||||
ContentPackageManager.LoadProgress.Error
|
||||
.Reason.ConsoleErrorsThrown);
|
||||
yield return ContentPackageManager.LoadProgress.Failure(errorCatcher.Errors.Select(e => e.Text));
|
||||
yield break;
|
||||
}
|
||||
yield return ContentPackageManager.LoadProgress.Progress((i + indexOffset) / (float)Files.Length);
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Barotrauma
|
||||
public const string RegularPackagesElementName = "regularpackages";
|
||||
public const string RegularPackagesSubElementName = "package";
|
||||
|
||||
public static bool ModsEnabled => GameMain.VanillaContent == null || EnabledPackages.All.Any(p => p.HasMultiplayerSyncedContent && p != GameMain.VanillaContent);
|
||||
|
||||
public static class EnabledPackages
|
||||
{
|
||||
public static CorePackage? Core { get; private set; } = null;
|
||||
@@ -435,22 +437,19 @@ namespace Barotrauma
|
||||
public readonly record struct LoadProgress(Result<float, LoadProgress.Error> Result)
|
||||
{
|
||||
public readonly record struct Error(
|
||||
Error.Reason ErrorReason,
|
||||
Option<Exception> Exception)
|
||||
Either<ImmutableArray<string>, Exception> ErrorsOrException)
|
||||
{
|
||||
public enum Reason { Exception, ConsoleErrorsThrown }
|
||||
|
||||
public Error(Reason reason) : this(reason, Option.None) { }
|
||||
public Error(Exception exception) : this(Reason.Exception, Option.Some(exception)) { }
|
||||
public Error(IEnumerable<string> errorMessages) : this(ErrorsOrException: errorMessages.ToImmutableArray()) { }
|
||||
public Error(Exception exception) : this(ErrorsOrException: exception) { }
|
||||
}
|
||||
|
||||
public static LoadProgress Failure(Exception exception)
|
||||
=> new LoadProgress(
|
||||
Result<float, Error>.Failure(new Error(exception)));
|
||||
|
||||
public static LoadProgress Failure(Error.Reason reason)
|
||||
public static LoadProgress Failure(IEnumerable<string> errorMessages)
|
||||
=> new LoadProgress(
|
||||
Result<float, Error>.Failure(new Error(reason)));
|
||||
Result<float, Error>.Failure(new Error(errorMessages)));
|
||||
|
||||
public static LoadProgress Progress(float value)
|
||||
=> new LoadProgress(
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Barotrauma
|
||||
public T GetAttributeEnum<T>(string key, in T def) where T : struct, Enum => Element.GetAttributeEnum(key, def);
|
||||
public (T1, T2) GetAttributeTuple<T1, T2>(string key, in (T1, T2) def) => Element.GetAttributeTuple(key, def);
|
||||
public (T1, T2)[] GetAttributeTupleArray<T1, T2>(string key, in (T1, T2)[] def) => Element.GetAttributeTupleArray(key, def);
|
||||
public Range<int> GetAttributeRange(string key, in Range<int> def) => Element.GetAttributeRange(key, def);
|
||||
|
||||
public Identifier VariantOf() => Element.VariantOf();
|
||||
|
||||
|
||||
@@ -1312,10 +1312,10 @@ namespace Barotrauma
|
||||
}
|
||||
}, () =>
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
FactionPrefab.Prefabs.Select(f => f.Identifier.Value).ToArray(),
|
||||
GameMain.GameSession?.Campaign.Factions.Select(f => f.Prefab.Identifier.ToString()).ToArray() ?? Array.Empty<string>()
|
||||
return new[]
|
||||
{
|
||||
FactionPrefab.Prefabs.Select(static f => f.Identifier.Value).ToArray(),
|
||||
GameMain.GameSession?.Campaign?.Factions.Select(static f => f.Prefab.Identifier.ToString()).ToArray() ?? Array.Empty<string>()
|
||||
};
|
||||
}, true));
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ namespace Barotrauma
|
||||
OnUseRangedWeapon,
|
||||
OnReduceAffliction,
|
||||
OnAddDamageAffliction,
|
||||
OnSelfRagdoll,
|
||||
OnRagdoll,
|
||||
OnRoundEnd,
|
||||
OnLootCharacter,
|
||||
@@ -168,7 +167,7 @@ namespace Barotrauma
|
||||
PumpSpeed,
|
||||
PumpMaxFlow,
|
||||
ReactorMaxOutput,
|
||||
ReactorFuelEfficiency,
|
||||
ReactorFuelConsumption,
|
||||
DeconstructorSpeed,
|
||||
FabricationSpeed
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma
|
||||
|
||||
bool requiresRescue = element.GetAttributeBool("requirerescue", false);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None, spawnPos, giveTags: true);
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None, spawnPos);
|
||||
|
||||
if (spawnPos is WayPoint wp)
|
||||
{
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Barotrauma
|
||||
List<Submarine> connectedSubs = level.BeaconStation.GetConnectedSubs();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!connectedSubs.Contains(item.Submarine)) { continue; }
|
||||
if (!connectedSubs.Contains(item.Submarine) || item.Submarine?.Info is { IsPlayer: true }) { continue; }
|
||||
if (item.GetComponent<PowerTransfer>() != null ||
|
||||
item.GetComponent<PowerContainer>() != null ||
|
||||
item.GetComponent<Reactor>() != null ||
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Barotrauma
|
||||
{
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value));
|
||||
info?.GiveExperience((int)((experienceGain * experienceGainMultiplier.Value) * experienceGainMultiplierIndividual.Value));
|
||||
}
|
||||
|
||||
// apply money gains afterwards to prevent them from affecting XP gains
|
||||
@@ -545,17 +545,14 @@ namespace Barotrauma
|
||||
return humanPrefab;
|
||||
}
|
||||
|
||||
protected Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.ServerAndClient, bool giveTags = true)
|
||||
protected static Character CreateHuman(HumanPrefab humanPrefab, List<Character> characters, Dictionary<Character, List<Item>> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.ServerAndClient)
|
||||
{
|
||||
var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
|
||||
characterInfo.TeamID = teamType;
|
||||
|
||||
if (positionToStayIn == null)
|
||||
{
|
||||
positionToStayIn =
|
||||
positionToStayIn ??=
|
||||
WayPoint.GetRandom(SpawnType.Human, characterInfo.Job?.Prefab, submarine) ??
|
||||
WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
||||
}
|
||||
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
spawnedCharacter.HumanPrefab = humanPrefab;
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(position.Position.ToVector2())))
|
||||
if (Level.Loaded.IsPositionInsideWall(position.Position.ToVector2()))
|
||||
{
|
||||
removals.Add(position);
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ namespace Barotrauma
|
||||
if (!SendUserStatistics) { return; }
|
||||
if (sentEventIdentifiers.Contains(identifier)) { return; }
|
||||
|
||||
if (GameMain.VanillaContent == null || ContentPackageManager.EnabledPackages.All.Any(p => p.HasMultiplayerSyncedContent && p != GameMain.VanillaContent))
|
||||
if (ContentPackageManager.ModsEnabled)
|
||||
{
|
||||
message = "[MODDED] " + message;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public const int DefaultMaxMissionCount = 2;
|
||||
public const int MaxMissionCountLimit = 10;
|
||||
public const int MaxMissionCountLimit = 3;
|
||||
public const int MinMissionCountLimit = 1;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
@@ -46,15 +46,15 @@ namespace Barotrauma
|
||||
public static void Init()
|
||||
{
|
||||
#if CLIENT
|
||||
Tutorial = new GameModePreset("tutorial".ToIdentifier(), typeof(TutorialMode), true);
|
||||
DevSandbox = new GameModePreset("devsandbox".ToIdentifier(), typeof(GameMode), true);
|
||||
SinglePlayerCampaign = new GameModePreset("singleplayercampaign".ToIdentifier(), typeof(SinglePlayerCampaign), true);
|
||||
TestMode = new GameModePreset("testmode".ToIdentifier(), typeof(TestGameMode), true);
|
||||
Tutorial = new GameModePreset("tutorial".ToIdentifier(), typeof(TutorialMode), isSinglePlayer: true);
|
||||
DevSandbox = new GameModePreset("devsandbox".ToIdentifier(), typeof(GameMode), isSinglePlayer: true);
|
||||
SinglePlayerCampaign = new GameModePreset("singleplayercampaign".ToIdentifier(), typeof(SinglePlayerCampaign), isSinglePlayer: true);
|
||||
TestMode = new GameModePreset("testmode".ToIdentifier(), typeof(TestGameMode), isSinglePlayer: true);
|
||||
#endif
|
||||
Sandbox = new GameModePreset("sandbox".ToIdentifier(), typeof(GameMode), false);
|
||||
Mission = new GameModePreset("mission".ToIdentifier(), typeof(CoOpMode), false);
|
||||
PvP = new GameModePreset("pvp".ToIdentifier(), typeof(PvPMode), false);
|
||||
MultiPlayerCampaign = new GameModePreset("multiplayercampaign".ToIdentifier(), typeof(MultiPlayerCampaign), false, false);
|
||||
Sandbox = new GameModePreset("sandbox".ToIdentifier(), typeof(GameMode), isSinglePlayer: false);
|
||||
Mission = new GameModePreset("mission".ToIdentifier(), typeof(CoOpMode), isSinglePlayer: false);
|
||||
PvP = new GameModePreset("pvp".ToIdentifier(), typeof(PvPMode), isSinglePlayer: false);
|
||||
MultiPlayerCampaign = new GameModePreset("multiplayercampaign".ToIdentifier(), typeof(MultiPlayerCampaign), isSinglePlayer: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,14 +179,41 @@ namespace Barotrauma
|
||||
|
||||
int price = prefab.Price.GetBuyPrice(GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation);
|
||||
int currentLevel = GetUpgradeLevel(prefab, category);
|
||||
int newLevel = currentLevel + 1;
|
||||
|
||||
int maxLevel = prefab.GetMaxLevelForCurrentSub();
|
||||
if (currentLevel + 1 > maxLevel)
|
||||
{
|
||||
DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" over the max level! ({currentLevel + 1} > {maxLevel}). The transaction has been cancelled.");
|
||||
DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" over the max level! ({newLevel} > {maxLevel}). The transaction has been cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool TryTakeResources(Character character)
|
||||
{
|
||||
bool result = prefab.TryTakeResources(character, newLevel);
|
||||
if (!result)
|
||||
{
|
||||
DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" but the player does not have the required resources.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (GameMain.NetworkMember)
|
||||
{
|
||||
case null when Character.Controlled is { } controlled: // singleplayer
|
||||
if (!TryTakeResources(controlled)) { return; }
|
||||
break;
|
||||
case { IsClient: true }:
|
||||
if (!prefab.HasResourcesToUpgrade(Character.Controlled, newLevel)) { return; }
|
||||
break;
|
||||
case { IsServer: true } when client?.Character is { } character:
|
||||
if (!TryTakeResources(character)) { return; }
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" without a player.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (price < 0)
|
||||
{
|
||||
Location? location = Campaign.Map?.CurrentLocation;
|
||||
|
||||
@@ -188,16 +188,26 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private DockingPort FindAdjacentPort()
|
||||
{
|
||||
float closestDist = float.MaxValue;
|
||||
DockingPort closestPort = null;
|
||||
foreach (DockingPort port in list)
|
||||
{
|
||||
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
|
||||
if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > DistanceTolerance.X) { continue; }
|
||||
if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > DistanceTolerance.Y) { continue; }
|
||||
float xDist = Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X);
|
||||
if (xDist > DistanceTolerance.X) { continue; }
|
||||
float yDist = Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
if (yDist > DistanceTolerance.Y) { continue; }
|
||||
|
||||
return port;
|
||||
float dist = xDist + yDist;
|
||||
//disfavor non-interactable ports
|
||||
if (port.item.NonInteractable) { dist *= 2; }
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestPort = port;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return closestPort;
|
||||
}
|
||||
|
||||
private void AttemptDock()
|
||||
|
||||
@@ -359,7 +359,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
lastBrokenTime = Timing.TotalTime;
|
||||
//the door has to be restored to 50% health before collision detection on the body is re-enabled
|
||||
if (item.ConditionPercentage > 50.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
if (item.ConditionPercentage / Math.Max(item.MaxRepairConditionMultiplier, 1.0f) > 50.0f &&
|
||||
(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
{
|
||||
IsBroken = false;
|
||||
}
|
||||
@@ -459,7 +460,10 @@ namespace Barotrauma.Items.Components
|
||||
ce = ce.Next;
|
||||
}
|
||||
}
|
||||
linkedGap.Open = 1.0f;
|
||||
if (linkedGap != null)
|
||||
{
|
||||
linkedGap.Open = 1.0f;
|
||||
}
|
||||
IsOpen = false;
|
||||
#if CLIENT
|
||||
if (convexHull != null) { convexHull.Enabled = false; }
|
||||
|
||||
@@ -366,7 +366,6 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < entitiesInRange.Count; i++)
|
||||
{
|
||||
float dist = float.MaxValue;
|
||||
|
||||
if (entitiesInRange[i] is Structure structure)
|
||||
{
|
||||
if (structure.IsHorizontal)
|
||||
@@ -388,7 +387,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (entitiesInRange[i] is Character character)
|
||||
{
|
||||
dist = MathUtils.LineSegmentToPointDistanceSquared(currPos, nodes[parentNodeIndex].WorldPosition, character.WorldPosition);
|
||||
dist = MathF.Sqrt(MathUtils.LineSegmentToPointDistanceSquared(currPos, nodes[parentNodeIndex].WorldPosition, character.WorldPosition));
|
||||
}
|
||||
|
||||
if (dist < closestDist)
|
||||
|
||||
@@ -49,6 +49,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Disable to make the weapon ignore all hit effects when it collides with walls, doors, or other items.")]
|
||||
public bool HitOnlyCharacters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, IsPropertySaveable.No)]
|
||||
public bool Swing { get; set; }
|
||||
|
||||
@@ -112,7 +119,7 @@ namespace Barotrauma.Items.Components
|
||||
reloadTimer = reload;
|
||||
reloadTimer /= 1f + character.GetStatValue(StatTypes.MeleeAttackSpeed);
|
||||
reloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier);
|
||||
character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer * 0.9f;
|
||||
character.AnimController.LockFlipping();
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
|
||||
@@ -219,7 +226,7 @@ namespace Barotrauma.Items.Components
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos + swingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
|
||||
if (ac.InWater)
|
||||
{
|
||||
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
|
||||
ac.LockFlipping();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -343,33 +350,36 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
hitTargets.Add(targetCharacter);
|
||||
}
|
||||
else if ((f2.Body.UserData as Structure ?? f2.UserData as Structure) is Structure targetStructure)
|
||||
else if (!HitOnlyCharacters)
|
||||
{
|
||||
if (AllowHitMultiple)
|
||||
if ((f2.Body.UserData as Structure ?? f2.UserData as Structure) is Structure targetStructure)
|
||||
{
|
||||
if (hitTargets.Contains(targetStructure)) { return true; }
|
||||
if (AllowHitMultiple)
|
||||
{
|
||||
if (hitTargets.Contains(targetStructure)) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitTargets.Any(t => t is Structure)) { return true; }
|
||||
}
|
||||
hitTargets.Add(targetStructure);
|
||||
}
|
||||
else
|
||||
else if (f2.Body.UserData is Item targetItem)
|
||||
{
|
||||
if (hitTargets.Any(t => t is Structure)) { return true; }
|
||||
if (AllowHitMultiple)
|
||||
{
|
||||
if (hitTargets.Contains(targetItem)) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitTargets.Any(t => t is Item)) { return true; }
|
||||
}
|
||||
hitTargets.Add(targetItem);
|
||||
}
|
||||
hitTargets.Add(targetStructure);
|
||||
}
|
||||
else if (f2.Body.UserData is Item targetItem)
|
||||
{
|
||||
if (AllowHitMultiple)
|
||||
else if (f2.Body.UserData is Holdable holdable && holdable.CanPush)
|
||||
{
|
||||
if (hitTargets.Contains(targetItem)) { return true; }
|
||||
hitTargets.Add(holdable.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitTargets.Any(t => t is Item)) { return true; }
|
||||
}
|
||||
hitTargets.Add(targetItem);
|
||||
}
|
||||
else if (f2.Body.UserData is Holdable holdable && holdable.CanPush)
|
||||
{
|
||||
hitTargets.Add(holdable.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -381,6 +391,7 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private System.Text.StringBuilder serverLogger;
|
||||
private void HandleImpact(Fixture targetFixture)
|
||||
{
|
||||
var target = targetFixture.Body;
|
||||
@@ -398,11 +409,13 @@ namespace Barotrauma.Items.Components
|
||||
Character user = User;
|
||||
Limb targetLimb = target.UserData as Limb;
|
||||
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
|
||||
Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure;
|
||||
Item targetItem = target.UserData as Item;
|
||||
Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
|
||||
if (Attack != null)
|
||||
{
|
||||
Attack.SetUser(user);
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
|
||||
if (targetLimb != null)
|
||||
{
|
||||
if (targetLimb.character.Removed) { return; }
|
||||
@@ -415,12 +428,12 @@ namespace Barotrauma.Items.Components
|
||||
targetCharacter.LastDamageSource = item;
|
||||
Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if ((target.UserData as Structure ?? targetFixture.UserData as Structure) is Structure targetStructure)
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
if (targetStructure.Removed) { return; }
|
||||
Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
{
|
||||
if (targetItem.Removed) { return; }
|
||||
var attackResult = Attack.DoDamage(user, targetItem, item.WorldPosition, 1.0f);
|
||||
@@ -457,27 +470,43 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
conditionalActionType = ActionType.OnFailure;
|
||||
}
|
||||
if (GameMain.NetworkMember is { IsServer: true } server && targetCharacter != null)
|
||||
if (GameMain.NetworkMember is { IsServer: true } server && targetEntity != null)
|
||||
{
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb));
|
||||
#if SERVER
|
||||
if (GameMain.Server != null) //TODO: Log structure hits
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb, targetEntity));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb, targetEntity));
|
||||
serverLogger ??= new System.Text.StringBuilder();
|
||||
serverLogger.Clear();
|
||||
serverLogger.Append($"{picker?.LogName} used {item.Name}");
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + targetCharacter.LogName + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
serverLogger.Append($"({string.Join(", ", item.ContainedItems.Select(i => i?.Name))})");
|
||||
}
|
||||
#endif
|
||||
string targetName;
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
targetName = targetCharacter.LogName;
|
||||
}
|
||||
else if (targetItem != null)
|
||||
{
|
||||
targetName = targetItem.Name;
|
||||
}
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
targetName = targetStructure.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetName = targetEntity.ToString();
|
||||
}
|
||||
serverLogger.Append($" on {targetName}.");
|
||||
#if SERVER
|
||||
Networking.GameServer.Log(serverLogger.ToString(), Networking.ServerLog.MessageType.Attack);
|
||||
#endif
|
||||
}
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
if (targetEntity != null)
|
||||
{
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
|
||||
@@ -213,6 +213,8 @@ namespace Barotrauma.Items.Components
|
||||
baseReloadTime = MathHelper.Lerp(reload, ReloadNoSkill, reloadFailure);
|
||||
}
|
||||
ReloadTimer = baseReloadTime / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
|
||||
ReloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.FiringRateMultiplier);
|
||||
|
||||
currentChargeTime = 0f;
|
||||
|
||||
if (character != null)
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
@@ -185,24 +184,23 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float degreeOfSuccess = character == null ? 0.5f : DegreeOfSuccess(character);
|
||||
|
||||
bool failed = false;
|
||||
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
|
||||
return false;
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (UsableIn == UseEnvironment.None)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
|
||||
return false;
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (item.InWater)
|
||||
{
|
||||
if (UsableIn == UseEnvironment.Air)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
|
||||
return false;
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -210,9 +208,15 @@ namespace Barotrauma.Items.Components
|
||||
if (UsableIn == UseEnvironment.Water)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
|
||||
return false;
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
if (failed)
|
||||
{
|
||||
// Always apply ActionType.OnUse. If doesn't fail, the effect is called later.
|
||||
ApplyStatusEffects(ActionType.OnUse, deltaTime, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 rayStart;
|
||||
Vector2 rayStartWorld;
|
||||
@@ -312,9 +316,12 @@ namespace Barotrauma.Items.Components
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
|
||||
|
||||
//if the item can cut off limbs, activate nearby bodies to allow the raycast to hit them
|
||||
if (statusEffectLists != null && statusEffectLists.ContainsKey(ActionType.OnUse))
|
||||
if (statusEffectLists != null)
|
||||
{
|
||||
if (statusEffectLists[ActionType.OnUse].Any(s => s.SeverLimbsProbability > 0.0f))
|
||||
static bool CanSeverJoints(ActionType type, Dictionary<ActionType, List<StatusEffect>> effectList) =>
|
||||
effectList.TryGetValue(type, out List<StatusEffect> effects) && effects.Any(e => e.SeverLimbsProbability > 0);
|
||||
|
||||
if (CanSeverJoints(ActionType.OnUse, statusEffectLists) || CanSeverJoints(ActionType.OnSuccess, statusEffectLists))
|
||||
{
|
||||
float rangeSqr = ConvertUnits.ToSimUnits(Range);
|
||||
rangeSqr *= rangeSqr;
|
||||
@@ -537,6 +544,7 @@ namespace Barotrauma.Items.Components
|
||||
if (nonFixableEntities.Contains(targetStructure.Prefab.Identifier) || nonFixableEntities.Any(t => targetStructure.Tags.Contains(t))) { return false; }
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, structure: targetStructure);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, structure: targetStructure);
|
||||
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
|
||||
|
||||
float structureFixAmount = StructureFixAmount;
|
||||
@@ -605,6 +613,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, character: targetCharacter, limb: closestLimb);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, character: targetCharacter, limb: closestLimb);
|
||||
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
|
||||
return true;
|
||||
}
|
||||
@@ -621,6 +630,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, character: targetLimb.character, limb: targetLimb);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, character: targetLimb.character, limb: targetLimb);
|
||||
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
|
||||
return true;
|
||||
}
|
||||
@@ -663,6 +673,7 @@ namespace Barotrauma.Items.Components
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem);
|
||||
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, targetItem);
|
||||
|
||||
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
|
||||
{
|
||||
@@ -875,7 +886,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
currentTargets.Add(character);
|
||||
currentTargets.Add(user);
|
||||
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||
}
|
||||
else if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!(GameMain.NetworkMember is { IsClient: true }))
|
||||
{
|
||||
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, useTarget: CurrentThrower, user: CurrentThrower);
|
||||
}
|
||||
throwState = ThrowState.None;
|
||||
}
|
||||
|
||||
@@ -856,7 +856,13 @@ namespace Barotrauma.Items.Components
|
||||
if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; }
|
||||
if (user != null) { effect.SetUser(user); }
|
||||
effect.AfflictionMultiplier = afflictionMultiplier;
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
|
||||
var c = character;
|
||||
if (user != null && effect.HasTargetType(StatusEffect.TargetType.Character) && !effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
// A bit hacky, but fixes MeleeWeapons targeting the use target instead of the attacker. Also applies to Projectiles and Throwables, or other callers that passes the user.
|
||||
c = user;
|
||||
}
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, c, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
|
||||
effect.AfflictionMultiplier = 1.0f;
|
||||
reducesCondition |= effect.ReducesItemCondition();
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ namespace Barotrauma.Items.Components
|
||||
//disable flipping for 0.5 seconds, because flipping the character when it's in a weird pose (e.g. lying in bed) can mess up the ragdoll
|
||||
if (character.AnimController is HumanoidAnimController humanoidAnim)
|
||||
{
|
||||
humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
|
||||
humanoidAnim.LockFlipping(0.5f);
|
||||
}
|
||||
|
||||
if (character.SelectedItem == item) { character.SelectedItem = null; }
|
||||
|
||||
@@ -874,6 +874,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private float GetMaxOutput() => item.StatManager.GetAdjustedValue(ItemTalentStats.ReactorMaxOutput, MaxPowerOutput);
|
||||
private float GetFuelConsumption() => item.StatManager.GetAdjustedValue(ItemTalentStats.ReactorFuelEfficiency, fuelConsumptionRate);
|
||||
private float GetFuelConsumption() => item.StatManager.GetAdjustedValue(ItemTalentStats.ReactorFuelConsumption, fuelConsumptionRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,7 +767,7 @@ namespace Barotrauma.Items.Components
|
||||
limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
|
||||
return false;
|
||||
}
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User))
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -775,7 +775,13 @@ namespace Barotrauma.Items.Components
|
||||
else if (target.Body.UserData is Item item)
|
||||
{
|
||||
if (item.Condition <= 0.0f) { return false; }
|
||||
if (!item.Prefab.DamagedByProjectiles) { return false; }
|
||||
if (!item.Prefab.DamagedByProjectiles)
|
||||
{
|
||||
if (item.GetComponent<Door>() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target.Body.UserData is Holdable { CanPush: false })
|
||||
{
|
||||
@@ -882,7 +888,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User))
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -953,8 +959,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (target.Body.UserData is Limb targetLimb)
|
||||
{
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: User);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: User);
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, useTarget: character, user: User);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, useTarget: character, user: User);
|
||||
var attack = targetLimb.attack;
|
||||
if (attack != null)
|
||||
{
|
||||
@@ -965,14 +971,22 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
|
||||
effect.Apply(effect.type, 1.0f, User, User);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character) || effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||
{
|
||||
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
|
||||
effect.Apply(effect.type, 1.0f, targetLimb.character, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (submarine?.Info == null || level == null || submarine.Info.Type == SubmarineType.Player) { return 0; }
|
||||
|
||||
float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, 1.0f);
|
||||
float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, level.LevelData.Biome.ActualMaxDifficulty / 100.0f);
|
||||
|
||||
if (level.Type == LevelData.LevelType.Outpost &&
|
||||
level.StartLocation?.Type?.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
//no high-quality spawns in friendly outposts
|
||||
difficultyFactor = 0.0f;
|
||||
}
|
||||
|
||||
return ToolBox.SelectWeightedRandom(Enumerable.Range(0, MaxQuality + 1), q => GetCommonness(q, difficultyFactor), randSync);
|
||||
|
||||
static float GetCommonness(int quality, float difficultyFactor)
|
||||
|
||||
@@ -159,9 +159,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
var user = item.GetComponent<Projectile>()?.User;
|
||||
if (source == null || target == null || target.Removed ||
|
||||
(source is Entity sourceEntity && sourceEntity.Removed) ||
|
||||
(source is Limb limb && limb.Removed))
|
||||
(source is Limb limb && limb.Removed) ||
|
||||
(user != null && user.Removed))
|
||||
{
|
||||
ResetSource();
|
||||
target = null;
|
||||
@@ -293,7 +295,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetMass = float.MaxValue;
|
||||
}
|
||||
var user = item.GetComponent<Projectile>()?.User;
|
||||
if (targetMass > TargetMinMass)
|
||||
{
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
@@ -302,7 +303,7 @@ namespace Barotrauma.Items.Components
|
||||
if (sourceBody != null)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null && !(targetBody.UserData is Character))
|
||||
if (targetBody != null && targetBody.UserData is not Character)
|
||||
{
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
|
||||
}
|
||||
|
||||
+18
-13
@@ -94,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
if (isOn == value && IsActive == value) { return; }
|
||||
|
||||
IsActive = isOn = value;
|
||||
SetLightSourceState(value, value ? lightBrightness : 0.0f);
|
||||
SetLightSourceState(value);
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (Light != null)
|
||||
{
|
||||
Light.Color = IsOn ? lightColor.Multiply(currentBrightness) : Color.Transparent;
|
||||
Light.Color = IsOn ? lightColor.Multiply(lightBrightness) : Color.Transparent;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -187,6 +187,8 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
public float TemporaryFlickerTimer;
|
||||
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -205,7 +207,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (base.IsActive == value) { return; }
|
||||
base.IsActive = isOn = value;
|
||||
SetLightSourceState(value, value ? lightBrightness : 0.0f);
|
||||
SetLightSourceState(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +238,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
SetLightSourceState(IsActive, lightBrightness);
|
||||
SetLightSourceState(IsActive);
|
||||
turret = item.GetComponent<Turret>();
|
||||
#if CLIENT
|
||||
if (Screen.Selected.IsEditor)
|
||||
@@ -263,8 +265,7 @@ namespace Barotrauma.Items.Components
|
||||
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
|
||||
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
|
||||
{
|
||||
lightBrightness = 1.0f;
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
SetLightSourceState(true);
|
||||
SetLightSourceTransformProjSpecific();
|
||||
base.IsActive = false;
|
||||
isOn = true;
|
||||
@@ -285,13 +286,15 @@ namespace Barotrauma.Items.Components
|
||||
UpdateAITarget(item.AiTarget);
|
||||
}
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
//something in UpdateOnActiveEffects may deactivate the light -> return so we don't turn it back on
|
||||
if (!IsActive) { return; }
|
||||
|
||||
#if CLIENT
|
||||
Light.ParentSub = item.Submarine;
|
||||
#endif
|
||||
if (item.Container != null && !(item.GetRootInventoryOwner() is Character))
|
||||
if (item.Container != null && item.GetRootInventoryOwner() is not Character)
|
||||
{
|
||||
SetLightSourceState(false, 0.0f);
|
||||
SetLightSourceState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,12 +303,14 @@ namespace Barotrauma.Items.Components
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null && !body.Enabled)
|
||||
{
|
||||
SetLightSourceState(false, 0.0f);
|
||||
SetLightSourceState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
TemporaryFlickerTimer -= deltaTime;
|
||||
|
||||
//currPowerConsumption = powerConsumption;
|
||||
if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage))
|
||||
if (Rand.Range(0.0f, 1.0f) < 0.05f && (Voltage < Rand.Range(0.0f, MinVoltage) || TemporaryFlickerTimer > 0.0f))
|
||||
{
|
||||
#if CLIENT
|
||||
if (Voltage > 0.1f)
|
||||
@@ -325,7 +330,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
SetLightSourceState(false, 0.0f);
|
||||
SetLightSourceState(false);
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
@@ -357,7 +362,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
#if CLIENT
|
||||
SetLightSourceState(Light.Enabled, currentBrightness);
|
||||
SetLightSourceState(Light.Enabled);
|
||||
#endif
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
@@ -375,7 +380,7 @@ namespace Barotrauma.Items.Components
|
||||
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
|
||||
}
|
||||
|
||||
partial void SetLightSourceState(bool enabled, float brightness);
|
||||
partial void SetLightSourceState(bool enabled, float? brightness = null);
|
||||
|
||||
public void SetLightSourceTransform()
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
partial class WireSection
|
||||
public partial class WireSection
|
||||
{
|
||||
private Vector2 start;
|
||||
private Vector2 end;
|
||||
@@ -775,20 +775,25 @@ namespace Barotrauma.Items.Components
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
public static IEnumerable<Vector2> ExtractNodes(XElement element)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
|
||||
string nodeString = componentElement.GetAttributeString("nodes", "");
|
||||
if (nodeString == "") return;
|
||||
string nodeString = element.GetAttributeString("nodes", "");
|
||||
if (nodeString.IsNullOrWhiteSpace()) { yield break; }
|
||||
|
||||
string[] nodeCoords = nodeString.Split(';');
|
||||
for (int i = 0; i < nodeCoords.Length / 2; i++)
|
||||
{
|
||||
float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
|
||||
float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
|
||||
nodes.Add(new Vector2(x, y));
|
||||
float.TryParse(nodeCoords[i * 2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
|
||||
float.TryParse(nodeCoords[i * 2 + 1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
|
||||
yield return new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap);
|
||||
|
||||
nodes.AddRange(ExtractNodes(componentElement));
|
||||
|
||||
Drawable = nodes.Any();
|
||||
}
|
||||
|
||||
@@ -445,7 +445,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
// single charged shot guns will decharge after firing
|
||||
// for cosmetic reasons, this is done by lerping in half the reload time
|
||||
currentChargeTime = Math.Max(0f, MaxChargeTime * (reload / reloadTime - 0.5f));
|
||||
currentChargeTime = reloadTime > 0.0f ?
|
||||
Math.Max(0f, MaxChargeTime * (reload / reloadTime - 0.5f)) :
|
||||
0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -268,6 +268,8 @@ namespace Barotrauma
|
||||
get { return capacity; }
|
||||
}
|
||||
|
||||
public int EmptySlotCount => slots.Count(i => !i.Empty());
|
||||
|
||||
public bool AllowSwappingContainedItems = true;
|
||||
|
||||
public Inventory(Entity owner, int capacity, int slotsPerRow = 5)
|
||||
@@ -887,10 +889,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (recursive)
|
||||
{
|
||||
if (item.OwnInventory != null)
|
||||
{
|
||||
item.OwnInventory.FindAllItems(predicate, recursive: true, list);
|
||||
}
|
||||
item.OwnInventory?.FindAllItems(predicate, recursive: true, list);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace Barotrauma
|
||||
private float sendConditionUpdateTimer;
|
||||
private bool conditionUpdatePending;
|
||||
|
||||
private float prevCondition;
|
||||
private float condition;
|
||||
|
||||
private bool inWater;
|
||||
@@ -1581,7 +1582,7 @@ namespace Barotrauma
|
||||
tags.Add(newTag);
|
||||
}
|
||||
|
||||
public IEnumerable<Identifier> GetTags()
|
||||
public IReadOnlyCollection<Identifier> GetTags()
|
||||
{
|
||||
return tags;
|
||||
}
|
||||
@@ -1743,15 +1744,14 @@ namespace Barotrauma
|
||||
if (Indestructible) { return; }
|
||||
if (InvulnerableToDamage && value <= condition) { return; }
|
||||
|
||||
float prev = condition;
|
||||
bool wasInFullCondition = IsFullCondition;
|
||||
|
||||
condition = MathHelper.Clamp(value, 0.0f, MaxCondition);
|
||||
if (MathUtils.NearlyEqual(prev, condition, epsilon: 0.000001f)) { return; }
|
||||
if (MathUtils.NearlyEqual(prevCondition, condition, epsilon: 0.000001f)) { return; }
|
||||
|
||||
RecalculateConditionValues();
|
||||
|
||||
if (condition == 0.0f && prev > 0.0f)
|
||||
if (condition == 0.0f && prevCondition > 0.0f)
|
||||
{
|
||||
//Flag connections to be updated as device is broken
|
||||
flagChangedConnections(connections);
|
||||
@@ -1765,7 +1765,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
ApplyStatusEffects(ActionType.OnBroken, 1.0f, null);
|
||||
}
|
||||
else if (condition > 0.0f && prev <= 0.0f)
|
||||
else if (condition > 0.0f && prevCondition <= 0.0f)
|
||||
{
|
||||
//Flag connections to be updated as device is now working again
|
||||
flagChangedConnections(connections);
|
||||
@@ -1793,8 +1793,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
LastConditionChange = condition - prev;
|
||||
LastConditionChange = condition - prevCondition;
|
||||
ConditionLastUpdated = Timing.TotalTime;
|
||||
prevCondition = condition;
|
||||
|
||||
static void flagChangedConnections(Dictionary<string, Connection> connections)
|
||||
{
|
||||
@@ -2159,8 +2160,9 @@ namespace Barotrauma
|
||||
var projectile = GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
//ignore character colliders (a projectile only hits limbs)
|
||||
if (f2.CollisionCategories == Physics.CollisionCharacter && f2.Body.UserData is Character) { return false; }
|
||||
// Ignore characters so that the impact sound only plays when the item hits a a wall or a door.
|
||||
// Projectile collisions are handled in Projectile.OnProjectileCollision(), so it should be safe to do this.
|
||||
if (f2.CollisionCategories == Physics.CollisionCharacter) { return false; }
|
||||
if (projectile.IgnoredBodies != null && projectile.IgnoredBodies.Contains(f2.Body)) { return false; }
|
||||
if (projectile.ShouldIgnoreSubmarineCollision(f2, contact)) { return false; }
|
||||
}
|
||||
@@ -2711,7 +2713,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnUse, character);
|
||||
#endif
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb);
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb, useTarget: targetLimb?.character, user: character);
|
||||
|
||||
if (ic.DeleteOnUse) { remove = true; }
|
||||
}
|
||||
@@ -2742,7 +2744,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnSecondaryUse, character);
|
||||
#endif
|
||||
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character);
|
||||
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character: character, user: character);
|
||||
|
||||
if (ic.DeleteOnUse) { remove = true; }
|
||||
}
|
||||
@@ -2781,8 +2783,8 @@ namespace Barotrauma
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
|
||||
ic.ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: user);
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: user);
|
||||
ic.ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, useTarget: targetLimb?.character, user: user);
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, useTarget: targetLimb?.character, user: user);
|
||||
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
@@ -3448,7 +3450,7 @@ namespace Barotrauma
|
||||
item.condition = MathHelper.Clamp(item.condition, 0, item.MaxCondition);
|
||||
}
|
||||
}
|
||||
item.lastSentCondition = item.condition;
|
||||
item.lastSentCondition = item.prevCondition = item.condition;
|
||||
item.RecalculateConditionValues();
|
||||
item.SetActiveSprite();
|
||||
|
||||
@@ -3522,15 +3524,10 @@ namespace Barotrauma
|
||||
upgrade.Save(element);
|
||||
}
|
||||
|
||||
if (condition < MaxCondition)
|
||||
{
|
||||
element.Add(new XAttribute("conditionpercentage", ConditionPercentage.ToString("G", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var conditionAttribute = element.GetAttribute("condition");
|
||||
if (conditionAttribute != null) { conditionAttribute.Remove(); }
|
||||
}
|
||||
element.Add(new XAttribute("conditionpercentage", ConditionPercentage.ToString("G", CultureInfo.InvariantCulture)));
|
||||
|
||||
var conditionAttribute = element.GetAttribute("condition");
|
||||
if (conditionAttribute != null) { conditionAttribute.Remove(); }
|
||||
|
||||
parentElement.Add(element);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
public EventType EventType { get; }
|
||||
}
|
||||
|
||||
public struct ComponentStateEventData : IEventData
|
||||
public readonly struct ComponentStateEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.ComponentState;
|
||||
public readonly ItemComponent Component;
|
||||
|
||||
@@ -568,9 +568,11 @@ namespace Barotrauma
|
||||
|
||||
public ImmutableDictionary<Identifier, FixedQuantityResourceInfo> LevelQuantity { get; private set; }
|
||||
|
||||
public bool CanSpriteFlipX { get; private set; }
|
||||
private bool canSpriteFlipX;
|
||||
public override bool CanSpriteFlipX => canSpriteFlipX;
|
||||
|
||||
public bool CanSpriteFlipY { get; private set; }
|
||||
private bool canSpriteFlipY;
|
||||
public override bool CanSpriteFlipY => canSpriteFlipY;
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be chosen as extra cargo in multiplayer. If not set, the item is available if it can be bought from outposts in the campaign.
|
||||
@@ -884,8 +886,8 @@ namespace Barotrauma
|
||||
case "sprite":
|
||||
string spriteFolder = GetTexturePath(subElement, variantOf);
|
||||
|
||||
CanSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
|
||||
CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
|
||||
canSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
|
||||
canSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
|
||||
|
||||
sprite = new Sprite(subElement, spriteFolder, lazyLoad: true);
|
||||
if (subElement.GetAttribute("sourcerect") == null &&
|
||||
|
||||
@@ -188,6 +188,12 @@ namespace Barotrauma
|
||||
item.Condition -= item.MaxCondition * EmpStrength * distFactor;
|
||||
}
|
||||
|
||||
var lightComponent = item.GetComponent<LightComponent>();
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor, 10.0f);
|
||||
}
|
||||
|
||||
//discharge batteries
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
if (powerContainer != null)
|
||||
|
||||
@@ -860,6 +860,7 @@ namespace Barotrauma
|
||||
(endPath != null && GetDistToTunnel(cell.Center, endPath) < minMainPathWidth) ||
|
||||
(endHole != null && GetDistToTunnel(cell.Center, endHole) < minMainPathWidth)) { continue; }
|
||||
if (cell.Edges.Any(e => e.AdjacentCell(cell)?.CellType != CellType.Path || e.NextToCave)) { continue; }
|
||||
if (PositionsOfInterest.Any(p => cell.IsPointInside(p.Position.ToVector2()))) { continue; }
|
||||
potentialIslands.Add(cell);
|
||||
}
|
||||
for (int i = 0; i < GenerationParams.IslandCount; i++)
|
||||
@@ -1099,6 +1100,7 @@ namespace Barotrauma
|
||||
caveCells.AddRange(cave.Tunnels.SelectMany(t => t.Cells));
|
||||
foreach (var caveCell in caveCells)
|
||||
{
|
||||
if (PositionsOfInterest.Any(p => caveCell.IsPointInside(p.Position.ToVector2()))) { continue; }
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < destructibleWallRatio * cave.CaveGenerationParams.DestructibleWallRatio)
|
||||
{
|
||||
var chunk = CreateIceChunk(caveCell.Edges, caveCell.Center, health: 50.0f);
|
||||
@@ -3176,7 +3178,7 @@ namespace Barotrauma
|
||||
TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos, filter);
|
||||
|
||||
Vector2 offset = Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.ServerAndClient), Rand.RandSync.ServerAndClient);
|
||||
if (!cells.Any(c => c.IsPointInside(startPos + offset)))
|
||||
if (!IsPositionInsideWall(startPos + offset))
|
||||
{
|
||||
startPos += offset;
|
||||
}
|
||||
@@ -3232,10 +3234,9 @@ namespace Barotrauma
|
||||
{
|
||||
suitablePositions.RemoveAll(p => !filter(p));
|
||||
}
|
||||
//avoid floating ice chunks on the main path
|
||||
if (positionType.HasFlag(PositionType.MainPath) || positionType.HasFlag(PositionType.SidePath))
|
||||
{
|
||||
suitablePositions.RemoveAll(p => ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(p.Position.ToVector2()))));
|
||||
suitablePositions.RemoveAll(p => IsPositionInsideWall(p.Position.ToVector2()));
|
||||
}
|
||||
if (!suitablePositions.Any())
|
||||
{
|
||||
@@ -3288,10 +3289,16 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position;
|
||||
position = farEnoughPositions[Rand.Int(farEnoughPositions.Count, useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced)].Position;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsPositionInsideWall(Vector2 worldPosition)
|
||||
{
|
||||
var closestCell = GetClosestCell(worldPosition);
|
||||
return closestCell != null && closestCell.IsPointInside(worldPosition);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
LevelObjectManager.Update(deltaTime);
|
||||
@@ -3334,14 +3341,13 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 GetBottomPosition(float xPosition)
|
||||
{
|
||||
int index = (int)Math.Floor(xPosition / Size.X * (bottomPositions.Count - 1));
|
||||
float interval = Size.X / (bottomPositions.Count - 1);
|
||||
|
||||
int index = (int)Math.Floor(xPosition / interval);
|
||||
if (index < 0 || index >= bottomPositions.Count - 1) { return new Vector2(xPosition, BottomPos); }
|
||||
|
||||
float t = (xPosition - bottomPositions[index].X) / (bottomPositions[index + 1].X - bottomPositions[index].X);
|
||||
//t can go slightly outside the 0-1 due to rounding, safe to ignore
|
||||
Debug.Assert(t <= 1.001f && t >= -0.001f);
|
||||
float t = (xPosition - bottomPositions[index].X) / interval;
|
||||
t = MathHelper.Clamp(t, 0.0f, 1.0f);
|
||||
|
||||
float yPos = MathHelper.Lerp(bottomPositions[index].Y, bottomPositions[index + 1].Y, t);
|
||||
|
||||
return new Vector2(xPosition, yPos);
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
int startLocationindex = element.GetAttributeInt("startlocation", -1);
|
||||
if (startLocationindex > 0 && startLocationindex < Locations.Count)
|
||||
if (startLocationindex >= 0 && startLocationindex < Locations.Count)
|
||||
{
|
||||
StartLocation = Locations[startLocationindex];
|
||||
}
|
||||
@@ -188,7 +188,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
int endLocationindex = element.GetAttributeInt("endlocation", -1);
|
||||
if (endLocationindex > 0 && endLocationindex < Locations.Count)
|
||||
if (endLocationindex >= 0 && endLocationindex < Locations.Count)
|
||||
{
|
||||
EndLocation = Locations[endLocationindex];
|
||||
}
|
||||
|
||||
@@ -174,6 +174,9 @@ namespace Barotrauma
|
||||
|
||||
public abstract Sprite Sprite { get; }
|
||||
|
||||
public virtual bool CanSpriteFlipX { get; } = false;
|
||||
public virtual bool CanSpriteFlipY { get; } = false;
|
||||
|
||||
public abstract string OriginalName { get; }
|
||||
|
||||
public abstract LocalizedString Name { get; }
|
||||
|
||||
@@ -124,9 +124,18 @@ namespace Barotrauma
|
||||
int eventCount = GameMain.Server.EntityEventManager.Events.Count();
|
||||
int uniqueEventCount = GameMain.Server.EntityEventManager.UniqueEvents.Count();
|
||||
#endif
|
||||
List<MapEntity> entities = MapEntity.mapEntityList.FindAll(e => e.Submarine == sub);
|
||||
HashSet<Submarine> connectedSubs = new HashSet<Submarine>() { sub };
|
||||
foreach (Submarine otherSub in Submarine.Loaded)
|
||||
{
|
||||
//remove linked subs too
|
||||
if (otherSub.Submarine == sub) { connectedSubs.Add(otherSub); }
|
||||
}
|
||||
List<MapEntity> entities = MapEntity.mapEntityList.FindAll(e => connectedSubs.Contains(e.Submarine));
|
||||
entities.ForEach(e => e.Remove());
|
||||
sub.Remove();
|
||||
foreach (Submarine otherSub in connectedSubs)
|
||||
{
|
||||
otherSub.Remove();
|
||||
}
|
||||
#if SERVER
|
||||
//remove any events created during the removal of the entities
|
||||
GameMain.Server.EntityEventManager.Events.RemoveRange(eventCount, GameMain.Server.EntityEventManager.Events.Count - eventCount);
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly ContentXElement ConfigElement;
|
||||
|
||||
public readonly bool CanSpriteFlipX;
|
||||
public readonly bool CanSpriteFlipY;
|
||||
public override bool CanSpriteFlipX { get; }
|
||||
public override bool CanSpriteFlipY { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If null, the orientation is determined automatically based on the dimensions of the structure instances
|
||||
|
||||
@@ -1310,7 +1310,7 @@ namespace Barotrauma
|
||||
return new Rectangle((int)bounds.X, (int)bounds.Y, (int)(bounds.Z - bounds.X), (int)(bounds.Y - bounds.W));
|
||||
}
|
||||
|
||||
public Submarine(SubmarineInfo info, bool showWarningMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID)
|
||||
public Submarine(SubmarineInfo info, bool showErrorMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID)
|
||||
{
|
||||
upgradeEventIdentifier = new Identifier($"Submarine{ID}");
|
||||
Loading = true;
|
||||
@@ -1381,65 +1381,65 @@ namespace Barotrauma
|
||||
center.Y -= center.Y % GridSize.Y;
|
||||
|
||||
RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this));
|
||||
}
|
||||
|
||||
subBody = new SubmarineBody(this, showWarningMessages);
|
||||
Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition);
|
||||
subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f);
|
||||
subBody = new SubmarineBody(this, showErrorMessages);
|
||||
Vector2 pos = ConvertUnits.ToSimUnits(HiddenSubPosition);
|
||||
subBody.Body.FarseerBody.SetTransformIgnoreContacts(ref pos, 0.0f);
|
||||
|
||||
if (info.IsOutpost)
|
||||
if (info.IsOutpost)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
bool indestructible =
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
bool indestructible =
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
|
||||
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
|
||||
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
item.Indestructible = true;
|
||||
}
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
}
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
//prevent rewiring
|
||||
if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable)
|
||||
{
|
||||
//prevent rewiring
|
||||
if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable)
|
||||
{
|
||||
connectionPanel.Locked = true;
|
||||
}
|
||||
connectionPanel.Locked = true;
|
||||
}
|
||||
else if (ic is Holdable holdable && holdable.Attached && item.GetComponent<LevelResource>() == null)
|
||||
{
|
||||
//prevent deattaching items from walls
|
||||
}
|
||||
else if (ic is Holdable holdable && holdable.Attached && item.GetComponent<LevelResource>() == null)
|
||||
{
|
||||
//prevent deattaching items from walls
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode) { continue; }
|
||||
#endif
|
||||
holdable.CanBePicked = false;
|
||||
holdable.CanBeSelected = false;
|
||||
}
|
||||
holdable.CanBePicked = false;
|
||||
holdable.CanBeSelected = false;
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible)
|
||||
{
|
||||
structure.Indestructible = true;
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible)
|
||||
{
|
||||
structure.Indestructible = true;
|
||||
}
|
||||
}
|
||||
else if (info.IsRuin)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
}
|
||||
else if (info.IsRuin)
|
||||
{
|
||||
ShowSonarMarker = false;
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
}
|
||||
|
||||
if (entityGrid != null)
|
||||
@@ -1481,7 +1481,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
//if the sub was made using an older version,
|
||||
//halve the brightness of the lights to make them look (almost) right on the new lighting formula
|
||||
if (showWarningMessages &&
|
||||
if (showErrorMessages &&
|
||||
!string.IsNullOrEmpty(Info.FilePath) &&
|
||||
Screen.Selected != GameMain.SubEditorScreen &&
|
||||
(Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0")))
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Barotrauma
|
||||
get { return submarine; }
|
||||
}
|
||||
|
||||
public SubmarineBody(Submarine sub, bool showWarningMessages = true)
|
||||
public SubmarineBody(Submarine sub, bool showErrorMessages = true)
|
||||
{
|
||||
this.submarine = sub;
|
||||
|
||||
@@ -119,9 +119,9 @@ namespace Barotrauma
|
||||
if (!Hull.HullList.Any(h => h.Submarine == sub))
|
||||
{
|
||||
farseerBody = GameMain.World.CreateRectangle(1.0f, 1.0f, 1.0f);
|
||||
if (showWarningMessages)
|
||||
if (showErrorMessages)
|
||||
{
|
||||
DebugConsole.ThrowError("WARNING: no hulls found, generating a physics body for the submarine failed.");
|
||||
DebugConsole.ThrowError($"No hulls found in the submarine \"{sub.Info.Name}\". Generating a physics body for the submarine failed.");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public T GetVote<T>(VoteType voteType)
|
||||
{
|
||||
return (votes[(int)voteType] is T) ? (T)votes[(int)voteType] : default(T);
|
||||
return (votes[(int)voteType] is T t) ? t : default;
|
||||
}
|
||||
|
||||
public void SetVote(VoteType voteType, object value)
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
interface IServerPositionSync : IServerSerializable
|
||||
{
|
||||
#if SERVER
|
||||
void ServerWritePosition(IWriteMessage msg, Client c);
|
||||
void ServerWritePosition(ReadWriteMessage tempBuffer, Client c);
|
||||
#endif
|
||||
#if CLIENT
|
||||
void ClientReadPosition(IReadMessage msg, float sendingTime);
|
||||
|
||||
@@ -99,6 +99,19 @@ namespace Barotrauma.Networking
|
||||
EntityEventInitial
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
readonly record struct EntityPositionHeader(
|
||||
bool IsItem,
|
||||
UInt32 PrefabUintIdentifier,
|
||||
UInt16 EntityId) : INetSerializableStruct
|
||||
{
|
||||
public static EntityPositionHeader FromEntity(Entity entity)
|
||||
=> new (
|
||||
IsItem: entity is Item,
|
||||
PrefabUintIdentifier: entity is MapEntity me ? me.Prefab.UintIdentifier : 0,
|
||||
EntityId: entity.ID);
|
||||
}
|
||||
|
||||
enum TraitorMessageType
|
||||
{
|
||||
Server,
|
||||
|
||||
@@ -171,6 +171,8 @@ namespace Barotrauma.Networking
|
||||
public bool ShouldCreateAnalyticsEvent
|
||||
=> DisconnectReason is not (
|
||||
DisconnectReason.Disconnected
|
||||
or DisconnectReason.ServerShutdown
|
||||
or DisconnectReason.ServerFull
|
||||
or DisconnectReason.Banned
|
||||
or DisconnectReason.Kicked
|
||||
or DisconnectReason.TooManyFailedLogins
|
||||
|
||||
@@ -1093,7 +1093,7 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = msg.ReadUInt16();
|
||||
if (index < 0 || index >= subList.Count) { continue; }
|
||||
if (index >= subList.Count) { continue; }
|
||||
string submarineName = subList[index].Name;
|
||||
HiddenSubs.Add(submarineName);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 };
|
||||
|
||||
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, IEnumerable<Client> voters)
|
||||
private static IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, IEnumerable<Client> voters)
|
||||
{
|
||||
Dictionary<T, int> voteList = new Dictionary<T, int>();
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
return voteList;
|
||||
}
|
||||
|
||||
public T HighestVoted<T>(VoteType voteType, List<Client> voters)
|
||||
public static T HighestVoted<T>(VoteType voteType, IEnumerable<Client> voters)
|
||||
{
|
||||
if (voteType == VoteType.Sub && !GameMain.NetworkMember.ServerSettings.AllowSubVoting) { return default; }
|
||||
if (voteType == VoteType.Mode && !GameMain.NetworkMember.ServerSettings.AllowModeVoting) { return default; }
|
||||
|
||||
@@ -621,6 +621,15 @@ namespace Barotrauma
|
||||
return stringValue.Split(';').Select(s => ParseTuple<T1, T2>(s, default)).ToArray();
|
||||
}
|
||||
|
||||
public static Range<int> GetAttributeRange(this XElement element, string name, Range<int> defaultValue)
|
||||
{
|
||||
var attribute = element?.GetAttribute(name);
|
||||
if (attribute is null) { return defaultValue; }
|
||||
|
||||
string stringValue = attribute.Value;
|
||||
return string.IsNullOrEmpty(stringValue) ? defaultValue : ParseRange(stringValue);
|
||||
}
|
||||
|
||||
public static string ElementInnerText(this XElement el)
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
@@ -895,6 +904,37 @@ namespace Barotrauma
|
||||
return floatArray;
|
||||
}
|
||||
|
||||
// parse a range string, e.g "1-3" or "3"
|
||||
public static Range<int> ParseRange(string rangeString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rangeString)) { return GetDefault(rangeString); }
|
||||
|
||||
string[] split = rangeString.Split('-');
|
||||
return split.Length switch
|
||||
{
|
||||
1 when TryParseInt(split[0], out int value) => new Range<int>(value, value),
|
||||
2 when TryParseInt(split[0], out int min) && TryParseInt(split[1], out int max) && min < max => new Range<int>(min, max),
|
||||
_ => GetDefault(rangeString)
|
||||
};
|
||||
|
||||
static bool TryParseInt(string value, out int result)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static Range<int> GetDefault(string rangeString)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error parsing range: \"{rangeString}\" (using default value 0-99)");
|
||||
return new Range<int>(0, 99);
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier VariantOf(this XElement element) =>
|
||||
element.GetAttributeIdentifier("inherit", element.GetAttributeIdentifier("variantof", ""));
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace Barotrauma
|
||||
EnableMouseLook = true,
|
||||
ChatOpen = true,
|
||||
CrewMenuOpen = true,
|
||||
EditorDisclaimerShown = false,
|
||||
ShowOffensiveServerPrompt = true,
|
||||
TutorialSkipWarning = true,
|
||||
CorpseDespawnDelay = 600,
|
||||
@@ -132,7 +131,6 @@ namespace Barotrauma
|
||||
public EnemyHealthBarMode ShowEnemyHealthBars;
|
||||
public bool ChatOpen;
|
||||
public bool CrewMenuOpen;
|
||||
public bool EditorDisclaimerShown;
|
||||
public bool ShowOffensiveServerPrompt;
|
||||
public bool TutorialSkipWarning;
|
||||
public int CorpseDespawnDelay;
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace Barotrauma
|
||||
case "targetgrandparent":
|
||||
case "targetcontaineditem":
|
||||
case "skillrequirement":
|
||||
case "targetslot":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
|
||||
@@ -1760,12 +1760,17 @@ namespace Barotrauma
|
||||
|
||||
void SpawnItem(ItemSpawnInfo chosenItemSpawnInfo)
|
||||
{
|
||||
Item parentItem = entity as Item;
|
||||
if (user == null && parentItem != null)
|
||||
{
|
||||
// Set the user for projectiles spawned from status effects (e.g. flak shrapnels)
|
||||
SetUser(parentItem.GetComponent<Projectile>()?.User);
|
||||
}
|
||||
switch (chosenItemSpawnInfo.SpawnPosition)
|
||||
{
|
||||
case ItemSpawnInfo.SpawnPositionType.This:
|
||||
Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Unsynced), onSpawned: newItem =>
|
||||
{
|
||||
Item parentItem = entity as Item;
|
||||
Projectile projectile = newItem.GetComponent<Projectile>();
|
||||
if (entity != null)
|
||||
{
|
||||
|
||||
@@ -85,15 +85,6 @@ namespace Barotrauma.Steam
|
||||
return Steamworks.SteamUGC.NumSubscribedItems;
|
||||
}
|
||||
|
||||
public static PublishedFileId[] GetSubscribedItems()
|
||||
{
|
||||
if (!IsInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return Array.Empty<PublishedFileId>();
|
||||
}
|
||||
return Steamworks.SteamUGC.GetSubscribedItems();
|
||||
}
|
||||
|
||||
public static bool UnlockAchievement(string achievementIdentifier) =>
|
||||
UnlockAchievement(achievementIdentifier.ToIdentifier());
|
||||
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
#nullable enable
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Steamworks.Data;
|
||||
using WorkshopItemSet = System.Collections.Generic.ISet<Steamworks.Ugc.Item>;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
static partial class SteamManager
|
||||
{
|
||||
public const string WorkshopItemPreviewImageFolder = "Workshop";
|
||||
public const string PreviewImageName = "PreviewImage.png";
|
||||
public const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png";
|
||||
|
||||
public static bool TryExtractSteamWorkshopId(this ContentPackage contentPackage, [NotNullWhen(true)]out SteamWorkshopId? workshopId)
|
||||
{
|
||||
workshopId = null;
|
||||
@@ -47,16 +41,22 @@ namespace Barotrauma.Steam
|
||||
private static async Task<WorkshopItemSet> GetWorkshopItems(Steamworks.Ugc.Query query, int? maxPages = null)
|
||||
{
|
||||
if (!IsInitialized) { return new HashSet<Steamworks.Ugc.Item>(); }
|
||||
|
||||
|
||||
await Task.Yield();
|
||||
query = query.WithKeyValueTags(true).WithLongDescription(true);
|
||||
var set = new HashSet<Steamworks.Ugc.Item>(ItemEqualityComparer.Instance);
|
||||
int prevSize = 0;
|
||||
for (int i = 1; maxPages is null || i <= maxPages; i++)
|
||||
for (int i = 1; i <= (maxPages ?? int.MaxValue); i++)
|
||||
{
|
||||
Steamworks.Ugc.ResultPage? page = await query.GetPageAsync(i);
|
||||
if (page is null || !page.Value.Entries.Any()) { break; }
|
||||
set.UnionWith(page.Value.Entries);
|
||||
using Steamworks.Ugc.ResultPage? page = await query.GetPageAsync(i);
|
||||
if (page is not { Entries: var entries }) { break; }
|
||||
|
||||
// This queries the results on the i-th page and stores them,
|
||||
// using page.Entries directly would result in two GetQueryUGCResult calls
|
||||
entries = entries.ToArray();
|
||||
|
||||
if (entries.None()) { break; }
|
||||
|
||||
set.UnionWith(entries);
|
||||
|
||||
if (set.Count == prevSize) { break; }
|
||||
prevSize = set.Count;
|
||||
@@ -66,10 +66,17 @@ namespace Barotrauma.Steam
|
||||
// which can happen on items that are not visible to the currently
|
||||
// logged in player (i.e. private & friends-only items)
|
||||
set.RemoveWhere(it => it.ConsumerApp != AppID);
|
||||
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public static ImmutableHashSet<Steamworks.Data.PublishedFileId> GetSubscribedItemIds()
|
||||
{
|
||||
return IsInitialized
|
||||
? Steamworks.SteamUGC.GetSubscribedItems().ToImmutableHashSet()
|
||||
: ImmutableHashSet<Steamworks.Data.PublishedFileId>.Empty;
|
||||
}
|
||||
|
||||
public static async Task<WorkshopItemSet> GetAllSubscribedItems()
|
||||
{
|
||||
if (!IsInitialized) { return new HashSet<Steamworks.Ugc.Item>(); }
|
||||
@@ -98,14 +105,86 @@ namespace Barotrauma.Steam
|
||||
.WhereUserPublished());
|
||||
}
|
||||
|
||||
public static async Task<Steamworks.Ugc.Item?> GetItem(UInt64 itemId)
|
||||
private static class SingleItemRequestPool
|
||||
{
|
||||
private static readonly object mutex = new();
|
||||
private static readonly TimeSpan delayAfterNewRequest = TimeSpan.FromSeconds(0.5);
|
||||
private static readonly HashSet<UInt64> ids = new();
|
||||
|
||||
private static Task<WorkshopItemSet>? currentBatch = null;
|
||||
|
||||
private static async Task<WorkshopItemSet> PrepareNewBatch()
|
||||
{
|
||||
// Wait for a bunch of requests to be made
|
||||
await Task.Delay(delayAfterNewRequest);
|
||||
|
||||
Task<WorkshopItemSet> queryTask;
|
||||
lock (mutex)
|
||||
{
|
||||
DebugConsole.Log(
|
||||
$"{nameof(SteamManager)}.{nameof(Workshop)}.{nameof(SingleItemRequestPool)}: " +
|
||||
$"Running batch of {ids.Count} requests");
|
||||
|
||||
queryTask = GetWorkshopItems(
|
||||
Steamworks.Ugc.Query.All
|
||||
.WithFileId(
|
||||
ids
|
||||
.Select(id => (Steamworks.Data.PublishedFileId)id)
|
||||
.ToArray()));
|
||||
ids.Clear();
|
||||
|
||||
// Immediately clear the current batch so the next request starts a new one
|
||||
currentBatch = null;
|
||||
}
|
||||
|
||||
return await queryTask;
|
||||
}
|
||||
|
||||
public static async Task<Steamworks.Ugc.Item?> MakeRequest(UInt64 id)
|
||||
{
|
||||
Task<WorkshopItemSet> ourTask;
|
||||
lock (mutex)
|
||||
{
|
||||
ids.Add(id);
|
||||
if (currentBatch is not { IsCompleted: false })
|
||||
{
|
||||
// There is no currently pending batch, start a new one
|
||||
currentBatch = Task.Run(PrepareNewBatch);
|
||||
}
|
||||
ourTask = currentBatch;
|
||||
}
|
||||
|
||||
var items = await ourTask;
|
||||
var result = items.FirstOrNull(it => it.Id == id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a Workshop item's metadata. This is batched to minimize Steamworks API calls.
|
||||
/// The description of the returned item is truncated to save bandwidth.
|
||||
/// </summary>
|
||||
/// <param name="itemId">Workshop Item ID</param>
|
||||
public static Task<Steamworks.Ugc.Item?> GetItem(UInt64 itemId)
|
||||
=> SingleItemRequestPool.MakeRequest(itemId);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a Workshop item's metadata in its own API call instead of batching.
|
||||
/// This minimizes delay but needs to be used with caution to prevent rate limiting.
|
||||
/// </summary>
|
||||
/// <param name="itemId">Workshop Item ID</param>
|
||||
/// <param name="withLongDescription">
|
||||
/// If true, ask for the item's entire description, otherwise it'll be truncated.
|
||||
/// </param>
|
||||
public static async Task<Steamworks.Ugc.Item?> GetItemAsap(UInt64 itemId, bool withLongDescription = false)
|
||||
{
|
||||
if (!IsInitialized) { return null; }
|
||||
|
||||
var items = await GetWorkshopItems(
|
||||
Steamworks.Ugc.Query.All
|
||||
.WithFileId(itemId));
|
||||
return items.Any() ? items.First() : (Steamworks.Ugc.Item?)null;
|
||||
.WithFileId(itemId)
|
||||
.WithLongDescription(withLongDescription));
|
||||
return items.Any() ? items.First() : null;
|
||||
}
|
||||
|
||||
public static async Task ForceRedownload(UInt64 itemId)
|
||||
|
||||
@@ -257,10 +257,59 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class UpgradePrefab : UpgradeContentPrefab
|
||||
internal readonly struct UpgradeResourceCost
|
||||
{
|
||||
public readonly int Amount;
|
||||
private readonly ImmutableArray<Identifier> targetTags;
|
||||
public readonly Range<int> TargetLevels;
|
||||
|
||||
public UpgradeResourceCost(ContentXElement element)
|
||||
{
|
||||
Amount = element.GetAttributeInt("amount", 0);
|
||||
targetTags = element.GetAttributeIdentifierArray("item", Array.Empty<Identifier>())!.ToImmutableArray();
|
||||
TargetLevels = element.GetAttributeRange("levels", new Range<int>(0, 99));
|
||||
}
|
||||
|
||||
public bool AppliesForLevel(int currentLevel) => TargetLevels.Contains(currentLevel);
|
||||
|
||||
public bool AppliesForLevel(Range<int> newLevels) => newLevels.Start <= TargetLevels.End && newLevels.End >= TargetLevels.Start;
|
||||
|
||||
public bool MatchesItem(Item item) => MatchesItem(item.Prefab);
|
||||
|
||||
public bool MatchesItem(ItemPrefab item)
|
||||
{
|
||||
foreach (Identifier tag in targetTags)
|
||||
{
|
||||
if (tag.Equals(item.Identifier) || item.Tags.Contains(tag)) { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct ApplicableResourceCollection
|
||||
{
|
||||
public readonly ImmutableArray<ItemPrefab> MatchingItems;
|
||||
public readonly UpgradeResourceCost Cost;
|
||||
public readonly int Count;
|
||||
|
||||
public ApplicableResourceCollection(IEnumerable<ItemPrefab> matchingItems, int count, UpgradeResourceCost cost)
|
||||
{
|
||||
MatchingItems = matchingItems.ToImmutableArray();
|
||||
Count = count;
|
||||
Cost = cost;
|
||||
}
|
||||
|
||||
public static ApplicableResourceCollection CreateFor(UpgradeResourceCost cost)
|
||||
{
|
||||
return new ApplicableResourceCollection(ItemPrefab.Prefabs.Where(cost.MatchesItem), cost.Amount, cost);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class UpgradePrefab : UpgradeContentPrefab
|
||||
{
|
||||
public static readonly PrefabCollection<UpgradePrefab> Prefabs = new PrefabCollection<UpgradePrefab>(
|
||||
onAdd: (prefab, isOverride) =>
|
||||
onAdd: static (prefab, isOverride) =>
|
||||
{
|
||||
if (!prefab.SuppressWarnings && !isOverride)
|
||||
{
|
||||
@@ -329,6 +378,7 @@ namespace Barotrauma
|
||||
|
||||
private Dictionary<string, string[]> targetProperties { get; }
|
||||
private readonly ImmutableArray<UpgradeMaxLevelMod> MaxLevelsMods;
|
||||
public readonly ImmutableHashSet<UpgradeResourceCost> ResourceCosts;
|
||||
|
||||
public UpgradePrefab(ContentXElement element, UpgradeModulesFile file) : base(element, file)
|
||||
{
|
||||
@@ -341,6 +391,7 @@ namespace Barotrauma
|
||||
|
||||
var targetProperties = new Dictionary<string, string[]>();
|
||||
var maxLevels = new List<UpgradeMaxLevelMod>();
|
||||
var resourceCosts = new HashSet<UpgradeResourceCost>();
|
||||
|
||||
Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", "");
|
||||
if (!nameIdentifier.IsEmpty)
|
||||
@@ -383,6 +434,11 @@ namespace Barotrauma
|
||||
maxLevels.Add(new UpgradeMaxLevelMod(subElement));
|
||||
break;
|
||||
}
|
||||
case "resourcecost":
|
||||
{
|
||||
resourceCosts.Add(new UpgradeResourceCost(subElement));
|
||||
break;
|
||||
}
|
||||
#if CLIENT
|
||||
case "decorativesprite":
|
||||
{
|
||||
@@ -414,6 +470,7 @@ namespace Barotrauma
|
||||
|
||||
this.targetProperties = targetProperties;
|
||||
MaxLevelsMods = maxLevels.ToImmutableArray();
|
||||
ResourceCosts = resourceCosts.ToImmutableHashSet();
|
||||
|
||||
upgradeCategoryIdentifiers = element.GetAttributeIdentifierArray("categories", Array.Empty<Identifier>())?
|
||||
.ToImmutableHashSet() ?? ImmutableHashSet<Identifier>.Empty;
|
||||
@@ -456,6 +513,58 @@ namespace Barotrauma
|
||||
return GetMaxLevel(info) > 0;
|
||||
}
|
||||
|
||||
public bool HasResourcesToUpgrade(Character? character, int currentLevel)
|
||||
{
|
||||
if (character is null) { return false; }
|
||||
if (!ResourceCosts.Any()) { return true; }
|
||||
|
||||
List<Item> allItems = character.Inventory.FindAllItems(recursive: true);
|
||||
return ResourceCosts.Where(cost => cost.AppliesForLevel(currentLevel)).All(cost => cost.Amount <= allItems.Count(cost.MatchesItem));
|
||||
}
|
||||
|
||||
public bool TryTakeResources(Character character, int currentLevel)
|
||||
{
|
||||
IEnumerable<UpgradeResourceCost> costs = ResourceCosts.Where(cost => cost.AppliesForLevel(currentLevel));
|
||||
|
||||
if (!costs.Any()) { return true; }
|
||||
|
||||
List<Item> allItems = character.Inventory.FindAllItems(recursive: true);
|
||||
HashSet<Item> itemsToRemove = new HashSet<Item>();
|
||||
|
||||
foreach (UpgradeResourceCost cost in costs)
|
||||
{
|
||||
int amountNeeded = cost.Amount;
|
||||
foreach (Item item in allItems.Where(cost.MatchesItem))
|
||||
{
|
||||
itemsToRemove.Add(item);
|
||||
amountNeeded--;
|
||||
if (amountNeeded <= 0) { break; }
|
||||
}
|
||||
|
||||
if (amountNeeded > 0) { return false; }
|
||||
}
|
||||
|
||||
foreach (Item item in itemsToRemove)
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
|
||||
if (GameMain.IsMultiplayer) { character.Inventory.CreateNetworkEvent(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ImmutableArray<ApplicableResourceCollection> GetApplicableResources(int level)
|
||||
{
|
||||
var applicableCosts = ResourceCosts.Where(cost => cost.AppliesForLevel(level)).ToImmutableHashSet();
|
||||
|
||||
var costs = applicableCosts.Any()
|
||||
? applicableCosts.Select(ApplicableResourceCollection.CreateFor).ToImmutableArray()
|
||||
: ImmutableArray<ApplicableResourceCollection>.Empty;
|
||||
|
||||
return costs;
|
||||
}
|
||||
|
||||
public bool IsDisallowed(MapEntity item)
|
||||
{
|
||||
return item.DisallowedUpgradeSet.Contains(Identifier)
|
||||
|
||||
@@ -212,8 +212,13 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ShortRepresentation.GetHashCode(StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool operator ==(Md5Hash? a, Md5Hash? b)
|
||||
=> (a is null == b is null) && (a?.Equals(b) ?? true);
|
||||
=> Equals(a, b);
|
||||
|
||||
public static bool operator !=(Md5Hash? a, Md5Hash? b) => !(a == b);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(in T v)
|
||||
public readonly bool Contains(in T v)
|
||||
=> start.CompareTo(v) <= 0 && end.CompareTo(v) >= 0;
|
||||
|
||||
private void VerifyStartLessThanEnd()
|
||||
|
||||
@@ -11,10 +11,10 @@ namespace Barotrauma
|
||||
public abstract bool IsSuccess { get; }
|
||||
public bool IsFailure => !IsSuccess;
|
||||
|
||||
public static Success<T, TError> Success(T value)
|
||||
public static Result<T, TError> Success(T value)
|
||||
=> new Success<T, TError>(value);
|
||||
|
||||
public static Failure<T, TError> Failure(TError error)
|
||||
public static Result<T, TError> Failure(TError error)
|
||||
=> new Failure<T, TError>(error);
|
||||
|
||||
public abstract bool TryUnwrapSuccess([MaybeNullWhen(returnValue: false)] out T value);
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Barotrauma.Networking;
|
||||
*/
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct Segment<T>(T Identifier, UInt16 Pointer) : INetSerializableStruct where T : struct;
|
||||
public readonly record struct Segment<T>(T Identifier, int Pointer) : INetSerializableStruct where T : struct;
|
||||
|
||||
readonly ref struct SegmentTableWriter<T> where T : struct
|
||||
{
|
||||
@@ -94,7 +94,7 @@ readonly ref struct SegmentTableWriter<T> where T : struct
|
||||
public static SegmentTableWriter<T> StartWriting(IWriteMessage msg)
|
||||
{
|
||||
var retVal = new SegmentTableWriter<T>(msg, msg.BitPosition);
|
||||
msg.WriteUInt16(0); //reserve space for the table pointer
|
||||
msg.WriteInt32(0); //reserve space for the table pointer
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -104,28 +104,22 @@ readonly ref struct SegmentTableWriter<T> where T : struct
|
||||
{
|
||||
throw new InvalidOperationException($"Too many segments in SegmentTable<{typeof(T).Name}>");
|
||||
}
|
||||
|
||||
if (message.BitPosition - PointerLocation > UInt16.MaxValue)
|
||||
{
|
||||
throw new OverflowException(
|
||||
$"Too much data is being stored in SegmentTable<{typeof(T).Name}> ({segments.Count} segments)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void StartNewSegment(T value)
|
||||
{
|
||||
ThrowOnInvalidState();
|
||||
segments.Add(new Segment<T>(value, (UInt16)(message.BitPosition-PointerLocation)));
|
||||
segments.Add(new Segment<T>(value, message.BitPosition - PointerLocation));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ThrowOnInvalidState();
|
||||
int tablePosition = message.BitPosition;
|
||||
|
||||
|
||||
//rewrite the table pointer now that we know where the table ends
|
||||
message.BitPosition = PointerLocation;
|
||||
message.WriteUInt16((UInt16)(tablePosition-PointerLocation));
|
||||
message.WriteInt32(tablePosition - PointerLocation);
|
||||
|
||||
//write the table
|
||||
message.BitPosition = tablePosition;
|
||||
@@ -274,7 +268,7 @@ readonly ref struct SegmentTableReader<T> where T : struct
|
||||
ExceptionHandler? exceptionHandler = null)
|
||||
{
|
||||
int pointerLocation = msg.BitPosition;
|
||||
int tablePointer = msg.ReadUInt16();
|
||||
int tablePointer = msg.ReadInt32();
|
||||
int tableLocation = pointerLocation + tablePointer;
|
||||
|
||||
int returnPosition = msg.BitPosition;
|
||||
|
||||
Reference in New Issue
Block a user