Build 0.20.9.0
This commit is contained in:
@@ -1838,7 +1838,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
|
||||
{
|
||||
bool advance = !canAttack && Character.InWater || distance > attackLimb.attack.Range * 0.9f;
|
||||
bool advance = !canAttack && Character.CurrentHull == null || distance > attackLimb.attack.Range * 0.9f;
|
||||
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
|
||||
if (fallBack)
|
||||
{
|
||||
@@ -1856,10 +1856,18 @@ namespace Barotrauma
|
||||
SteeringManager.SteeringSeek(steerPos, 10);
|
||||
}
|
||||
}
|
||||
else if (!Character.InWater)
|
||||
else
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
FaceTarget(SelectedAiTarget.Entity);
|
||||
if (Character.CurrentHull == null && !canAttack)
|
||||
{
|
||||
SteeringManager.SteeringWander();
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
FaceTarget(SelectedAiTarget.Entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!canAttack || distance > Math.Min(AttackLimb.attack.Range * 0.9f, 100))
|
||||
@@ -1882,7 +1890,7 @@ namespace Barotrauma
|
||||
}
|
||||
Entity targetEntity = wallTarget?.Structure ?? SelectedAiTarget?.Entity;
|
||||
IDamageable damageTarget = targetEntity as IDamageable;
|
||||
if (AttackLimb?.attack is Attack { Ranged: true} attack && targetEntity != null)
|
||||
if (AttackLimb?.attack is Attack { Ranged: true} attack)
|
||||
{
|
||||
AimRangedAttack(attack, targetEntity);
|
||||
}
|
||||
@@ -1901,7 +1909,7 @@ namespace Barotrauma
|
||||
|
||||
public void AimRangedAttack(Attack attack, Entity targetEntity)
|
||||
{
|
||||
if (attack == null || attack.Ranged == false || targetEntity == null) { return; }
|
||||
if (attack is not { Ranged: true } || targetEntity is not { Removed: false }) { return; }
|
||||
Character.SetInput(InputType.Aim, false, true);
|
||||
Limb limb = GetLimbToRotate(attack);
|
||||
if (limb != null)
|
||||
|
||||
@@ -1907,8 +1907,8 @@ namespace Barotrauma
|
||||
visibleHulls = VisibleHulls;
|
||||
}
|
||||
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = HasDivingSuit(character);
|
||||
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
|
||||
bool ignoreWater = character.IsProtectedFromPressure();
|
||||
bool ignoreOxygen = HasDivingGear(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
if (isCurrentHull)
|
||||
|
||||
@@ -77,11 +77,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
||||
{
|
||||
if (GameMain.GameSession.RoundDuration > 30.0f) { currentFlags.Add("Initial".ToIdentifier()); }
|
||||
if (GameMain.GameSession.RoundDuration < 30.0f) { currentFlags.Add("Initial".ToIdentifier()); }
|
||||
}
|
||||
else if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
if (GameMain.GameSession.RoundDuration > 120.0f &&
|
||||
if (GameMain.GameSession.RoundDuration < 120.0f &&
|
||||
speaker?.CurrentHull != null &&
|
||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||
|
||||
+14
-1
@@ -876,7 +876,20 @@ namespace Barotrauma
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false
|
||||
},
|
||||
onAbandon: () => Abandon = true);
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||
{
|
||||
// If in the same room with an enemy -> don't try to escape because we'd want to fight it
|
||||
SteeringManager.Reset();
|
||||
RemoveSubObjective(ref followTargetObjective);
|
||||
}
|
||||
else
|
||||
{
|
||||
// else abandon and fall back to find safety mode
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
if (followTargetObjective == null) { return; }
|
||||
if (Mode == CombatMode.Arrest && Enemy.IsKnockedDown)
|
||||
{
|
||||
|
||||
+30
-3
@@ -25,6 +25,8 @@ namespace Barotrauma
|
||||
private readonly Item item;
|
||||
public Item ItemToContain { get; private set; }
|
||||
|
||||
public int? TargetSlot;
|
||||
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
|
||||
@@ -126,7 +128,19 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CanInteractWith(container.Item, checkLinked: false))
|
||||
{
|
||||
if (RemoveExisting || (RemoveExistingWhenNecessary && !container.Inventory.CanBePut(ItemToContain)))
|
||||
static bool CanBePut(Inventory inventory, int? targetSlot, Item itemToContain)
|
||||
{
|
||||
if (targetSlot.HasValue)
|
||||
{
|
||||
return inventory.CanBePutInSlot(itemToContain, targetSlot.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return inventory.CanBePut(itemToContain);
|
||||
}
|
||||
}
|
||||
|
||||
if (RemoveExisting || (RemoveExistingWhenNecessary && !CanBePut(container.Inventory, TargetSlot, ItemToContain)))
|
||||
{
|
||||
HumanAIController.UnequipContainedItems(container.Item, predicate: RemoveExistingPredicate, unequipMax: RemoveMax);
|
||||
}
|
||||
@@ -136,7 +150,20 @@ namespace Barotrauma
|
||||
}
|
||||
Inventory originalInventory = ItemToContain.ParentInventory;
|
||||
var slots = originalInventory?.FindIndices(ItemToContain);
|
||||
if (container.Inventory.TryPutItem(ItemToContain, null))
|
||||
|
||||
static bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
|
||||
{
|
||||
if (targetSlot.HasValue)
|
||||
{
|
||||
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return inventory.TryPutItem(itemToContain, user: null);
|
||||
}
|
||||
}
|
||||
|
||||
if (TryPutItem(container.Inventory, TargetSlot, ItemToContain))
|
||||
{
|
||||
if (MoveWholeStack && slots != null)
|
||||
{
|
||||
@@ -144,7 +171,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in originalInventory.GetItemsAt(slot).ToList())
|
||||
{
|
||||
container.Inventory.TryPutItem(item, null);
|
||||
TryPutItem(container.Inventory, TargetSlot, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -23,9 +23,8 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (!character.IsOnPlayerTeam) { return Targets.None() ? 0 : 100; }
|
||||
int totalEnemies = Targets.Count();
|
||||
if (totalEnemies == 0) { return 0; }
|
||||
if (Targets.None()) { return 0; }
|
||||
if (!character.IsOnPlayerTeam) { return 100; }
|
||||
if (character.IsSecurity) { return 100; }
|
||||
if (objectiveManager.IsOrder(this)) { return 100; }
|
||||
// If there's any security officers onboard, leave fighting for them.
|
||||
|
||||
+7
-1
@@ -103,13 +103,19 @@ namespace Barotrauma
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank").Value, null, 0, "getoxygentank".ToIdentifier(), 30.0f);
|
||||
}
|
||||
}
|
||||
return new AIObjectiveContainItem(character, OXYGEN_SOURCE, targetItem.GetComponent<ItemContainer>(), objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
var container = targetItem.GetComponent<ItemContainer>();
|
||||
var objective = new AIObjectiveContainItem(character, OXYGEN_SOURCE, container, objectiveManager, spawnItemIfNotFound: character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
ConditionLevel = MIN_OXYGEN,
|
||||
RemoveExistingWhenNecessary = true
|
||||
};
|
||||
if (container.HasSubContainers)
|
||||
{
|
||||
objective.TargetSlot = container.FindSuitableSubContainerIndex(OXYGEN_SOURCE);
|
||||
}
|
||||
return objective;
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@ namespace Barotrauma
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit));
|
||||
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
if (Mimic)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
|
||||
@@ -435,7 +435,7 @@ namespace Barotrauma
|
||||
spawnPoint ??= WayPoint.WayPointList.Where(wp => wp.SpawnType == SpawnType.Human && wp.Submarine?.Info.Type == SubmarineType.Player).GetRandomUnsynced();
|
||||
spawnPos = spawnPoint?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
||||
}
|
||||
var pet = Character.Create(speciesName, spawnPos, seed);
|
||||
var pet = Character.Create(speciesName, spawnPos, seed, spawnInitialItems: false);
|
||||
var petBehavior = (pet?.AIController as EnemyAIController)?.PetBehavior;
|
||||
if (petBehavior != null)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Barotrauma
|
||||
get { return aiController; }
|
||||
}
|
||||
|
||||
public AICharacter(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isNetworkPlayer = false, RagdollParams ragdoll = null)
|
||||
: base(prefab, position, seed, characterInfo, id: id, isRemotePlayer: isNetworkPlayer, ragdollParams: ragdoll)
|
||||
public AICharacter(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isNetworkPlayer = false, RagdollParams ragdoll = null, bool spawnInitialItems = true)
|
||||
: base(prefab, position, seed, characterInfo, id: id, isRemotePlayer: isNetworkPlayer, ragdollParams: ragdoll, spawnInitialItems)
|
||||
{
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -529,8 +529,17 @@ namespace Barotrauma
|
||||
private Color speechBubbleColor;
|
||||
private float speechBubbleTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents the character from interacting with items or characters
|
||||
/// </summary>
|
||||
public bool DisableInteract { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prevents the character from highlighting items or characters with the cursor,
|
||||
/// meaning it can't interact with anything but the things it has currently selected/equipped
|
||||
/// </summary>
|
||||
public bool DisableFocusingOnEntities { get; set; }
|
||||
|
||||
//text displayed when the character is highlighted if custom interact is set
|
||||
public LocalizedString CustomInteractHUDText { get; private set; }
|
||||
private Action<Character, Character> onCustomInteract;
|
||||
@@ -1088,9 +1097,9 @@ namespace Barotrauma
|
||||
/// <param name="isRemotePlayer">Is the character controlled by a remote player.</param>
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null)
|
||||
public static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, RagdollParams ragdoll = null, bool spawnInitialItems = true)
|
||||
{
|
||||
return Create(characterInfo.SpeciesName, position, seed, characterInfo, id, isRemotePlayer, hasAi, true, ragdoll);
|
||||
return Create(characterInfo.SpeciesName, position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent: true, ragdoll, spawnInitialItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1105,16 +1114,16 @@ namespace Barotrauma
|
||||
/// <param name="hasAi">Is the character controlled by AI.</param>
|
||||
/// <param name="createNetworkEvent">Should clients receive a network event about the creation of this character?</param>
|
||||
/// <param name="ragdoll">Ragdoll configuration file. If null, will select the default.</param>
|
||||
public static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool throwErrorIfNotFound = true)
|
||||
public static Character Create(string speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool throwErrorIfNotFound = true, bool spawnInitialItems = true)
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
speciesName = Path.GetFileNameWithoutExtension(speciesName);
|
||||
}
|
||||
return Create(speciesName.ToIdentifier(), position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, ragdoll, throwErrorIfNotFound);
|
||||
return Create(speciesName.ToIdentifier(), position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, ragdoll, throwErrorIfNotFound, spawnInitialItems);
|
||||
}
|
||||
|
||||
public static Character Create(Identifier speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool throwErrorIfNotFound = true)
|
||||
public static Character Create(Identifier speciesName, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool throwErrorIfNotFound = true, bool spawnInitialItems = true)
|
||||
{
|
||||
var prefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (prefab == null)
|
||||
@@ -1131,29 +1140,29 @@ namespace Barotrauma
|
||||
|
||||
return null;
|
||||
}
|
||||
return Create(prefab, position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, ragdoll);
|
||||
return Create(prefab, position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, ragdoll, spawnInitialItems);
|
||||
}
|
||||
|
||||
public static Character Create(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null)
|
||||
public static Character Create(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, RagdollParams ragdoll = null, bool spawnInitialItems = true)
|
||||
{
|
||||
Character newCharacter = null;
|
||||
if (prefab.Identifier != CharacterPrefab.HumanSpeciesName)
|
||||
{
|
||||
var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll);
|
||||
var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems);
|
||||
var ai = new EnemyAIController(aiCharacter, seed);
|
||||
aiCharacter.SetAI(ai);
|
||||
newCharacter = aiCharacter;
|
||||
}
|
||||
else if (hasAi)
|
||||
{
|
||||
var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll);
|
||||
var aiCharacter = new AICharacter(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems);
|
||||
var ai = new HumanAIController(aiCharacter);
|
||||
aiCharacter.SetAI(ai);
|
||||
newCharacter = aiCharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
newCharacter = new Character(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll);
|
||||
newCharacter = new Character(prefab, position, seed, characterInfo, id, isRemotePlayer, ragdoll, spawnInitialItems);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
@@ -1170,7 +1179,7 @@ namespace Barotrauma
|
||||
wallet = new Wallet(Option<Character>.Some(this));
|
||||
}
|
||||
|
||||
protected Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null)
|
||||
protected Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, bool isRemotePlayer = false, RagdollParams ragdollParams = null, bool spawnInitialItems = true)
|
||||
: this(null, id)
|
||||
{
|
||||
this.Seed = seed;
|
||||
@@ -1280,7 +1289,8 @@ namespace Barotrauma
|
||||
{
|
||||
Inventory = new CharacterInventory(
|
||||
inventoryElements.Count == 1 ? inventoryElements[0] : ToolBox.SelectWeightedRandom(inventoryElements, inventoryCommonness, random),
|
||||
this);
|
||||
this,
|
||||
spawnInitialItems);
|
||||
}
|
||||
if (healthElements.Count == 0)
|
||||
{
|
||||
@@ -2719,7 +2729,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this))
|
||||
if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this) && !DisableFocusingOnEntities)
|
||||
{
|
||||
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
@@ -2754,6 +2764,7 @@ namespace Barotrauma
|
||||
focusedItem = null;
|
||||
}
|
||||
findFocusedTimer -= deltaTime;
|
||||
DisableFocusingOnEntities = false;
|
||||
}
|
||||
#endif
|
||||
var head = AnimController.GetLimb(LimbType.Head);
|
||||
@@ -4249,11 +4260,14 @@ namespace Barotrauma
|
||||
|
||||
if (!isNetworkMessage)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
}
|
||||
|
||||
CharacterHealth.ApplyAffliction(null, new Affliction(AfflictionPrefab.Pressure, AfflictionPrefab.Pressure.MaxStrength));
|
||||
if (isNetworkMessage && GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Vitality <= CharacterHealth.MinVitality) { Kill(CauseOfDeathType.Pressure, null, isNetworkMessage: true); }
|
||||
if (GameMain.NetworkMember is not { IsClient: true } || isNetworkMessage)
|
||||
{
|
||||
Kill(CauseOfDeathType.Pressure, null, isNetworkMessage: true);
|
||||
}
|
||||
if (IsDead)
|
||||
{
|
||||
BreakJoints();
|
||||
@@ -4930,7 +4944,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
|
||||
{
|
||||
if (talentOption.TalentIdentifiers.None(HasTalent))
|
||||
if (!talentOption.HasMaxTalents(info.UnlockedTalents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class CharacterHUD
|
||||
{
|
||||
static partial void RecreateHudTextsIfControllingProjSpecific(Character character);
|
||||
|
||||
static partial void RecreateHudTextsIfFocusedProjSpecific(params Item[] items);
|
||||
|
||||
public static void RecreateHudTextsIfControlling(Character character) => RecreateHudTextsIfControllingProjSpecific(character);
|
||||
|
||||
public static void RecreateHudTextsIfFocused(params Item[] items) => RecreateHudTextsIfFocusedProjSpecific(items);
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsUnconscious
|
||||
{
|
||||
get { return (Vitality <= 0.0f || Character.IsDead) && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious); }
|
||||
get { return Character.IsDead || (Vitality <= 0.0f && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious)); }
|
||||
}
|
||||
|
||||
public float PressureKillDelay { get; private set; } = 5.0f;
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Barotrauma
|
||||
|
||||
bool allowCheats = false;
|
||||
#if CLIENT
|
||||
allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is EditorScreen);
|
||||
allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is { IsEditor: true });
|
||||
#endif
|
||||
if (!allowCheats && !CheatsEnabled && IsCheat)
|
||||
{
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Barotrauma
|
||||
MaxAttachableCount,
|
||||
ExplosionRadiusMultiplier,
|
||||
ExplosionDamageMultiplier,
|
||||
FabricateMedicineSpeedMultiplier,
|
||||
FabricationSpeed,
|
||||
BallastFloraDamageMultiplier,
|
||||
HoldBreathMultiplier,
|
||||
Apprenticeship,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -53,6 +54,11 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
conditionals = conditionalList;
|
||||
|
||||
if (itemTags.None() && ItemIdentifiers.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
@@ -100,7 +106,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (inventory == null) { return false; }
|
||||
int count = 0;
|
||||
foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier), recursive: Recursive))
|
||||
HashSet<Item> eventTargets = new HashSet<Item>();
|
||||
foreach (Identifier tag in itemTags)
|
||||
{
|
||||
foreach (var target in ParentEvent.GetTargets(tag))
|
||||
{
|
||||
if (target is Item item)
|
||||
{
|
||||
eventTargets.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item item in inventory.FindAllItems(it =>
|
||||
itemTags.Any(it.HasTag) ||
|
||||
itemIdentifierSplit.Contains(it.Prefab.Identifier) ||
|
||||
eventTargets.Contains(it),
|
||||
recursive: Recursive))
|
||||
{
|
||||
if (!ConditionalsMatch(item, character)) { continue; }
|
||||
count++;
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
|
||||
public bool DisableEvents
|
||||
{
|
||||
get { return IsFirstRound && GameMain.GameSession.RoundDuration > FirstRoundEventDelay; }
|
||||
get { return IsFirstRound && GameMain.GameSession.RoundDuration < FirstRoundEventDelay; }
|
||||
}
|
||||
|
||||
public bool CheatsEnabled;
|
||||
|
||||
@@ -764,7 +764,7 @@ namespace Barotrauma
|
||||
/// </remarks>
|
||||
public static ImmutableHashSet<Character> GetSessionCrewCharacters(CharacterType type)
|
||||
{
|
||||
if (GameMain.GameSession.CrewManager is not { } crewManager) { return ImmutableHashSet<Character>.Empty; }
|
||||
if (GameMain.GameSession?.CrewManager is not { } crewManager) { return ImmutableHashSet<Character>.Empty; }
|
||||
|
||||
IEnumerable<Character> players;
|
||||
IEnumerable<Character> bots;
|
||||
@@ -907,7 +907,7 @@ namespace Barotrauma
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"), RoundDuration);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name.Value ?? "none"), RoundDuration);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0), RoundDuration);
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count ?? 0), RoundDuration);
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier + ":" + (mission.Completed ? "Completed" : "Failed"), RoundDuration);
|
||||
|
||||
@@ -56,6 +56,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
EndReadyCheck();
|
||||
|
||||
#if CLIENT
|
||||
msgBox?.Close();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace Barotrauma
|
||||
return slotString == null ? Array.Empty<string>() : slotString.Split(',');
|
||||
}
|
||||
|
||||
public CharacterInventory(XElement element, Character character)
|
||||
public CharacterInventory(XElement element, Character character, bool spawnInitialItems)
|
||||
: base(character, ParseSlotTypes(element).Length)
|
||||
{
|
||||
this.character = character;
|
||||
@@ -84,6 +84,8 @@ namespace Barotrauma
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
if (!spawnInitialItems) { return; }
|
||||
|
||||
#if CLIENT
|
||||
//clients don't create items until the server says so
|
||||
if (GameMain.Client != null) { return; }
|
||||
@@ -94,7 +96,7 @@ namespace Barotrauma
|
||||
if (!subElement.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
if (!ItemPrefab.Prefabs.TryGet(itemIdentifier, out var itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in character inventory \"" + character.SpeciesName + "\" - item \"" + itemIdentifier + "\" not found.");
|
||||
continue;
|
||||
@@ -200,6 +202,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
CreateSlots();
|
||||
#endif
|
||||
CharacterHUD.RecreateHudTextsIfControlling(character);
|
||||
//if the item was equipped and there are more items in the same stack, equip one of those items
|
||||
if (tryEquipFromSameStack && wasEquipped)
|
||||
{
|
||||
@@ -498,6 +501,7 @@ namespace Barotrauma
|
||||
HintManager.OnObtainedItem(character, item);
|
||||
}
|
||||
#endif
|
||||
CharacterHUD.RecreateHudTextsIfControlling(character);
|
||||
if (item.CampaignInteractionType == CampaignMode.InteractionType.Cargo)
|
||||
{
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
|
||||
@@ -47,6 +47,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(itemPrefab));
|
||||
}
|
||||
|
||||
public bool MatchesItem(Identifier identifierOrTag)
|
||||
{
|
||||
return
|
||||
ContainableItems == null || ContainableItems.Count == 0 ||
|
||||
ContainableItems.Any(c => c.Identifiers.Contains(identifierOrTag) && !c.ExcludedIdentifiers.Contains(identifierOrTag));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly NamedEvent<ItemContainer> OnContainedItemsChanged = new NamedEvent<ItemContainer>();
|
||||
@@ -374,7 +381,7 @@ namespace Barotrauma.Items.Components
|
||||
// Set the contained items active if there's an item inserted inside the container. Enables e.g. the rifle flashlight when it's attached to the rifle (put inside of it).
|
||||
SetContainedActive(true);
|
||||
}
|
||||
|
||||
CharacterHUD.RecreateHudTextsIfFocused(item, containedItem);
|
||||
OnContainedItemsChanged.Invoke(this);
|
||||
}
|
||||
|
||||
@@ -386,9 +393,9 @@ namespace Barotrauma.Items.Components
|
||||
public void OnItemRemoved(Item containedItem)
|
||||
{
|
||||
activeContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
|
||||
//deactivate if the inventory is empty
|
||||
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
|
||||
CharacterHUD.RecreateHudTextsIfFocused(item, containedItem);
|
||||
OnContainedItemsChanged.Invoke(this);
|
||||
}
|
||||
|
||||
@@ -664,6 +671,18 @@ namespace Barotrauma.Items.Components
|
||||
return relatedItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the first slot whose restrictions match the specified tag or identifier
|
||||
/// </summary>
|
||||
public int? FindSuitableSubContainerIndex(Identifier itemTagOrIdentifier)
|
||||
{
|
||||
for (int i = 0; i < slotRestrictions.Length; i++)
|
||||
{
|
||||
if (slotRestrictions[i].MatchesItem(itemTagOrIdentifier)) { return i; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
switch (connection.Name)
|
||||
|
||||
@@ -603,6 +603,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void UpdateRequiredTimeProjSpecific();
|
||||
|
||||
private static bool AnyOneHasRecipeForItem(Character user, ItemPrefab item)
|
||||
{
|
||||
return
|
||||
(user != null && user.HasRecipeForItem(item.Identifier)) ||
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Bot).Any(c => c.HasRecipeForItem(item.Identifier));
|
||||
}
|
||||
|
||||
private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary<Identifier, List<Item>> availableIngredients, Character character)
|
||||
{
|
||||
@@ -610,8 +617,7 @@ namespace Barotrauma.Items.Components
|
||||
if (fabricableItem.RequiresRecipe)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
if (!character.HasRecipeForItem(fabricableItem.TargetItem.Identifier) &&
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Bot).None(c => c.HasRecipeForItem(fabricableItem.TargetItem.Identifier)))
|
||||
if (!AnyOneHasRecipeForItem(character, fabricableItem.TargetItem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -678,9 +684,10 @@ namespace Barotrauma.Items.Components
|
||||
//fabricating takes 100 times longer if degree of success is close to 0
|
||||
//characters with a higher skill than required can fabricate up to 100% faster
|
||||
float time = fabricableItem.RequiredTime / item.StatManager.GetAdjustedValue(ItemTalentStats.FabricationSpeed, FabricationSpeed) / MathHelper.Clamp(t, 0.01f, 2.0f);
|
||||
if (user is not null && fabricableItem.TargetItem is { } it && it.Tags.Contains("medical"))
|
||||
|
||||
if (user?.Info is { } info && fabricableItem.TargetItem is { } it)
|
||||
{
|
||||
time *= 1f + user.GetStatValue(StatTypes.FabricateMedicineSpeedMultiplier);
|
||||
time /= 1f + it.Tags.Sum(tag => info.GetSavedStatValue(StatTypes.FabricationSpeed, tag));
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,8 @@ namespace Barotrauma.Items.Components
|
||||
public List<Hull> LinkedHulls = new List<Hull>();
|
||||
}
|
||||
|
||||
private DateTime resetDataTime;
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
private readonly Dictionary<Hull, HullData> hullDatas;
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Does the machine require inputs from water detectors in order to show the water levels inside rooms.")]
|
||||
public bool RequireWaterDetectors
|
||||
{
|
||||
@@ -77,7 +73,6 @@ namespace Barotrauma.Items.Components
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
hullDatas = new Dictionary<Hull, HullData>();
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -85,37 +80,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//reset data if we haven't received anything in a while
|
||||
//(so that outdated hull info won't be shown if detectors stop sending signals)
|
||||
if (DateTime.Now > resetDataTime)
|
||||
{
|
||||
foreach (HullData hullData in hullDatas.Values)
|
||||
{
|
||||
if (!hullData.Distort)
|
||||
{
|
||||
if (Timing.TotalTime > hullData.LastOxygenDataTime + 1.0) { hullData.ReceivedOxygenAmount = null; }
|
||||
if (Timing.TotalTime > hullData.LastWaterDataTime + 1.0) { hullData.ReceivedWaterAmount = null; }
|
||||
}
|
||||
}
|
||||
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (cardRefreshTimer > cardRefreshDelay)
|
||||
{
|
||||
if (item.Submarine is { } sub)
|
||||
{
|
||||
UpdateIDCards(sub);
|
||||
}
|
||||
|
||||
cardRefreshTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
cardRefreshTimer += deltaTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
hasPower = Voltage > MinVoltage;
|
||||
if (hasPower)
|
||||
{
|
||||
@@ -140,67 +104,5 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
Item source = signal.source;
|
||||
if (source == null || source.CurrentHull == null) { return; }
|
||||
|
||||
Hull sourceHull = source.CurrentHull;
|
||||
if (!hullDatas.TryGetValue(sourceHull, out HullData hullData))
|
||||
{
|
||||
hullData = new HullData();
|
||||
hullDatas.Add(sourceHull, hullData);
|
||||
}
|
||||
|
||||
if (hullData.Distort) { return; }
|
||||
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "water_data_in":
|
||||
//cheating a bit because water detectors don't actually send the water level
|
||||
bool fromWaterDetector = source.GetComponent<WaterDetector>() != null;
|
||||
hullData.ReceivedWaterAmount = null;
|
||||
hullData.LastWaterDataTime = Timing.TotalTime;
|
||||
if (fromWaterDetector)
|
||||
{
|
||||
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
|
||||
}
|
||||
foreach (var linked in sourceHull.linkedTo)
|
||||
{
|
||||
if (!(linked is Hull linkedHull)) { continue; }
|
||||
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
|
||||
{
|
||||
linkedHullData = new HullData();
|
||||
hullDatas.Add(linkedHull, linkedHullData);
|
||||
}
|
||||
linkedHullData.ReceivedWaterAmount = null;
|
||||
if (fromWaterDetector)
|
||||
{
|
||||
linkedHullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(linkedHull);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "oxygen_data_in":
|
||||
if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float oxy))
|
||||
{
|
||||
oxy = Rand.Range(0.0f, 100.0f);
|
||||
}
|
||||
hullData.ReceivedOxygenAmount = oxy;
|
||||
hullData.LastOxygenDataTime = Timing.TotalTime;
|
||||
foreach (var linked in sourceHull.linkedTo)
|
||||
{
|
||||
if (linked is not Hull linkedHull) { continue; }
|
||||
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
|
||||
{
|
||||
linkedHullData = new HullData();
|
||||
hullDatas.Add(linkedHull, linkedHullData);
|
||||
}
|
||||
linkedHullData.ReceivedOxygenAmount = oxy;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
const float NetworkUpdateIntervalHigh = 0.5f;
|
||||
|
||||
const float TemperatureBoostAmount = 20;
|
||||
const float TemperatureBoostAmount = 25;
|
||||
|
||||
//the rate at which the reactor is being run on (higher rate -> higher temperature)
|
||||
private float fissionRate;
|
||||
@@ -26,10 +26,6 @@ namespace Barotrauma.Items.Components
|
||||
//amount of power generated balanced with the load)
|
||||
private bool autoTemp;
|
||||
|
||||
//automatical adjustment to the power output when
|
||||
//turbine output and temperature are in the optimal range
|
||||
private float autoAdjustAmount;
|
||||
|
||||
private float fuelConsumptionRate;
|
||||
|
||||
private float meltDownTimer, meltDownDelay;
|
||||
@@ -53,6 +49,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float temperatureBoost;
|
||||
|
||||
public bool AllowTemperatureBoost => Math.Abs(temperatureBoost) < TemperatureBoostAmount * 0.9f;
|
||||
|
||||
private bool _powerOn;
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes)]
|
||||
@@ -314,7 +312,7 @@ namespace Barotrauma.Items.Components
|
||||
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
|
||||
temperatureBoost = adjustValueWithoutOverShooting(temperatureBoost, 0.0f, deltaTime);
|
||||
#if CLIENT
|
||||
temperatureBoostUpButton.Enabled = temperatureBoostDownButton.Enabled = Math.Abs(temperatureBoost) < TemperatureBoostAmount * 0.9f;
|
||||
temperatureBoostUpButton.Enabled = temperatureBoostDownButton.Enabled = AllowTemperatureBoost;
|
||||
#endif
|
||||
|
||||
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(TargetFissionRate, AvailableFuel), deltaTime);
|
||||
|
||||
@@ -248,6 +248,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (container?.Inventory == null) { return; }
|
||||
|
||||
bool recreateHudTexts = false;
|
||||
for (var i = 0; i < container.Inventory.Capacity; i++)
|
||||
{
|
||||
if (i < 0 || GrowableSeeds.Length <= i) { continue; }
|
||||
@@ -257,6 +258,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (growable != null)
|
||||
{
|
||||
recreateHudTexts |= GrowableSeeds[i] != growable;
|
||||
GrowableSeeds[i] = growable;
|
||||
growable.IsActive = true;
|
||||
}
|
||||
@@ -267,11 +269,14 @@ namespace Barotrauma.Items.Components
|
||||
// Kill the plant if it's somehow removed
|
||||
oldGrowable.Decayed = true;
|
||||
oldGrowable.IsActive = false;
|
||||
recreateHudTexts = true;
|
||||
}
|
||||
|
||||
GrowableSeeds[i] = null;
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
CharacterHUD.RecreateHudTexts |= recreateHudTexts;
|
||||
#endif
|
||||
|
||||
// server handles this
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//[power/min]
|
||||
private float capacity;
|
||||
private float adjustedCapacity;
|
||||
|
||||
private float charge, prevCharge;
|
||||
|
||||
@@ -66,7 +67,11 @@ namespace Barotrauma.Items.Components
|
||||
public float Capacity
|
||||
{
|
||||
get => capacity;
|
||||
set { capacity = Math.Max(value, 1.0f); }
|
||||
set
|
||||
{
|
||||
capacity = Math.Max(value, 1.0f);
|
||||
adjustedCapacity = GetCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current charge of the device.")]
|
||||
@@ -76,10 +81,10 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
charge = MathHelper.Clamp(value, 0.0f, capacity);
|
||||
charge = MathHelper.Clamp(value, 0.0f, adjustedCapacity);
|
||||
|
||||
//send a network event if the charge has changed by more than 5%
|
||||
if (Math.Abs(charge - lastSentCharge) / capacity > 0.05f)
|
||||
if (Math.Abs(charge - lastSentCharge) / adjustedCapacity > 0.05f)
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); }
|
||||
@@ -89,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public float ChargePercentage => MathUtils.Percentage(Charge, GetCapacity());
|
||||
public float ChargePercentage => MathUtils.Percentage(Charge, adjustedCapacity);
|
||||
|
||||
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
|
||||
public float MaxRechargeSpeed
|
||||
@@ -157,6 +162,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
adjustedCapacity = GetCapacity();
|
||||
if (item.Connections == null)
|
||||
{
|
||||
IsActive = false;
|
||||
@@ -164,7 +170,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
isRunning = true;
|
||||
float chargeRatio = charge / capacity;
|
||||
float chargeRatio = charge / adjustedCapacity;
|
||||
|
||||
if (chargeRatio > 0.0f)
|
||||
{
|
||||
@@ -180,7 +186,7 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(((int)Math.Round(CurrPowerOutput)).ToString(), "power_value_out");
|
||||
item.SendSignal(((int)Math.Round(loadReading)).ToString(), "load_value_out");
|
||||
item.SendSignal(((int)Math.Round(Charge)).ToString(), "charge");
|
||||
item.SendSignal(((int)Math.Round(Charge / capacity * 100)).ToString(), "charge_%");
|
||||
item.SendSignal(((int)Math.Round(Charge / adjustedCapacity * 100)).ToString(), "charge_%");
|
||||
item.SendSignal(((int)Math.Round(RechargeSpeed / maxRechargeSpeed * 100)).ToString(), "charge_rate");
|
||||
}
|
||||
|
||||
@@ -193,16 +199,16 @@ namespace Barotrauma.Items.Components
|
||||
if (connection == powerIn)
|
||||
{
|
||||
//Don't draw power if fully charged
|
||||
if (charge >= capacity)
|
||||
if (charge >= adjustedCapacity)
|
||||
{
|
||||
charge = capacity;
|
||||
charge = adjustedCapacity;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Condition <= 0.0f) { return 0.0f; }
|
||||
|
||||
float missingCharge = capacity - charge;
|
||||
float missingCharge = adjustedCapacity - charge;
|
||||
float targetRechargeSpeed = rechargeSpeed;
|
||||
|
||||
if (ExponentialRechargeSpeed)
|
||||
@@ -239,7 +245,7 @@ namespace Barotrauma.Items.Components
|
||||
if (connection == powerOut)
|
||||
{
|
||||
float maxOutput;
|
||||
float chargeRatio = prevCharge / capacity;
|
||||
float chargeRatio = prevCharge / adjustedCapacity;
|
||||
if (chargeRatio < 0.1f)
|
||||
{
|
||||
maxOutput = Math.Max(chargeRatio * 10.0f, 0.0f) * MaxOutPut;
|
||||
@@ -292,7 +298,7 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//Decrease charge based on how much power is leaving the device
|
||||
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, GetCapacity());
|
||||
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, adjustedCapacity);
|
||||
prevCharge = Charge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,13 +639,16 @@ namespace Barotrauma.Items.Components
|
||||
ItemContainer projectileContainer = projectiles.First().Item.Container?.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null && projectileContainer.Item != item)
|
||||
{
|
||||
projectileContainer.Item.Use(deltaTime, null);
|
||||
//Use root container (e.g. loader) too in case it needs to react to firing somehow
|
||||
var rootContainer = projectileContainer.Item.GetRootContainer();
|
||||
if (rootContainer != projectileContainer.Item)
|
||||
if (rootContainer != null && rootContainer != projectileContainer.Item)
|
||||
{
|
||||
rootContainer.Use(deltaTime, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
projectileContainer.Item.Use(deltaTime, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -576,11 +576,13 @@ namespace Barotrauma
|
||||
if (selectedSlot?.Inventory == this) { selectedSlot.ForceTooltipRefresh = true; }
|
||||
}
|
||||
#endif
|
||||
CharacterHUD.RecreateHudTextsIfControlling(user);
|
||||
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.Enabled = false;
|
||||
item.body.BodyType = FarseerPhysics.BodyType.Dynamic;
|
||||
item.SetTransform(item.SimPosition, rotation: 0.0f, findNewHull: false);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
@@ -924,6 +926,7 @@ namespace Barotrauma
|
||||
if (selectedSlot?.Inventory == this) { selectedSlot.ForceTooltipRefresh = true; }
|
||||
}
|
||||
#endif
|
||||
CharacterHUD.RecreateHudTextsIfFocused(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1092,6 +1092,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var holdables = components.Where(c => c is Holdable);
|
||||
if (holdables.Count() > 1)
|
||||
{
|
||||
DebugConsole.AddWarning($"Item {Prefab.Identifier} has multiple {nameof(Holdable)} components ({string.Join(", ", holdables.Select(h => h.GetType().Name))}).");
|
||||
}
|
||||
|
||||
InsertToList();
|
||||
ItemList.Add(this);
|
||||
if (Prefab.IsDangerous) { dangerousItems.Add(this); }
|
||||
@@ -1104,7 +1110,6 @@ namespace Barotrauma
|
||||
if (HasTag("logic")) { isLogic = true; }
|
||||
|
||||
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
|
||||
Components.ForEach(c => c.ApplyStatusEffects(ActionType.OnSpawn, 1.0f));
|
||||
RecalculateConditionValues();
|
||||
#if CLIENT
|
||||
Submarine.ForceVisibilityRecheck();
|
||||
@@ -2881,11 +2886,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
foreach (ItemComponent ic in components) { ic.Equip(character); }
|
||||
|
||||
CharacterHUD.RecreateHudTextsIfControlling(character);
|
||||
}
|
||||
|
||||
public void Unequip(Character character)
|
||||
{
|
||||
foreach (ItemComponent ic in components) { ic.Unequip(character); }
|
||||
CharacterHUD.RecreateHudTextsIfControlling(character);
|
||||
}
|
||||
|
||||
public List<(object obj, SerializableProperty property)> GetProperties<T>()
|
||||
@@ -3427,12 +3435,15 @@ namespace Barotrauma
|
||||
//if the item was in full condition considering the unmodified health
|
||||
//(not taking possible HealthMultipliers added by mods into account),
|
||||
//make sure it stays in full condition
|
||||
bool wasFullCondition = prevCondition >= item.Prefab.Health;
|
||||
if (wasFullCondition)
|
||||
if (item.condition > 0)
|
||||
{
|
||||
item.condition = item.MaxCondition;
|
||||
bool wasFullCondition = prevCondition >= item.Prefab.Health;
|
||||
if (wasFullCondition)
|
||||
{
|
||||
item.condition = item.MaxCondition;
|
||||
}
|
||||
item.condition = MathHelper.Clamp(item.condition, 0, item.MaxCondition);
|
||||
}
|
||||
item.condition = MathHelper.Clamp(item.condition, 0, item.MaxCondition);
|
||||
}
|
||||
item.lastSentCondition = item.condition;
|
||||
item.RecalculateConditionValues();
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IgnoreInEditor { get; set; }
|
||||
|
||||
private ImmutableHashSet<Identifier> excludedIdentifiers;
|
||||
public ImmutableHashSet<Identifier> ExcludedIdentifiers { get; private set; }
|
||||
|
||||
private RelationType type;
|
||||
|
||||
@@ -87,20 +87,20 @@ namespace Barotrauma
|
||||
|
||||
public string JoinedExcludedIdentifiers
|
||||
{
|
||||
get { return string.Join(",", excludedIdentifiers); }
|
||||
get { return string.Join(",", ExcludedIdentifiers); }
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
excludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet();
|
||||
ExcludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesItem(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (excludedIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
|
||||
foreach (var excludedIdentifier in excludedIdentifiers)
|
||||
if (ExcludedIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
|
||||
foreach (var excludedIdentifier in ExcludedIdentifiers)
|
||||
{
|
||||
if (item.HasTag(excludedIdentifier)) { return false; }
|
||||
}
|
||||
@@ -118,8 +118,8 @@ namespace Barotrauma
|
||||
public bool MatchesItem(ItemPrefab itemPrefab)
|
||||
{
|
||||
if (itemPrefab == null) { return false; }
|
||||
if (excludedIdentifiers.Contains(itemPrefab.Identifier)) { return false; }
|
||||
foreach (var excludedIdentifier in excludedIdentifiers)
|
||||
if (ExcludedIdentifiers.Contains(itemPrefab.Identifier)) { return false; }
|
||||
foreach (var excludedIdentifier in ExcludedIdentifiers)
|
||||
{
|
||||
if (itemPrefab.Tags.Contains(excludedIdentifier)) { return false; }
|
||||
}
|
||||
@@ -138,7 +138,7 @@ namespace Barotrauma
|
||||
public RelatedItem(Identifier[] identifiers, Identifier[] excludedIdentifiers)
|
||||
{
|
||||
this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
|
||||
this.excludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
|
||||
this.ExcludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
|
||||
|
||||
statusEffects = new List<StatusEffect>();
|
||||
}
|
||||
@@ -230,7 +230,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute(nameof(ItemPos), ItemPos.Value));
|
||||
}
|
||||
|
||||
if (excludedIdentifiers.Count > 0)
|
||||
if (ExcludedIdentifiers.Count > 0)
|
||||
{
|
||||
element.Add(new XAttribute("excludedidentifiers", JoinedExcludedIdentifiers));
|
||||
}
|
||||
|
||||
@@ -228,6 +228,9 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 edgeDiff = edge.Point2 - edge.Point1;
|
||||
Vector2 edgeDir = Vector2.Normalize(edgeDiff);
|
||||
|
||||
//If the edge is next to an empty cell and there's another solid cell at the other side of the empty one,
|
||||
//don't touch this edge. Otherwise we may end up closing off small passages between cells.
|
||||
var adjacentEmptyCell = edge.AdjacentCell(cell);
|
||||
@@ -238,8 +241,10 @@ namespace Barotrauma
|
||||
//find the edge at the opposite side of the adjacent cell
|
||||
foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges)
|
||||
{
|
||||
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) > 0 &&
|
||||
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType != CellType.Solid)
|
||||
if (otherEdge == edge || otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType != CellType.Solid) { continue; }
|
||||
Vector2 otherEdgeDir = Vector2.Normalize(otherEdge.Point2 - otherEdge.Point1);
|
||||
//dot product is > 0.7 if the edges are roughly parallel
|
||||
if (Math.Abs(Vector2.Dot(otherEdgeDir, edgeDir)) > 0.7f)
|
||||
{
|
||||
adjacentEdge = otherEdge;
|
||||
break;
|
||||
@@ -251,13 +256,11 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
List<Vector2> edgePoints = new List<Vector2>();
|
||||
Vector2 edgeNormal = edge.GetNormal(cell);
|
||||
|
||||
float edgeLength = Vector2.Distance(edge.Point1, edge.Point2);
|
||||
int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
|
||||
Vector2 edgeDir = edge.Point2 - edge.Point1;
|
||||
for (int i = 0; i <= pointCount; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
@@ -275,7 +278,7 @@ namespace Barotrauma
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.ServerAndClient);
|
||||
Vector2 extrudedPoint =
|
||||
edge.Point1 +
|
||||
edgeDir * (i / (float)pointCount) +
|
||||
edgeDiff * (i / (float)pointCount) +
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF;
|
||||
|
||||
var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2);
|
||||
|
||||
@@ -709,7 +709,7 @@ namespace Barotrauma
|
||||
if (Rand.Range(0, 10, Rand.RandSync.ServerAndClient) != 0) { continue; }
|
||||
}
|
||||
|
||||
if (!TooClose(siteX, siteY))
|
||||
if (!TooCloseToOtherSites(siteX, siteY))
|
||||
{
|
||||
siteCoordsX.Add(siteX);
|
||||
siteCoordsY.Add(siteY);
|
||||
@@ -717,14 +717,14 @@ namespace Barotrauma
|
||||
|
||||
if (closeToCave)
|
||||
{
|
||||
for (int x2 = x; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
for (int x2 = x - siteInterval.X; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
{
|
||||
for (int y2 = y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
for (int y2 = y - siteInterval.Y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
{
|
||||
int caveSiteX = x2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
int caveSiteY = y2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
|
||||
if (!TooClose(caveSiteX, caveSiteY))
|
||||
if (!TooCloseToOtherSites(caveSiteX, caveSiteY, caveSiteInterval))
|
||||
{
|
||||
siteCoordsX.Add(caveSiteX);
|
||||
siteCoordsY.Add(caveSiteY);
|
||||
@@ -735,11 +735,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool TooClose(double siteX, double siteY)
|
||||
bool TooCloseToOtherSites(double siteX, double siteY, float minDistance = 10.0f)
|
||||
{
|
||||
float minDistanceSqr = minDistance * minDistance;
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
if (MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteX, siteY) < 10.0f * 10.0f)
|
||||
if (MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteX, siteY) < minDistanceSqr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -720,7 +720,7 @@ namespace Barotrauma
|
||||
|
||||
private void HandleLevelCollision(Impact impact, VoronoiCell cell = null)
|
||||
{
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10)
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.RoundDuration < 10)
|
||||
{
|
||||
//ignore level collisions for the first 10 seconds of the round in case the sub spawns in a way that causes it to hit a wall
|
||||
//(e.g. level without outposts to dock to and an incorrectly configured ballast that makes the sub go up)
|
||||
|
||||
@@ -23,13 +23,22 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
Success = 0x00,
|
||||
Heartbeat = 0x01,
|
||||
RequestShutdown = 0xCC,
|
||||
Crash = 0xFF
|
||||
}
|
||||
|
||||
private static ManualResetEvent writeManualResetEvent;
|
||||
|
||||
private static volatile bool shutDown;
|
||||
public static bool HasShutDown => shutDown;
|
||||
private enum StatusEnum
|
||||
{
|
||||
NeverStarted,
|
||||
Active,
|
||||
RequestedShutDown,
|
||||
ShutDown
|
||||
}
|
||||
|
||||
private static volatile StatusEnum status = StatusEnum.NeverStarted;
|
||||
public static bool HasShutDown => status is StatusEnum.ShutDown;
|
||||
|
||||
private const int ReadBufferSize = MsgConstants.MTU * 2;
|
||||
private static byte[] readTempBytes;
|
||||
@@ -38,7 +47,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static ConcurrentQueue<byte[]> msgsToWrite;
|
||||
private static ConcurrentQueue<string> errorsToWrite;
|
||||
|
||||
|
||||
private static ConcurrentQueue<byte[]> msgsToRead;
|
||||
|
||||
private static Thread readThread;
|
||||
@@ -48,6 +57,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static void PrivateStart()
|
||||
{
|
||||
status = StatusEnum.Active;
|
||||
|
||||
readIncOffset = 0;
|
||||
readIncTotal = 0;
|
||||
|
||||
@@ -58,8 +69,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
msgsToRead = new ConcurrentQueue<byte[]>();
|
||||
|
||||
shutDown = false;
|
||||
|
||||
readCancellationToken = new CancellationTokenSource();
|
||||
|
||||
writeManualResetEvent = new ManualResetEvent(false);
|
||||
@@ -80,7 +89,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static void PrivateShutDown()
|
||||
{
|
||||
shutDown = true;
|
||||
if (Thread.CurrentThread != GameMain.MainThread)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot call {nameof(ChildServerRelay)}.{nameof(PrivateShutDown)} from a thread other than the main one");
|
||||
}
|
||||
if (status is StatusEnum.NeverStarted) { return; }
|
||||
status = StatusEnum.ShutDown;
|
||||
writeManualResetEvent?.Set();
|
||||
readCancellationToken?.Cancel();
|
||||
readThread?.Join(); readThread = null;
|
||||
@@ -93,18 +108,18 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
|
||||
private static int ReadIncomingMsgs()
|
||||
private static Option<int> ReadIncomingMsgs()
|
||||
{
|
||||
Task<int> readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token);
|
||||
if (readTask is null) { return -1; }
|
||||
if (readTask is null) { return Option<int>.None(); }
|
||||
|
||||
int timeOutMilliseconds = 100;
|
||||
for (int i = 0; i < 150; i++)
|
||||
{
|
||||
if (shutDown)
|
||||
if (status is StatusEnum.ShutDown)
|
||||
{
|
||||
readCancellationToken?.Cancel();
|
||||
return -1;
|
||||
return Option<int>.None();
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -115,36 +130,36 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
catch (AggregateException aggregateException)
|
||||
{
|
||||
if (aggregateException.InnerException is OperationCanceledException) { return -1; }
|
||||
if (aggregateException.InnerException is OperationCanceledException) { return Option<int>.None(); }
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return -1;
|
||||
return Option<int>.None();
|
||||
}
|
||||
}
|
||||
|
||||
if (readTask.Status != TaskStatus.RanToCompletion)
|
||||
if (readTask.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
bool swallowException = shutDown
|
||||
&& ((readTask.Exception?.InnerException is ObjectDisposedException)
|
||||
|| (readTask.Exception?.InnerException is System.IO.IOException));
|
||||
if (swallowException)
|
||||
{
|
||||
readCancellationToken?.Cancel();
|
||||
return -1;
|
||||
}
|
||||
throw new Exception(
|
||||
$"ChildServerRelay readTask did not run to completion: status was {readTask.Status}.",
|
||||
readTask.Exception);
|
||||
return Option<int>.Some(readTask.Result);
|
||||
}
|
||||
|
||||
return readTask.Result;
|
||||
bool swallowException =
|
||||
status is not StatusEnum.Active
|
||||
&& readTask.Exception?.InnerException is ObjectDisposedException or System.IO.IOException;
|
||||
if (swallowException)
|
||||
{
|
||||
readCancellationToken?.Cancel();
|
||||
return Option<int>.None();
|
||||
}
|
||||
throw new Exception(
|
||||
$"ChildServerRelay readTask did not run to completion: status was {readTask.Status}.",
|
||||
readTask.Exception);
|
||||
}
|
||||
|
||||
private static void CheckPipeConnected(string name, PipeType pipe)
|
||||
{
|
||||
if (!(pipe is { IsConnected: true }))
|
||||
if (status is StatusEnum.Active && pipe is not { IsConnected: true })
|
||||
{
|
||||
throw new Exception($"{name} was disconnected unexpectedly");
|
||||
}
|
||||
@@ -155,7 +170,7 @@ namespace Barotrauma.Networking
|
||||
private static void UpdateRead()
|
||||
{
|
||||
Span<byte> msgLengthSpan = stackalloc byte[4 + 1];
|
||||
while (!shutDown)
|
||||
while (!HasShutDown)
|
||||
{
|
||||
CheckPipeConnected(nameof(readStream), readStream);
|
||||
|
||||
@@ -165,10 +180,9 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (readIncOffset >= readIncTotal)
|
||||
{
|
||||
readIncTotal = ReadIncomingMsgs();
|
||||
if (!ReadIncomingMsgs().TryUnwrap(out readIncTotal)) { return false; }
|
||||
readIncOffset = 0;
|
||||
if (readIncTotal == 0) { Thread.Yield(); continue; }
|
||||
if (readIncTotal < 0) { return false; }
|
||||
}
|
||||
readTo[i] = readTempBytes[readIncOffset];
|
||||
readIncOffset++;
|
||||
@@ -176,7 +190,7 @@ namespace Barotrauma.Networking
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!readBytes(msgLengthSpan)) { shutDown = true; break; }
|
||||
if (!readBytes(msgLengthSpan)) { status = StatusEnum.ShutDown; break; }
|
||||
|
||||
int msgLength = msgLengthSpan[0]
|
||||
| (msgLengthSpan[1] << 8)
|
||||
@@ -184,24 +198,24 @@ namespace Barotrauma.Networking
|
||||
| (msgLengthSpan[3] << 24);
|
||||
WriteStatus writeStatus = (WriteStatus)msgLengthSpan[4];
|
||||
|
||||
if (msgLength > 0)
|
||||
{
|
||||
byte[] msg = new byte[msgLength];
|
||||
if (!readBytes(msg.AsSpan())) { shutDown = true; break; }
|
||||
byte[] msg = msgLength > 0 ? new byte[msgLength] : Array.Empty<byte>();
|
||||
if (msg.Length > 0 && !readBytes(msg.AsSpan())) { status = StatusEnum.ShutDown; break; }
|
||||
|
||||
switch (writeStatus)
|
||||
{
|
||||
case WriteStatus.Success:
|
||||
msgsToRead.Enqueue(msg);
|
||||
break;
|
||||
case WriteStatus.Heartbeat:
|
||||
//do nothing
|
||||
break;
|
||||
case WriteStatus.Crash:
|
||||
HandleCrashString(Encoding.UTF8.GetString(msg));
|
||||
shutDown = true;
|
||||
break;
|
||||
}
|
||||
switch (writeStatus)
|
||||
{
|
||||
case WriteStatus.Success:
|
||||
msgsToRead.Enqueue(msg);
|
||||
break;
|
||||
case WriteStatus.Heartbeat:
|
||||
//do nothing
|
||||
break;
|
||||
case WriteStatus.RequestShutdown:
|
||||
status = StatusEnum.ShutDown;
|
||||
break;
|
||||
case WriteStatus.Crash:
|
||||
HandleCrashString(Encoding.UTF8.GetString(msg));
|
||||
status = StatusEnum.ShutDown;
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.Yield();
|
||||
@@ -210,13 +224,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static void UpdateWrite()
|
||||
{
|
||||
while (!shutDown)
|
||||
while (!HasShutDown)
|
||||
{
|
||||
CheckPipeConnected(nameof(writeStream), writeStream);
|
||||
|
||||
byte[] msg;
|
||||
|
||||
void writeMsg(WriteStatus writeStatus)
|
||||
void writeMsg(WriteStatus writeStatus, byte[] msg)
|
||||
{
|
||||
// It's SUPER IMPORTANT that this stack allocation
|
||||
// remains in this local function and is never inlined,
|
||||
@@ -224,21 +236,19 @@ namespace Barotrauma.Networking
|
||||
// when the function returns; placing it in the loop
|
||||
// this method is based around would lead to a stack
|
||||
// overflow real quick!
|
||||
Span<byte> bytesToWrite = stackalloc byte[4 + 1 + msg.Length];
|
||||
Span<byte> headerBytes = stackalloc byte[4 + 1];
|
||||
|
||||
bytesToWrite[0] = (byte)(msg.Length & 0xFF);
|
||||
bytesToWrite[1] = (byte)((msg.Length >> 8) & 0xFF);
|
||||
bytesToWrite[2] = (byte)((msg.Length >> 16) & 0xFF);
|
||||
bytesToWrite[3] = (byte)((msg.Length >> 24) & 0xFF);
|
||||
headerBytes[0] = (byte)(msg.Length & 0xFF);
|
||||
headerBytes[1] = (byte)((msg.Length >> 8) & 0xFF);
|
||||
headerBytes[2] = (byte)((msg.Length >> 16) & 0xFF);
|
||||
headerBytes[3] = (byte)((msg.Length >> 24) & 0xFF);
|
||||
|
||||
bytesToWrite[4] = (byte)writeStatus;
|
||||
Span<byte> msgSlice = bytesToWrite.Slice(4 + 1, msg.Length);
|
||||
|
||||
msg.AsSpan().CopyTo(msgSlice);
|
||||
headerBytes[4] = (byte)writeStatus;
|
||||
|
||||
try
|
||||
{
|
||||
writeStream?.Write(bytesToWrite);
|
||||
writeStream?.Write(headerBytes);
|
||||
writeStream?.Write(msg);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -246,7 +256,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
case ObjectDisposedException _:
|
||||
case System.IO.IOException _:
|
||||
if (!shutDown) { throw; }
|
||||
if (!HasShutDown) { throw; }
|
||||
break;
|
||||
default:
|
||||
throw;
|
||||
@@ -254,29 +264,34 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
if (status is StatusEnum.RequestedShutDown)
|
||||
{
|
||||
writeMsg(WriteStatus.RequestShutdown, Array.Empty<byte>());
|
||||
status = StatusEnum.ShutDown;
|
||||
}
|
||||
|
||||
while (errorsToWrite.TryDequeue(out var error))
|
||||
{
|
||||
msg = Encoding.UTF8.GetBytes(error);
|
||||
writeMsg(WriteStatus.Crash);
|
||||
shutDown = true;
|
||||
writeMsg(WriteStatus.Crash, Encoding.UTF8.GetBytes(error));
|
||||
status = StatusEnum.ShutDown;
|
||||
}
|
||||
|
||||
while (msgsToWrite.TryDequeue(out msg))
|
||||
while (msgsToWrite.TryDequeue(out var msg))
|
||||
{
|
||||
writeMsg(WriteStatus.Success);
|
||||
writeMsg(WriteStatus.Success, msg);
|
||||
|
||||
if (shutDown) { break; }
|
||||
if (HasShutDown) { break; }
|
||||
}
|
||||
|
||||
if (!shutDown)
|
||||
if (!HasShutDown)
|
||||
{
|
||||
writeManualResetEvent.Reset();
|
||||
if (!writeManualResetEvent.WaitOne(1000))
|
||||
{
|
||||
if (shutDown) { return; }
|
||||
if (HasShutDown) { return; }
|
||||
|
||||
//heartbeat to keep the other end alive
|
||||
msg = Array.Empty<byte>(); writeMsg(WriteStatus.Heartbeat);
|
||||
writeMsg(WriteStatus.Heartbeat, Array.Empty<byte>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +299,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public static void Write(byte[] msg)
|
||||
{
|
||||
if (shutDown) { return; }
|
||||
if (HasShutDown) { return; }
|
||||
|
||||
if (msg.Length > 0x1fff_ffff)
|
||||
{
|
||||
@@ -298,7 +313,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public static bool Read(out byte[] msg)
|
||||
{
|
||||
if (shutDown) { msg = null; return false; }
|
||||
if (HasShutDown) { msg = null; return false; }
|
||||
|
||||
return msgsToRead.TryDequeue(out msg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user