Faction Test 100.8.0.0

This commit is contained in:
Markus Isberg
2022-12-01 22:00:25 +02:00
parent 0057f5bfce
commit f7f1ebd979
66 changed files with 965 additions and 505 deletions

View File

@@ -9,7 +9,7 @@ using System.Linq;
namespace Barotrauma
{
class CharacterHUD
partial class CharacterHUD
{
const float BossHealthBarDuration = 120.0f;
@@ -147,8 +147,8 @@ namespace Barotrauma
}
}
public static bool ShouldRecreateHudTexts { get; set; } = true;
private static bool heldDownShiftWhenGotHudTexts;
public static bool RecreateHudTexts { get; set; } = true;
private static bool lastHudTextsContextual;
private static float timeHealthWindowClosed;
public static bool IsCampaignInterfaceOpen =>
@@ -266,7 +266,7 @@ namespace Barotrauma
if (focusedItemOverlayTimer <= 0.0f)
{
focusedItem = null;
ShouldRecreateHudTexts = true;
RecreateHudTexts = true;
}
}
}
@@ -388,7 +388,7 @@ namespace Barotrauma
if (focusedItem != character.FocusedItem)
{
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
ShouldRecreateHudTexts = true;
RecreateHudTexts = true;
}
focusedItem = character.FocusedItem;
}
@@ -412,14 +412,14 @@ namespace Barotrauma
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{
bool shiftDown = PlayerInput.IsShiftDown();
if (ShouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown)
bool hudTextsContextual = PlayerInput.IsShiftDown();
if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
{
ShouldRecreateHudTexts = true;
heldDownShiftWhenGotHudTexts = shiftDown;
RecreateHudTexts = true;
lastHudTextsContextual = hudTextsContextual;
}
var hudTexts = focusedItem.GetHUDTexts(character, ShouldRecreateHudTexts);
ShouldRecreateHudTexts = false;
var hudTexts = focusedItem.GetHUDTexts(character, RecreateHudTexts);
RecreateHudTexts = false;
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
@@ -864,5 +864,25 @@ namespace Barotrauma
Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
}
static partial void RecreateHudTextsIfControllingProjSpecific(Character character)
{
if (character == Character.Controlled)
{
RecreateHudTexts = true;
}
}
static partial void RecreateHudTextsIfFocusedProjSpecific(params Item[] items)
{
foreach (var item in items)
{
if (item == Character.Controlled?.FocusedItem)
{
RecreateHudTexts = true;
break;
}
}
}
}
}

View File

@@ -30,7 +30,7 @@ namespace Barotrauma
public void ClientExecute(string[] args)
{
bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is EditorScreen);
bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is { IsEditor: true });
if (!allowCheats && !CheatsEnabled && IsCheat)
{
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red);

View File

@@ -269,6 +269,8 @@ namespace Barotrauma
selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
var specializationCount = tree.TalentSubTrees.Count(t => t.Type == TalentTreeType.Specialization);
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in tree.TalentSubTrees)
{
@@ -310,7 +312,7 @@ namespace Barotrauma
for (int i = 0; i < optionAmount; i++)
{
TalentOption option = subTree.TalentOptionStages[i];
CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info);
CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info, specializationCount);
}
subTreeLayoutGroup.RectTransform.Resize(new Point(subTreeLayoutGroup.Rect.Width,
subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing)));
@@ -327,7 +329,12 @@ namespace Barotrauma
var specializationList = GetSpecializationList();
//resize (scale up) children if there's less than 3 of them to make them cover the whole width of the menu
specializationList.Content.RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height),
resizeChildren: specializationList.Content.Children.Count() < 3);
resizeChildren: specializationCount < 3);
//make room for scrollbar if there's more than the default amount of specializations
if (specializationCount > 3)
{
specializationList.RectTransform.MinSize = new Point(specializationList.Rect.Width, specializationList.Content.Rect.Height + (int)(specializationList.ScrollBar.Rect.Height * 0.9f));
}
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
@@ -337,17 +344,17 @@ namespace Barotrauma
{
return specList;
}
GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
return newSpecializationList;
}
}
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info)
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info, int specializationCount)
{
int elementPadding = GUI.IntScale(8);
int height = GUI.IntScale((GameMain.GameSession?.Campaign == null ? 65 : 60) * (specializationCount > 3 ? 0.97f : 1.0f));
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.01f), parent.RectTransform, anchor: Anchor.TopCenter)
{ MinSize = new Point(0, GUI.IntScale(65)) }, style: null);
{ MinSize = new Point(0, height) }, style: null);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;

View File

@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
@@ -117,7 +118,7 @@ namespace Barotrauma
private void UpdateBar()
{
double elapsedTime = (DateTime.Now - startTime).TotalSeconds;
if (msgBox != null && !msgBox.Closed && GUIMessageBox.MessageBoxes.Contains(msgBox))
if (msgBox is { Closed: false } && GUIMessageBox.MessageBoxes.Contains(msgBox))
{
if (msgBox.FindChild(TimerData, true) is GUIProgressBar bar)
{
@@ -129,7 +130,7 @@ namespace Barotrauma
int second = (int)Math.Ceiling(elapsedTime);
if (second > lastSecond)
{
if (msgBox != null && !msgBox.Closed)
if (msgBox is { Closed: false })
{
SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
}
@@ -137,6 +138,19 @@ namespace Barotrauma
}
}
private static void CloseLingeringPopups()
{
foreach (GUIComponent box in GUIMessageBox.MessageBoxes.ToImmutableArray())
{
if (box is not GUIMessageBox msgBox) { continue; }
if (msgBox.UserData is PromptData or ResultData)
{
msgBox.Close();
}
}
}
public static void ClientRead(IReadMessage inc)
{
ReadyCheckState state = (ReadyCheckState)inc.ReadByte();
@@ -154,6 +168,8 @@ namespace Barotrauma
switch (state)
{
case ReadyCheckState.Start:
CloseLingeringPopups();
bool isOwn = false;
byte authorId = 0;
@@ -175,8 +191,8 @@ namespace Barotrauma
clients.Add(inc.ReadByte());
}
ReadyCheck rCheck = new ReadyCheck(clients,
DateTimeOffset.FromUnixTimeSeconds(startTime).LocalDateTime,
ReadyCheck rCheck = new ReadyCheck(clients,
DateTimeOffset.FromUnixTimeSeconds(startTime).LocalDateTime,
DateTimeOffset.FromUnixTimeSeconds(endTime).LocalDateTime);
crewManager.ActiveReadyCheck = rCheck;
@@ -224,7 +240,7 @@ namespace Barotrauma
if (IsFinished) { return; }
IsFinished = true;
int readyCount = Clients.Count(pair => pair.Value == ReadyStatus.Yes);
int readyCount = Clients.Count(static pair => pair.Value == ReadyStatus.Yes);
int totalCount = Clients.Count;
GameMain.Client.AddChatMessage(ChatMessage.Create(string.Empty, readyCheckStatus(readyCount, totalCount).Value, ChatMessageType.Server, null));
}
@@ -238,31 +254,29 @@ namespace Barotrauma
if (resultsBox == null || resultsBox.Closed || !GUIMessageBox.MessageBoxes.Contains(resultsBox)) { return; }
if (resultsBox.Content.FindChild(UserListData) is GUIListBox userList)
if (resultsBox.Content.FindChild(UserListData) is not GUIListBox userList) { return; }
// for some reason FindChild doesn't work here?
foreach (GUIComponent child in userList.Content.Children)
{
// for some reason FindChild doesn't work here?
foreach (GUIComponent child in userList.Content.Children)
if (child.UserData is not byte b || b != id) { continue; }
if (child.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is not GUIImage image) { continue; }
string style;
switch (status)
{
if (!(child.UserData is byte b) || b != id) { continue; }
if (child.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is GUIImage image)
{
string style;
switch (status)
{
case ReadyStatus.Yes:
style = "MissionCompletedIcon";
break;
case ReadyStatus.No:
style = "MissionFailedIcon";
break;
default:
return;
}
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
}
case ReadyStatus.Yes:
style = "MissionCompletedIcon";
break;
case ReadyStatus.No:
style = "MissionFailedIcon";
break;
default:
return;
}
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
}
}

View File

@@ -327,17 +327,20 @@ namespace Barotrauma.Items.Components
var item1 = c1.GUIComponent.UserData as FabricationRecipe;
var item2 = c2.GUIComponent.UserData as FabricationRecipe;
int itemPlacement1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f ? 0 : -1;
int itemPlacement2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f ? 0 : -1;
itemPlacement1 += item1.RequiresRecipe && !character.HasRecipeForItem(item1.TargetItem.Identifier) ? -2 : 0;
itemPlacement2 += item2.RequiresRecipe && !character.HasRecipeForItem(item2.TargetItem.Identifier) ? -2 : 0;
int itemPlacement1 = calculatePlacement(item1);
int itemPlacement2 = calculatePlacement(item2);
if (itemPlacement1 != itemPlacement2)
{
return itemPlacement1 > itemPlacement2 ? -1 : 1;
}
int calculatePlacement(FabricationRecipe recipe)
{
int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
placement += recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem) ? -2 : 0;
return placement;
}
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
});
@@ -372,7 +375,9 @@ namespace Barotrauma.Items.Components
AutoScaleHorizontal = true,
CanBeFocused = false
};
var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && (fabricableItem.RequiresRecipe && !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier)));
var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c =>
c.UserData is FabricationRecipe fabricableItem &&
fabricableItem.RequiresRecipe && !AnyOneHasRecipeForItem(character, fabricableItem.TargetItem));
if (firstRequiresRecipe != null)
{
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));

View File

@@ -144,6 +144,9 @@ namespace Barotrauma.Items.Components
partial class MiniMap : Powered
{
private Dictionary<Hull, HullData> hullDatas;
private DateTime resetDataTime;
private GUIFrame submarineContainer;
private GUIFrame? hullInfoFrame;
@@ -226,6 +229,8 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific()
{
hullDatas = new Dictionary<Hull, HullData>();
SetDefaultMode();
noPowerTip = TextManager.Get("SteeringNoPowerTip");
@@ -551,6 +556,34 @@ namespace Barotrauma.Items.Components
CreateHUD();
}
//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 (cardRefreshTimer > cardRefreshDelay)
{
if (item.Submarine is { } sub)
{
UpdateIDCards(sub);
}
cardRefreshTimer = 0;
}
else
{
cardRefreshTimer += deltaTime;
}
if (scissorComponent != null)
{
if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus)
@@ -1736,6 +1769,67 @@ namespace Barotrauma.Items.Components
return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
}
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 not 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;
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();

View File

@@ -396,7 +396,9 @@ namespace Barotrauma.Items.Components
ToolTip = TextManager.Get("reactor.temperatureboostup"),
OnClicked = (_, __) =>
{
applyTemperatureBoost(TemperatureBoostAmount, temperatureBoostSoundUp);
unsentChanges = true;
sendUpdateTimer = 0.0f;
ApplyTemperatureBoost(TemperatureBoostAmount);
return true;
}
};
@@ -407,25 +409,13 @@ namespace Barotrauma.Items.Components
ToolTip = TextManager.Get("reactor.temperatureboostdown"),
OnClicked = (_, __) =>
{
applyTemperatureBoost(-TemperatureBoostAmount, temperatureBoostSoundDown);
unsentChanges = true;
sendUpdateTimer = 0.0f;
ApplyTemperatureBoost(-TemperatureBoostAmount);
return true;
}
};
void applyTemperatureBoost(float amount, RoundSound sound)
{
temperatureBoost = amount;
if (sound != null)
{
SoundPlayer.PlaySound(
sound.Sound,
item.WorldPosition,
sound.Volume,
sound.Range,
hullGuess: item.CurrentHull);
}
}
var graphArea = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), bottomRightArea.RectTransform))
{
Stretch = true,
@@ -471,6 +461,24 @@ namespace Barotrauma.Items.Components
}
};
}
private void ApplyTemperatureBoost(float amount)
{
if (Math.Abs(temperatureBoost) <= TemperatureBoostAmount * 0.9f &&
Math.Abs(amount) > TemperatureBoostAmount * 0.9f)
{
var sound = amount > 0 ? temperatureBoostSoundUp : temperatureBoostSoundDown;
if (sound != null)
{
SoundPlayer.PlaySound(
sound.Sound,
item.WorldPosition,
sound.Volume,
sound.Range,
hullGuess: item.CurrentHull);
}
}
temperatureBoost = amount;
}
private void InitInventoryUI()
{
@@ -895,6 +903,7 @@ namespace Barotrauma.Items.Components
msg.WriteBoolean(PowerOn);
msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(temperatureBoost, -TemperatureBoostAmount, TemperatureBoostAmount, 8);
correctionTimer = CorrectionDelay;
}
@@ -903,7 +912,7 @@ namespace Barotrauma.Items.Components
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8), sendingTime);
StartDelayedCorrection(msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8 + 8), sendingTime);
return;
}
@@ -913,6 +922,7 @@ namespace Barotrauma.Items.Components
TargetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
TargetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8);
ApplyTemperatureBoost(msg.ReadRangedSingle(-TemperatureBoostAmount, TemperatureBoostAmount, 8));
if (Math.Abs(FissionRateScrollBar.BarScroll - TargetFissionRate / 100.0f) > 0.01f)
{

View File

@@ -62,6 +62,7 @@ namespace Barotrauma.Items.Components
private float displayBorderSize;
private List<SonarBlip> sonarBlips;
private readonly HashSet<SonarBlip> prevBlips = new HashSet<SonarBlip>();
private float prevPassivePingRadius;
@@ -817,24 +818,33 @@ namespace Barotrauma.Items.Components
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
float dist = (float)Math.Sqrt(distSqr);
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500 && t.IsWithinSector(transducerCenter))
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500)
{
int prevBlipCount = sonarBlips.Count;
prevBlips.Clear();
foreach (var blip in sonarBlips)
{
prevBlips.Add(blip);
}
Ping(t.WorldPosition, transducerCenter,
Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f),
t.SoundRange * displayScale, 0, displayScale, range,
passive: true, pingStrength: 0.5f);
sonarBlips.Add(new SonarBlip(t.WorldPosition, 1.0f, 1.0f));
//remove blips that weren't in the AITarget's sector
if (t.HasSector())
{
for (int i = sonarBlips.Count - 1; i >= prevBlipCount; i--)
for (int i = sonarBlips.Count - 1; i >= 0; i--)
{
if (prevBlips.Contains(sonarBlips[i])) { continue; }
if (!t.IsWithinSector(sonarBlips[i].Position))
{
sonarBlips.RemoveAt(i);
}
}
}
if (t.IsWithinSector(transducerCenter))
{
sonarBlips.Add(new SonarBlip(t.WorldPosition, fadeTimer: 1.0f, scale: MathHelper.Clamp(t.SoundRange / 2000, 1.0f, 5.0f)));
}
}
}
}

View File

@@ -97,7 +97,7 @@ namespace Barotrauma.Items.Components
var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), chargeTextContainer.RectTransform, Anchor.CenterRight),
"", textColor: GUIStyle.TextColorNormal, font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
{
TextGetter = () => $"{(int)MathF.Round(charge)}/{(int)capacity} {kWmin} ({(int)MathF.Round(MathUtils.Percentage(charge, capacity))} %)"
TextGetter = () => $"{(int)MathF.Round(charge)}/{(int)adjustedCapacity} {kWmin} ({(int)MathF.Round(MathUtils.Percentage(charge, adjustedCapacity))} %)"
};
if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUIStyle.SmallFont; }
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
{
ProgressGetter = () =>
{
return capacity <= 0.0f ? 1.0f : charge / capacity;
return adjustedCapacity <= 0.0f ? 1.0f : charge / adjustedCapacity;
}
};
}
@@ -126,7 +126,7 @@ namespace Barotrauma.Items.Components
{
if (chargeIndicator != null)
{
float chargeRatio = charge / capacity;
float chargeRatio = charge / adjustedCapacity;
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
}
}
@@ -144,9 +144,9 @@ namespace Barotrauma.Items.Components
Matrix rotate = Matrix.CreateRotationZ(item.RotationRad);
Vector2 center = Vector2.Transform((indicatorPos + (scaledIndicatorSize * 0.5f)) * flip, rotate) + itemPosition;
if (charge > 0 && capacity > 0)
if (charge > 0 && adjustedCapacity > 0)
{
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
float chargeRatio = MathHelper.Clamp(charge / adjustedCapacity, 0.0f, 1.0f);
Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
Vector2 indicatorCenter = (indicatorPos + (scaledIndicatorSize * 0.5f)) * flip;
Vector2 indicatorSize;
@@ -193,7 +193,7 @@ namespace Barotrauma.Items.Components
rechargeSpeedSlider.BarScroll = rechargeRate;
}
#endif
Charge = msg.ReadRangedSingle(0.0f, 1.0f, 8) * capacity;
Charge = msg.ReadRangedSingle(0.0f, 1.0f, 8) * adjustedCapacity;
}
}
}

View File

@@ -1608,28 +1608,79 @@ namespace Barotrauma
{
containedState = item.Condition / item.MaxCondition;
}
else if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
{
int ignoredItems = itemContainer.AllSubContainableItems == null ? 0 : itemContainer.AllSubContainableItems.Count;
int itemCount = itemContainer.Inventory.AllItems.Count() - ignoredItems;
containedState = itemCount / (float)(itemContainer.GetMaxStackSize(0) * itemContainer.MainContainerCapacity);
}
else
{
int targetSlot = Math.Max(itemContainer.ContainedStateIndicatorSlot, 0);
var containedItem = itemContainer.Inventory.slots[targetSlot].FirstOrDefault();
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && (itemContainer.Inventory.Capacity == 1 || itemContainer.HasSubContainers))
ItemSlot containedItemSlot = null;
if (targetSlot < itemContainer.Inventory.slots.Length)
{
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(targetSlot));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
containedItemSlot = itemContainer.Inventory.slots[targetSlot];
}
if (containedItemSlot != null)
{
Item containedItem = containedItemSlot.FirstOrDefault();
if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
{
containedState = itemContainer.Inventory.slots[targetSlot].Items.Count / (float)maxStackSize;
if (containedItem == null)
{
// No item on the defined slot, check if the items on other slots can be used.
containedItem = containedItemSlot.FirstOrDefault() ?? itemContainer.Inventory.AllItems.FirstOrDefault(it => itemContainer.CanBeContained(it, targetSlot));
}
if (containedItem != null)
{
int ignoredItemCount = 0;
var subContainableItems = itemContainer.AllSubContainableItems;
float capacity = itemContainer.GetMaxStackSize(targetSlot);
if (subContainableItems != null)
{
bool useMainContainerCapacity = true;
foreach (Item it in itemContainer.Inventory.AllItems)
{
// Ignore all items in the sub containers.
foreach (RelatedItem ri in subContainableItems)
{
if (ri.MatchesItem(containedItem))
{
// The target item is in a subcontainer -> inverse the logic.
useMainContainerCapacity = false;
break;
}
if (ri.MatchesItem(it))
{
ignoredItemCount++;
}
}
if (!useMainContainerCapacity) { break; }
}
if (useMainContainerCapacity)
{
capacity *= itemContainer.MainContainerCapacity;
}
else
{
// Ignore all items in the main container.
ignoredItemCount = itemContainer.Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
capacity *= itemContainer.Capacity - itemContainer.MainContainerCapacity;
}
}
int itemCount = itemContainer.Inventory.AllItems.Count() - ignoredItemCount;
containedState = Math.Min(itemCount / Math.Max(capacity, 1), 1);
}
}
else
{
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && (itemContainer.Inventory.Capacity == 1 || itemContainer.HasSubContainers))
{
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(targetSlot));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{
containedState = containedItemSlot.Items.Count / (float)maxStackSize;
}
}
}
}
}

View File

@@ -1256,11 +1256,9 @@ namespace Barotrauma
{
foreach (ItemComponent ic in components)
{
if (ic.DisplayMsg.IsNullOrEmpty()) { continue; }
if (!ic.CanBePicked && !ic.CanBeSelected) { continue; }
if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; }
if (ic is ConnectionPanel connectionPanel && !connectionPanel.CanRewire()) { continue; }
Color color = Color.Gray;
if (ic.HasRequiredItems(character, false))
{
@@ -1273,6 +1271,7 @@ namespace Barotrauma
color = Color.Cyan;
}
}
if (ic.DisplayMsg.IsNullOrEmpty()) { continue; }
texts.Add(new ColoredText(ic.DisplayMsg.Value, color, false, false));
}
}

View File

@@ -1,12 +1,15 @@
using System.Diagnostics;
using System.IO.Pipes;
using System.Linq;
using System.Threading;
namespace Barotrauma.Networking
{
static partial class ChildServerRelay
{
public static Process Process;
public static bool IsProcessAlive => Process is { HasExited: false };
private static bool localHandlesDisposed;
private static AnonymousPipeServerStream writePipe;
private static AnonymousPipeServerStream readPipe;
@@ -44,18 +47,27 @@ namespace Barotrauma.Networking
localHandlesDisposed = true;
}
public static void ClosePipes()
public static void AttemptGracefulShutDown(int maxAttempts = 20)
{
writePipe?.Dispose(); writePipe = null;
readPipe?.Dispose(); readPipe = null;
shutDown = true;
status = StatusEnum.RequestedShutDown;
writeManualResetEvent?.Set();
int checks = 0;
while (Process is { HasExited: false })
{
if (checks >= maxAttempts)
{
DebugConsole.AddWarning("Server could not be shut down gracefully");
break;
}
Thread.Sleep(100);
checks++;
}
ForceShutDown();
}
public static void ShutDown()
public static void ForceShutDown()
{
Process?.Kill(); Process = null;
writePipe = null; readPipe = null;
PrivateShutDown();
}

View File

@@ -332,10 +332,27 @@ namespace Barotrauma.Networking
};
}
public void CreateServerCrashMessage()
{
// Close any message boxes that say "The server has crashed."
var basicServerCrashMsg = TextManager.Get($"{nameof(DisconnectReason)}.{nameof(DisconnectReason.ServerCrashed)}");
GUIMessageBox.MessageBoxes
.OfType<GUIMessageBox>()
.Where(mb => mb.Text?.Text == basicServerCrashMsg)
.ToArray()
.ForEach(mb => mb.Close());
// Open a new message box with the crash report path
if (GUIMessageBox.MessageBoxes.All(
mb => (mb as GUIMessageBox)?.Text?.Text != ChildServerRelay.CrashMessage))
{
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
}
}
private bool ReturnToPreviousMenu(GUIButton button, object obj)
{
Quit();
Submarine.Unload();
GameMain.Client = null;
GameMain.GameSession = null;
@@ -531,14 +548,10 @@ namespace Barotrauma.Networking
{
if (GameMain.WindowActive)
{
if (ChildServerRelay.Process?.HasExited ?? true)
if (!ChildServerRelay.IsProcessAlive)
{
Quit();
if (!GUIMessageBox.MessageBoxes.Any(mb => (mb as GUIMessageBox)?.Text?.Text == ChildServerRelay.CrashMessage))
{
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
}
CreateServerCrashMessage();
}
}
}
@@ -942,13 +955,6 @@ namespace Barotrauma.Networking
GUI.ClearCursorWait();
ChildServerRelay.ShutDown();
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.ClearRichPresence();
}
if (disconnectPacket.ShouldCreateAnalyticsEvent)
{
GameAnalyticsManager.AddErrorEventOnce(
@@ -974,11 +980,43 @@ namespace Barotrauma.Networking
}
else
{
ReturnToPreviousMenu(null, null);
new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage)
if (ClientPeer is SteamP2PClientPeer or SteamP2POwnerPeer)
{
DisplayInLoadingScreens = true
};
SteamManager.LeaveLobby();
}
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel();
if (SteamManager.IsInitialized)
{
Steamworks.SteamFriends.ClearRichPresence();
}
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
{
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
}
ChildServerRelay.AttemptGracefulShutDown();
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
characterInfo?.Remove();
VoipClient?.Dispose();
VoipClient = null;
GameMain.Client = null;
GameMain.GameSession = null;
ReturnToPreviousMenu(null, null);
if (disconnectPacket.DisconnectReason != DisconnectReason.Disconnected)
{
new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage)
{
DisplayInLoadingScreens = true
};
}
}
}
@@ -2538,46 +2576,9 @@ namespace Barotrauma.Networking
public void Quit()
{
if (ClientPeer is SteamP2PClientPeer || ClientPeer is SteamP2POwnerPeer)
{
SteamManager.LeaveLobby();
}
GameMain.ModDownloadScreen.Reset();
ContentPackageManager.EnabledPackages.Restore();
CampaignMode.StartRoundCancellationToken?.Cancel();
ClientPeer?.Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
ClientPeer = null;
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
{
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
}
if (ChildServerRelay.Process != null)
{
int checks = 0;
while (ChildServerRelay.Process is { HasExited: false })
{
if (checks > 10)
{
ChildServerRelay.ShutDown();
}
Thread.Sleep(100);
checks++;
}
}
ChildServerRelay.ShutDown();
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
characterInfo?.Remove();
VoipClient?.Dispose();
VoipClient = null;
GameMain.Client = null;
GameMain.GameSession = null;
}
public void SendCharacterInfo(string newName = null)

View File

@@ -91,15 +91,11 @@ namespace Barotrauma.Networking
ToolBox.ThrowIfNull(netClient);
ToolBox.ThrowIfNull(incomingLidgrenMessages);
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
if (isOwner && !ChildServerRelay.IsProcessAlive)
{
var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
gameClient?.CreateServerCrashMessage();
return;
}

View File

@@ -187,15 +187,11 @@ namespace Barotrauma.Networking
{
if (!isActive) { return; }
if (ChildServerRelay.HasShutDown || !(ChildServerRelay.Process is { HasExited: false }))
if (ChildServerRelay.HasShutDown || !ChildServerRelay.IsProcessAlive)
{
var gameClient = GameMain.Client;
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) =>
{
GameMain.MainMenuScreen.Select();
return false;
};
gameClient?.CreateServerCrashMessage();
return;
}
@@ -401,8 +397,6 @@ namespace Barotrauma.Networking
ClosePeerSession(remotePeers[i]);
}
ChildServerRelay.ClosePipes();
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
SteamManager.LeaveLobby();

View File

@@ -17,9 +17,7 @@ namespace Barotrauma.Networking
get;
private set;
}
public static IReadOnlyList<string> CaptureDeviceNames =>
Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
private readonly IntPtr captureDevice;
@@ -169,6 +167,11 @@ namespace Barotrauma.Networking
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, storedBufferID);
}
public static IReadOnlyList<string> GetCaptureDeviceNames()
{
return Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
}
IntPtr nativeBuffer;
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];

View File

@@ -11,8 +11,6 @@ namespace Barotrauma
{
partial class GameScreen : Screen
{
public override bool IsEditor => GameMain.GameSession?.GameMode is TestGameMode;
private RenderTarget2D renderTargetBackground;
private RenderTarget2D renderTarget;
private RenderTarget2D renderTargetWater;

View File

@@ -47,7 +47,14 @@ namespace Barotrauma
private GUITextBox serverNameBox, passwordBox, maxPlayersBox;
private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
private GUIDropDown serverExecutableDropdown;
private readonly GUIButton joinServerButton, hostServerButton, steamWorkshopButton;
private readonly GUIButton joinServerButton, hostServerButton;
private readonly GUIFrame modsButtonContainer;
private readonly GUIButton modsButton, modUpdatesButton;
private Task<IReadOnlyList<Steamworks.Ugc.Item>> modUpdateTask;
private float modUpdateTimer = 0.0f;
private const float ModUpdateInterval = 60.0f;
private readonly GameMain game;
private GUIImage playstyleBanner;
@@ -268,15 +275,29 @@ namespace Barotrauma
RelativeSpacing = 0.035f
};
#if USE_STEAM
steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
modsButtonContainer = new GUIFrame(new RectTransform(Vector2.One, customizeList.RectTransform),
style: null);
modsButton = new GUIButton(new RectTransform(Vector2.One, modsButtonContainer.RectTransform),
TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
Enabled = true,
UserData = Tab.SteamWorkshop,
OnClicked = SelectTab
};
#endif
modUpdatesButton = new GUIButton(new RectTransform(Vector2.One * 0.95f, modsButtonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "GUIUpdateButton")
{
ToolTip = TextManager.Get("ModUpdatesAvailable"),
OnClicked = (_, _) =>
{
BulkDownloader.PrepareUpdates();
return false;
},
Visible = false
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
@@ -525,6 +546,8 @@ namespace Barotrauma
#region Selection
public override void Select()
{
ResetModUpdateButton();
if (WorkshopItemsToUpdate.Any())
{
while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId))
@@ -711,6 +734,13 @@ namespace Barotrauma
}
#endregion
public void ResetModUpdateButton()
{
modUpdateTask = null;
modUpdateTimer = 0;
modUpdatesButton.Visible = false;
}
public void QuickStart(bool fixedSeed = false, Identifier sub = default, float difficulty = 50, LevelGenerationParams levelGenerationParams = null)
{
if (fixedSeed)
@@ -930,15 +960,32 @@ namespace Barotrauma
public override void Update(double deltaTime)
{
#if !DEBUG && USE_STEAM
modUpdateTimer -= (float)deltaTime;
if (modUpdateTimer <= 0.0f && modUpdateTask is not { IsCompleted: false })
{
modUpdateTask = BulkDownloader.GetItemsThatNeedUpdating();
modUpdateTimer = ModUpdateInterval;
}
if (GameSettings.CurrentConfig.UseSteamMatchmaking)
{
hostServerButton.Enabled = Steam.SteamManager.IsInitialized;
}
steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized;
#elif USE_STEAM
steamWorkshopButton.Enabled = true;
#endif
if (modUpdateTask is { IsCompletedSuccessfully: true })
{
modUpdatesButton.Visible = modUpdateTask.Result.Count > 0;
}
if (modUpdatesButton.Visible)
{
var modButtonLabelSize =
modsButton.Font.MeasureString(modsButton.Text).ToPoint()
+ new Point(GUI.IntScale(25));
modUpdatesButton.RectTransform.AbsoluteOffset =
(modButtonLabelSize.X, modsButton.Rect.Height / 2 - modUpdatesButton.Rect.Height / 2);
}
switch (selectedTab)
{
case Tab.NewGame:

View File

@@ -103,6 +103,10 @@ namespace Barotrauma
public GUIFrame JobPreferenceContainer;
public GUIListBox JobList;
private Identifier micIconStyle;
private float micCheckTimer;
const float MicCheckInterval = 1.0f;
private float autoRestartTimer;
//persistent characterinfo provided by the server
@@ -2656,27 +2660,9 @@ namespace Barotrauma
public override void Update(double deltaTime)
{
base.Update(deltaTime);
if (GameMain.Client == null) { return; }
Identifier currMicStyle = micIcon.Style.Element.NameAsIdentifier();
Identifier targetMicStyle = "GUIMicrophoneEnabled".ToIdentifier();
var voipCaptureDeviceNames = VoipCapture.CaptureDeviceNames;
if (voipCaptureDeviceNames.Count == 0)
{
targetMicStyle = "GUIMicrophoneUnavailable".ToIdentifier();
}
else if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Disabled)
{
targetMicStyle = "GUIMicrophoneDisabled".ToIdentifier();
}
if (targetMicStyle != currMicStyle)
{
GUIStyle.Apply(micIcon, targetMicStyle);
}
UpdateMicIcon((float)deltaTime);
foreach (GUIComponent child in PlayerList.Content.Children)
{
@@ -2738,6 +2724,35 @@ namespace Barotrauma
if (!mouseRect.Contains(PlayerInput.MousePosition)) { jobVariantTooltip = null; }
}
}
private void UpdateMicIcon(float deltaTime)
{
micCheckTimer -= deltaTime;
if (micCheckTimer > 0.0f) { return; }
Identifier newMicIconStyle = "GUIMicrophoneEnabled".ToIdentifier();
if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Disabled)
{
newMicIconStyle = "GUIMicrophoneDisabled".ToIdentifier();
}
else
{
var voipCaptureDeviceNames = VoipCapture.GetCaptureDeviceNames();
if (voipCaptureDeviceNames.Count == 0)
{
newMicIconStyle = "GUIMicrophoneUnavailable".ToIdentifier();
}
}
if (newMicIconStyle != micIconStyle)
{
micIconStyle = newMicIconStyle;
GUIStyle.Apply(micIcon, newMicIconStyle);
}
micCheckTimer = MicCheckInterval;
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
if (backgroundSprite?.Texture == null) { return; }

View File

@@ -354,9 +354,24 @@ namespace Barotrauma
ToolTip = RichString.Rich(TextManager.Get("SaveSubButton") + "‖color:125,125,125‖\nCtrl + S‖color:end‖"),
OnClicked = (btn, data) =>
{
#if DEBUG
if (ContentPackageManager.EnabledPackages.All.Any(cp => cp != ContentPackageManager.VanillaCorePackage && cp.Files.Any(f => f is not BaseSubFile)))
{
var msgBox = new GUIMessageBox("DEBUG-ONLY WARNING", "You currently have some mods enabled. Are you sure you want to save the submarine? If the mods override any vanilla content, saving the submarine may cause unintended changes.",
new LocalizedString[] { "Yes, I know what I'm doing", "Cancel" });
msgBox.Buttons[0].OnClicked = (btn, data) =>
{
msgBox.Close();
loadFrame = null;
CreateSaveScreen();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
return false;
}
#endif
loadFrame = null;
CreateSaveScreen();
return true;
}
};
@@ -3107,7 +3122,7 @@ namespace Barotrauma
return false;
}
private void SnapToGrid()
private static void SnapToGrid()
{
// First move components
foreach (MapEntity e in MapEntity.SelectedList)
@@ -3120,6 +3135,10 @@ namespace Barotrauma
var wire = item.GetComponent<Wire>();
if (wire != null) { continue; }
item.Move(offset);
if (item.GetComponent<Door>()?.LinkedGap is Gap linkedGap)
{
linkedGap.Move(item.Position - linkedGap.Position);
}
}
else if (e is Structure structure)
{
@@ -3144,7 +3163,7 @@ namespace Barotrauma
}
}
private IEnumerable<SubmarineInfo> GetLoadableSubs()
private static IEnumerable<SubmarineInfo> GetLoadableSubs()
{
string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
return SubmarineInfo.SavedSubmarines.Where(s
@@ -3531,9 +3550,18 @@ namespace Barotrauma
public void LoadSub(SubmarineInfo info)
{
Submarine.Unload();
var selectedSub = new Submarine(info);
MainSub = selectedSub;
MainSub.UpdateTransform(interpolate: false);
Submarine selectedSub = null;
try
{
selectedSub = new Submarine(info);
MainSub = selectedSub;
MainSub.UpdateTransform(interpolate: false);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to load the submarine. The submarine file might be corrupted.", e);
return;
}
ClearUndoBuffer();
CreateDummyCharacter();

View File

@@ -742,8 +742,7 @@ namespace Barotrauma
if (Screen.Selected == null) { return "menu".ToIdentifier(); }
if ((Screen.Selected?.IsEditor ?? false)
|| (Screen.Selected == GameMain.NetLobbyScreen))
if (Screen.Selected is { IsEditor: true } || GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected == GameMain.NetLobbyScreen)
{
return "editor".ToIdentifier();
}
@@ -853,7 +852,7 @@ namespace Barotrauma
{
return "levelend".ToIdentifier();
}
if (GameMain.GameSession.RoundDuration > 120.0 &&
if (GameMain.GameSession.RoundDuration < 120.0 &&
Level.Loaded?.Type == LevelData.LevelType.LocationConnection)
{
return "start".ToIdentifier();

View File

@@ -64,7 +64,7 @@ namespace Barotrauma.Steam
});
}
private static async Task<IReadOnlyList<Steamworks.Ugc.Item>> GetItemsThatNeedUpdating()
public static async Task<IReadOnlyList<Steamworks.Ugc.Item>> GetItemsThatNeedUpdating()
{
var determiningTasks = ContentPackageManager.WorkshopPackages.Select(async p => (p, await p.IsUpToDate()));
(ContentPackage Package, bool IsUpToDate)[] outOfDatePackages = await Task.WhenAll(determiningTasks);
@@ -126,6 +126,7 @@ namespace Barotrauma.Steam
{
mutableWorkshopMenu.PopulateInstalledModLists(forceRefreshEnabled: true);
}
GameMain.MainMenuScreen.ResetModUpdateButton();
});
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>100.6.0.0</Version>
<Version>100.8.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>100.6.0.0</Version>
<Version>100.8.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>100.6.0.0</Version>
<Version>100.8.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>100.6.0.0</Version>
<Version>100.8.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>100.6.0.0</Version>
<Version>100.8.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -18,20 +18,22 @@ namespace Barotrauma.Items.Components
bool powerOn = msg.ReadBoolean();
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
float turbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
float temperatureBoostAmount = msg.ReadRangedSingle(-TemperatureBoostAmount, TemperatureBoostAmount, 8);
if (!item.CanClientAccess(c)) { return; }
IsActive = true;
if (!autoTemp && AutoTemp) blameOnBroken = c;
if (turbineOutput < TargetTurbineOutput) blameOnBroken = c;
if (fissionRate > TargetFissionRate) blameOnBroken = c;
if (!_powerOn && powerOn) blameOnBroken = c;
if (!autoTemp && AutoTemp) { blameOnBroken = c; }
if (turbineOutput < TargetTurbineOutput) { blameOnBroken = c; }
if (fissionRate > TargetFissionRate) { blameOnBroken = c; }
if (!_powerOn && powerOn) { blameOnBroken = c; }
AutoTemp = autoTemp;
_powerOn = powerOn;
TargetFissionRate = fissionRate;
TargetTurbineOutput = turbineOutput;
if (AllowTemperatureBoost) { temperatureBoost = temperatureBoostAmount; }
LastUser = c.Character;
if (nextServerLogWriteTime == null)
@@ -51,6 +53,7 @@ namespace Barotrauma.Items.Components
msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(degreeOfSuccess, 0.0f, 1.0f, 8);
msg.WriteRangedSingle(temperatureBoost, -TemperatureBoostAmount, TemperatureBoostAmount, 8);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Barotrauma.Items.Components
{
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
float chargeRatio = MathHelper.Clamp(charge / adjustedCapacity, 0.0f, 1.0f);
msg.WriteRangedSingle(chargeRatio, 0.0f, 1.0f, 8);
}
}

View File

@@ -335,9 +335,9 @@ namespace Barotrauma.Networking
#endif
if (!started) { return; }
if (OwnerConnection != null && ChildServerRelay.HasShutDown)
if (ChildServerRelay.HasShutDown)
{
Quit();
GameMain.Instance.CloseServer();
return;
}
@@ -3938,6 +3938,7 @@ namespace Barotrauma.Networking
public void Quit()
{
if (started)
{
started = false;

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>100.6.0.0</Version>
<Version>100.8.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -1908,7 +1908,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)
{
@@ -1926,10 +1926,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))
@@ -1954,7 +1962,7 @@ namespace Barotrauma
}
}
Entity targetEntity = wallTarget?.Structure ?? SelectedAiTarget?.Entity;
if (AttackLimb?.attack is Attack { Ranged: true } attack && targetEntity != null)
if (AttackLimb?.attack is Attack { Ranged: true } attack)
{
AimRangedAttack(attack, targetEntity);
}
@@ -1981,7 +1989,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);
if (attack.AimRotationTorque <= 0) { return; }
Limb limb = GetLimbToRotate(attack);

View File

@@ -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)

View File

@@ -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))

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -23,9 +23,8 @@ namespace Barotrauma
protected override float TargetEvaluation()
{
if (!character.IsOnPlayerTeam && !character.IsOriginallyOnPlayerTeam) { return Targets.None() ? 0 : 100; }
int totalEnemies = Targets.Count;
if (totalEnemies == 0) { return 0; }
if (Targets.None()) { return 0; }
if (!character.IsOnPlayerTeam && !character.IsOriginallyOnPlayerTeam) { return 100; }
if (character.IsSecurity) { return 100; }
if (objectiveManager.IsOrder(this)) { return 100; }
// If there's any security officers onboard, leave fighting for them.

View File

@@ -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: () =>
{

View File

@@ -267,7 +267,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))

View File

@@ -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)
{

View File

@@ -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();
}

View File

@@ -540,8 +540,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;
@@ -1099,9 +1108,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>
@@ -1116,16 +1125,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)
@@ -1142,29 +1151,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
@@ -1181,7 +1190,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;
@@ -1291,7 +1300,8 @@ namespace Barotrauma
{
Inventory = new CharacterInventory(
inventoryElements.Count == 1 ? inventoryElements[0] : ToolBox.SelectWeightedRandom(inventoryElements, inventoryCommonness, random),
this);
this,
spawnInitialItems);
}
if (healthElements.Count == 0)
{
@@ -2713,7 +2723,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)
{
@@ -2748,6 +2758,7 @@ namespace Barotrauma
focusedItem = null;
}
findFocusedTimer -= deltaTime;
DisableFocusingOnEntities = false;
}
#endif
var head = AnimController.GetLimb(LimbType.Head);
@@ -4243,11 +4254,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();
@@ -4927,7 +4941,7 @@ namespace Barotrauma
{
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
{
if (talentOption.TalentIdentifiers.None(HasTalent))
if (!talentOption.HasMaxTalents(info.UnlockedTalents))
{
return false;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -152,7 +152,7 @@ namespace Barotrauma
MaxAttachableCount,
ExplosionRadiusMultiplier,
ExplosionDamageMultiplier,
FabricateMedicineSpeedMultiplier,
FabricationSpeed,
BallastFloraDamageMultiplier,
HoldBreathMultiplier,
Apprenticeship,

View File

@@ -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++;

View File

@@ -79,7 +79,7 @@ namespace Barotrauma
public bool DisableEvents
{
get { return IsFirstRound && GameMain.GameSession.RoundDuration > FirstRoundEventDelay; }
get { return IsFirstRound && GameMain.GameSession.RoundDuration < FirstRoundEventDelay; }
}
public bool CheatsEnabled;

View File

@@ -813,7 +813,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;
@@ -956,7 +956,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);

View File

@@ -56,6 +56,10 @@ namespace Barotrauma
}
EndReadyCheck();
#if CLIENT
msgBox?.Close();
#endif
}
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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;
}
}

View File

@@ -677,13 +677,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

View File

@@ -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);
}
}

View File

@@ -1095,6 +1095,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); }
@@ -1107,7 +1113,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();
@@ -2896,11 +2901,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>()
@@ -3442,12 +3450,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();

View File

@@ -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>();
}
@@ -233,7 +233,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));
}

View File

@@ -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);

View File

@@ -716,7 +716,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);
@@ -724,14 +724,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);
@@ -742,11 +742,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;
}

View File

@@ -738,7 +738,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)

View File

@@ -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);
}

View File

@@ -1,3 +1,15 @@
---------------------------------------------------------------------------------------------------------
v100.8.0.0
---------------------------------------------------------------------------------------------------------
- Fixed vanilla package failing to load due to a duplicate music clip in sounds.xml, leading to a crash on startup.
---------------------------------------------------------------------------------------------------------
v100.7.0.0
---------------------------------------------------------------------------------------------------------
No changes aside from the fixes in v0.20.9.0.
---------------------------------------------------------------------------------------------------------
v100.6.0.0
---------------------------------------------------------------------------------------------------------
@@ -83,6 +95,65 @@ Test version of the faction overhaul:
- There's now always two paths from biome to another, one controlled by the Coalition and one by Separatists.
- Improvements to the campaign map.
---------------------------------------------------------------------------------------------------------
v0.20.9.0
---------------------------------------------------------------------------------------------------------
Changes and additions:
- Added a button to the main menu that can be used to update all installed mods when there's updates available.
- Optimized the server lobby: there was an issue in the logic that updates the microphone icon that caused the game to check available audio devices every frame.
- Optimized status monitors: previously some parts of their UI were always updated regardless if anyone is viewing the UI.
- Made flak cannons a bit more quiet (so they don't drown out all other sounds).
Unstable only:
- Don't allow placing most of the new creature loot in cabinets/containers.
- Fixed some more vanilla subs' reactors having 1000 condition instead of 100.
- Adjustments to Harpoon Coil-Rifle sounds.
- Two new music tracks (one "default" one for missions and another for colonies).
- Fixed no events triggering in the first campaign outpost.
- Fixed crashing when firing a turret that doesn't use ammo boxes + ammo boxes getting used twice when firing.
- Fixed water-reactant items emitting particles when submerged even when inside a waterproof container + optimized the effects a bit.
- Allow recycling ammo boxes that are below 10% full.
- Fixed autoshotgun's ammunition indicator showing 1 item less than it should when there's no flashlight in the flashlight slot.
- Fixed "censorship" event (in which a clown asks you to retrieve a confiscated crate) being impossible to complete.
- Fixed crash when opening the fabricator UI in the sub editor.
- Improvements to the new beacon station clown event.
- Fixed passive sonar still revealing more than it should when there's directional pings active.
- Fixed inventory being visible when using a periscope in the sub editor test mode.
- Fixed inability to get out of the clown crate in multiplayer.
- Fixed "Lab Contacts" increasing the medical fabrication speed instead of reducing it.
- Fixed "All talents unlocked" showing before you had unlocked all talents.
- Fixed "Tinkerer" not working at all.
- Fixed "Quickfixer" not doubling the repair speed.
- Fixed exosuit draining the battery when not worn.
- Fixed exosuit consuming all contained tanks when there's a battery in it.
- Fixed bots failing to swap tanks in the exosuit.
- Removed the flashlight slot from the machine pistol.
- Fixed Defensebot's movement outside of the submarine.
- Petraptors poop.
- Fix a console error after a loaded game when a defense bot is present. Happened because we tried to spawn the initial items in the inventory, which was full.
- Fixed softlock if you get killed by barotrauma when under the effect of Miracle Worker.
- Fixed fabricator displaying items that can be crafted using a bot's talents as requiring a recipe to craft.
- Fixed fabricating nuclear shells and nuclear depth charges with the cheaper recipe unlocked by "Nuclear Option" not fully using up the fuel rods.
Bugfixes:
- Fixed occasional crashes when shutting down a server (for example with the error messages "pipe is broken" or "ChildServerRelay readTask did not run to completion")
- Fixed junction boxes not getting damaged by water since the power rework.
- Fixed opiate withdrawal only reducing down to 20%, but never fully healing by itself.
- Fixed engines reverting back to the non-damaged sprite when they're damaged badly enough that the sprite starts shaking.
- Fixed walls being set up incorrectly in vertical abandoned outpost hallway modules, causing them to stick out into the connected modules.
- Attempt to fix items sometimes ending up rotated inside a container (e.g. diving suit sprite appearing rotated on a diving suit locker).
- Fixed "man and his raptor" outpost event giving 1000 marks in an incorrect branch of the dialog (the one where you immediately accept the NPC on board, instead of the one where the NPC says they'll pay you 1000 mk).
- Fixed cases of interaction texts for focused item (most notoriously, the planter) not being updated correctly.
- Fixed "snap to grid" causing door gaps to get misaligned.
- Fixed weird equipping behavior on fruit and paints, causing them to be equipped in both hands when trying to unequip.
- Yet another fix to cave tunnels sometimes being too narrow to pass through.
- Fixed minerals sometimes being placed outside the level in mineral missions.
- Fixed reactor temperature boost not working in multiplayer.
Modding:
- Fixed item's OnSpawn effects being applied twice.
---------------------------------------------------------------------------------------------------------
v0.20.8.0
---------------------------------------------------------------------------------------------------------