Faction Test 100.6.0.0

This commit is contained in:
Markus Isberg
2022-11-25 19:55:45 +02:00
parent c44fb0ad3a
commit 0057f5bfce
130 changed files with 2771 additions and 1509 deletions
@@ -175,11 +175,11 @@ namespace Barotrauma
position += amount;
}
public void ClientWrite(IWriteMessage msg)
public void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
{
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
msg.WriteByte((byte)ClientNetObject.SPECTATING_POS);
segmentTableWriter.StartNewSegment(ClientNetSegment.SpectatingPos);
msg.WriteSingle(position.X);
msg.WriteSingle(position.Y);
}
@@ -673,15 +673,6 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
if (InvisibleTimer > 0.0f)
{
if (Controlled == null || Controlled == this || (Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
{
InvisibleTimer = Math.Min(InvisibleTimer, 1.0f);
}
InvisibleTimer -= deltaTime;
}
foreach (GUIMessage message in guiMessages)
{
bool wasPending = message.Timer < 0.0f;
@@ -149,6 +149,7 @@ namespace Barotrauma
public static bool ShouldRecreateHudTexts { get; set; } = true;
private static bool heldDownShiftWhenGotHudTexts;
private static float timeHealthWindowClosed;
public static bool IsCampaignInterfaceOpen =>
GameMain.GameSession?.Campaign != null &&
@@ -222,7 +223,8 @@ namespace Barotrauma
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
{
bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
bool healthWindowOpen = CharacterHealth.OpenHealthWindow != null || timeHealthWindowClosed < 0.2f;
if (mouseOnPortrait && !healthWindowOpen && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
{
CharacterHealth.OpenHealthWindow = character.CharacterHealth;
}
@@ -290,6 +292,15 @@ namespace Barotrauma
}
}
}
if (CharacterHealth.OpenHealthWindow != null)
{
timeHealthWindowClosed = 0.0f;
}
else
{
timeHealthWindowClosed += deltaTime;
}
}
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
@@ -539,7 +550,17 @@ namespace Barotrauma
{
var item = character.Inventory.GetItemAt(i);
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
//if the item is also equipped in another slot we already went through, don't draw the hud again
bool duplicateFound = false;
for (int j = 0; j < i; j++)
{
if (character.Inventory.SlotTypes[j] != InvSlotType.Any && character.Inventory.GetItemAt(j) == item)
{
duplicateFound = true;
break;
}
}
if (duplicateFound) { continue; }
foreach (ItemComponent ic in item.Components)
{
if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
@@ -113,9 +113,9 @@ namespace Barotrauma
}
}
public void ClientWriteInput(IWriteMessage msg)
public void ClientWriteInput(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
{
msg.WriteByte((byte)ClientNetObject.CHARACTER_INPUT);
segmentTableWriter.StartNewSegment(ClientNetSegment.CharacterInput);
if (memInput.Count > 60)
{
@@ -156,7 +156,7 @@ namespace Barotrauma
Character.Controlled.DeselectCharacter();
}
Character.Controlled.ResetInteract = true;
Character.Controlled.DisableInteract = true;
if (openHealthWindow != null)
{
if (value.Character.Info == null || value.Character == Character.Controlled || Character.Controlled.HasEquippedItem("healthscanner".ToIdentifier()))
@@ -51,11 +51,10 @@ namespace Barotrauma
&& p.InstallTime.TryUnwrap(out var installTime)
&& item.LatestUpdateTime <= installTime))
.ToArray();
if (needInstalling.Any())
{
await Task.WhenAll(
needInstalling.Select(SteamManager.Workshop.DownloadModThenEnqueueInstall));
}
if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); }
await Task.WhenAll(
needInstalling.Select(SteamManager.Workshop.DownloadModThenEnqueueInstall));
return needInstalling;
}
@@ -128,21 +128,17 @@ namespace Barotrauma
public static void Update(float deltaTime)
{
lock (queuedMessages)
while (queuedMessages.TryDequeue(out var newMsg))
{
while (queuedMessages.Count > 0)
{
var newMsg = queuedMessages.Dequeue();
AddMessage(newMsg);
AddMessage(newMsg);
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
{
unsavedMessages.Add(newMsg);
if (unsavedMessages.Count >= messagesPerFile)
{
unsavedMessages.Add(newMsg);
if (unsavedMessages.Count >= messagesPerFile)
{
SaveLogs();
unsavedMessages.Clear();
}
SaveLogs();
unsavedMessages.Clear();
}
}
}
@@ -258,25 +254,21 @@ namespace Barotrauma
public static void DequeueMessages()
{
lock (queuedMessages)
while (queuedMessages.TryDequeue(out var newMsg))
{
while (queuedMessages.Count > 0)
if (listBox == null)
{
var newMsg = queuedMessages.Dequeue();
if (listBox == null)
{
//don't attempt to add to the listbox if it hasn't been created yet
Messages.Add(newMsg);
}
else
{
AddMessage(newMsg);
}
//don't attempt to add to the listbox if it hasn't been created yet
Messages.Add(newMsg);
}
else
{
AddMessage(newMsg);
}
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
{
unsavedMessages.Add(newMsg);
}
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
{
unsavedMessages.Add(newMsg);
}
}
}
@@ -12,6 +12,7 @@ namespace Barotrauma
partial void OnStateChangedProjSpecific()
{
SoundPlayer.ForceMusicUpdate();
if (Phase == MissionPhase.NoItemsDestroyed)
{
CoroutineManager.Invoke(() =>
@@ -31,6 +32,10 @@ namespace Barotrauma
}
else if (Phase == MissionPhase.BossKilled)
{
if (!string.IsNullOrEmpty(endCinematicSound))
{
SoundPlayer.PlaySound(endCinematicSound);
}
CoroutineManager.Invoke(() =>
{
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale)
@@ -60,7 +65,7 @@ namespace Barotrauma
if (limb.LightSource is Lights.LightSource light)
{
light.Enabled = true;
}
}
}
}
}
@@ -115,6 +115,11 @@ namespace Barotrauma
};
}
public Identifier GetOverrideMusicType()
{
return Prefab.GetOverrideMusicType(State);
}
public virtual void ClientRead(IReadMessage msg)
{
State = msg.ReadInt16();
@@ -1,11 +1,16 @@
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
{
partial class MissionPrefab : PrefabWithUintIdentifier
{
private ImmutableArray<Sprite> portraits = new ImmutableArray<Sprite>();
public bool HasPortraits => portraits.Length > 0;
public Sprite Icon
{
get;
@@ -49,24 +54,57 @@ namespace Barotrauma
private Sprite hudIcon;
private Color? hudIconColor;
private ImmutableDictionary<int, Identifier> overrideMusicOnState;
partial void InitProjSpecific(ContentXElement element)
{
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f);
Dictionary<int, Identifier> overrideMusic = new Dictionary<int, Identifier>();
List<Sprite> portraits = new List<Sprite>();
foreach (var subElement in element.Elements())
{
string name = subElement.Name.ToString();
if (name.Equals("icon", StringComparison.OrdinalIgnoreCase))
switch (subElement.Name.ToString().ToLowerInvariant())
{
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
}
else if (name.Equals("hudicon", StringComparison.OrdinalIgnoreCase))
{
hudIcon = new Sprite(subElement);
hudIconColor = subElement.GetAttributeColor("color");
case "icon":
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
break;
case "hudicon":
hudIcon = new Sprite(subElement);
hudIconColor = subElement.GetAttributeColor("color");
break;
case "overridemusic":
overrideMusic.Add(
subElement.GetAttributeInt("state", 0),
subElement.GetAttributeIdentifier("type", Identifier.Empty));
break;
case "portrait":
var portrait = new Sprite(subElement, lazyLoad: true);
if (portrait != null)
{
portraits.Add(portrait);
}
break;
}
}
this.portraits = portraits.ToImmutableArray();
overrideMusicOnState = overrideMusic.ToImmutableDictionary();
}
public Identifier GetOverrideMusicType(int state)
{
if (overrideMusicOnState.TryGetValue(state, out Identifier id))
{
return id;
}
return Identifier.Empty;
}
public Sprite GetPortrait(int randomSeed)
{
if (portraits.Length == 0) { return null; }
return portraits[Math.Abs(randomSeed) % portraits.Length];
}
partial void DisposeProjectSpecific()
@@ -335,7 +335,7 @@ namespace Barotrauma
DrawString(spriteBatch, new Vector2(10, y),
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
if (GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 1.0)
if (GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 1.0)
{
y += yStep;
DrawString(spriteBatch, new Vector2(10, y),
@@ -695,22 +695,24 @@ namespace Barotrauma
}
}
public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, Color color)
public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, Color color, Rectangle? drawArea = null, SpriteEffects spriteEffects = SpriteEffects.None)
{
float scale = Math.Max(
(float)GameMain.GraphicsWidth / backgroundSprite.SourceRect.Width,
(float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f;
float paddingX = backgroundSprite.SourceRect.Width * scale - GameMain.GraphicsWidth;
float paddingY = backgroundSprite.SourceRect.Height * scale - GameMain.GraphicsHeight;
Rectangle area = drawArea ?? new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
double noiseT = (Timing.TotalTime * 0.02f);
float scale = Math.Max(
(float)area.Width / backgroundSprite.SourceRect.Width,
(float)area.Height / backgroundSprite.SourceRect.Height) * 1.1f;
float paddingX = backgroundSprite.SourceRect.Width * scale - area.Width;
float paddingY = backgroundSprite.SourceRect.Height * scale - area.Height;
double noiseT = Timing.TotalTime * 0.02f;
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(backgroundSprite.Texture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
area.Center.ToVector2() + pos,
null, color, 0.0f, backgroundSprite.size / 2,
scale, SpriteEffects.None, 0.0f);
scale, spriteEffects, 0.0f);
}
#region Update list
@@ -758,7 +758,7 @@ namespace Barotrauma
toolTipBlock.DrawManually(spriteBatch);
}
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement)
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement, Anchor anchor = Anchor.BottomCenter, Pivot pivot = Pivot.TopLeft)
{
if (ObjectiveManager.ContentRunning) { return; }
@@ -775,7 +775,10 @@ namespace Barotrauma
toolTipBlock.UserData = toolTip;
}
toolTipBlock.RectTransform.AbsoluteOffset = new Point(targetElement.Center.X, targetElement.Bottom);
toolTipBlock.RectTransform.AbsoluteOffset =
RectTransform.CalculateAnchorPoint(anchor, targetElement) +
RectTransform.CalculatePivotOffset(pivot, toolTipBlock.RectTransform.NonScaledSize);
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
@@ -11,9 +11,9 @@ namespace Barotrauma
{
class LoadingScreen
{
private readonly Texture2D defaultBackgroundTexture, overlay;
private readonly Sprite defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap;
private Texture2D currentBackgroundTexture;
private Sprite currentBackgroundTexture;
private readonly Sprite noiseSprite;
private string randText = "";
@@ -24,6 +24,8 @@ namespace Barotrauma
private Video currSplashScreen;
private DateTime videoStartTime;
private bool mirrorBackground;
public struct PendingSplashScreen
{
public string Filename;
@@ -112,12 +114,12 @@ namespace Barotrauma
public LoadingScreen(GraphicsDevice graphics)
{
defaultBackgroundTexture = TextureLoader.FromFile("Content/Map/LocationPortraits/AlienRuins.png");
defaultBackgroundTexture = new Sprite("Content/Map/LocationPortraits/MainMenu1.png", Vector2.Zero);
decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
overlay = TextureLoader.FromFile("Content/UI/MainMenuVignette.png");
overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true;
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
@@ -143,24 +145,19 @@ namespace Barotrauma
currentBackgroundTexture ??= defaultBackgroundTexture;
float overlayScale = Math.Min(GameMain.GraphicsWidth / overlay.size.X, GameMain.GraphicsHeight / overlay.size.Y);
Rectangle drawArea = new Rectangle(
(int)(overlay.size.X * overlayScale / 2), 0,
(int)(GameMain.GraphicsWidth - overlay.size.X * overlayScale / 2), GameMain.GraphicsHeight);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
float scale = Math.Max(
(float)GameMain.GraphicsWidth / currentBackgroundTexture.Width,
(float)GameMain.GraphicsHeight / currentBackgroundTexture.Height) * 1.2f;
float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth;
float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight;
double noiseT = (Timing.TotalTime * 0.02f);
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(currentBackgroundTexture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2),
scale, SpriteEffects.None, 0.0f);
spriteBatch.Draw(overlay, Vector2.Zero, null, Color.White, 0.0f, Vector2.Zero, Math.Min(GameMain.GraphicsWidth / (float)overlay.Width, GameMain.GraphicsHeight / (float)overlay.Height), SpriteEffects.None, 0.0f);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = Timing.TotalTime * 0.02f;
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
@@ -430,7 +427,12 @@ namespace Barotrauma
drawn = false;
LoadState = null;
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue));
if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))
{
currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
}
mirrorBackground = Rand.Range(0.0f, 1.0f) < 0.5f;
while (!drawn)
{
@@ -134,7 +134,7 @@ namespace Barotrauma
set => hadSellSubPermissions = value;
}
private bool HasPermissionToUseTab(StoreTab tab)
private static bool HasPermissionToUseTab(StoreTab tab)
{
return tab switch
{
@@ -278,6 +278,7 @@ namespace Barotrauma
RefreshBuying(updateOwned: false);
RefreshSelling(updateOwned: false);
RefreshSellingFromSub(updateOwned: false);
SetConfirmButtonBehavior();
needsRefresh = false;
}
@@ -881,18 +882,17 @@ namespace Barotrauma
float prevBuyListScroll = storeBuyList.BarScroll;
float prevShoppingCrateScroll = shoppingCrateBuyList.BarScroll;
int dailySpecialCount = ActiveStore.DailySpecials.Count;
if ((storeDailySpecialsGroup != null) != ActiveStore.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
int dailySpecialCount = ActiveStore?.DailySpecials.Count(s => s.CanCharacterBuy()) ?? 0;
if ((ActiveStore == null && storeDailySpecialsGroup != null) || (storeDailySpecialsGroup != null) != ActiveStore.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
{
if (storeDailySpecialsGroup == null || dailySpecialCount != prevDailySpecialCount)
storeBuyList.RemoveChild(storeDailySpecialsGroup?.Parent);
if (ActiveStore != null && (storeDailySpecialsGroup == null || dailySpecialCount != prevDailySpecialCount))
{
storeBuyList.RemoveChild(storeDailySpecialsGroup?.Parent);
storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, dailySpecialCount);
storeDailySpecialsGroup.Parent.SetAsFirstChild();
}
else
{
storeBuyList.RemoveChild(storeDailySpecialsGroup.Parent);
storeDailySpecialsGroup = null;
}
storeBuyList.RecalculateChildren();
@@ -901,15 +901,17 @@ namespace Barotrauma
bool hasPermissions = HasTabPermissions(StoreTab.Buy);
var existingItemFrames = new HashSet<GUIComponent>();
foreach (PurchasedItem item in ActiveStore.Stock)
if (ActiveStore != null)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (ItemPrefab itemPrefab in ActiveStore.DailySpecials)
{
if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) { continue; }
CreateOrUpdateItemFrame(itemPrefab, 0);
foreach (PurchasedItem item in ActiveStore.Stock)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (ItemPrefab itemPrefab in ActiveStore.DailySpecials)
{
if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) { continue; }
CreateOrUpdateItemFrame(itemPrefab, 0);
}
}
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
@@ -969,11 +971,11 @@ namespace Barotrauma
float prevSellListScroll = storeSellList.BarScroll;
float prevShoppingCrateScroll = shoppingCrateSellList.BarScroll;
int requestedGoodsCount = ActiveStore.RequestedGoods.Count;
if ((storeRequestedGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
if ((ActiveStore == null && storeRequestedGoodGroup != null) || (storeRequestedGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
{
storeSellList.RemoveChild(storeRequestedGoodGroup?.Parent);
if (storeRequestedGoodGroup == null || requestedGoodsCount != prevRequestedGoodsCount)
if (ActiveStore != null && (storeRequestedGoodGroup == null || requestedGoodsCount != prevRequestedGoodsCount))
{
storeRequestedGoodGroup = CreateDealsGroup(storeSellList, requestedGoodsCount);
storeRequestedGoodGroup.Parent.SetAsFirstChild();
@@ -988,14 +990,17 @@ namespace Barotrauma
bool hasPermissions = HasTabPermissions(StoreTab.Sell);
var existingItemFrames = new HashSet<GUIComponent>();
foreach (PurchasedItem item in itemsToSell)
if (ActiveStore != null)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (var requestedGood in ActiveStore.RequestedGoods)
{
if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
CreateOrUpdateItemFrame(requestedGood, 0);
foreach (PurchasedItem item in itemsToSell)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (var requestedGood in ActiveStore.RequestedGoods)
{
if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
CreateOrUpdateItemFrame(requestedGood, 0);
}
}
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
@@ -1053,11 +1058,11 @@ namespace Barotrauma
float prevSellListScroll = storeSellFromSubList.BarScroll;
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
int requestedGoodsCount = ActiveStore.RequestedGoods.Count;
if ((storeRequestedSubGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
if ((ActiveStore == null && storeRequestedSubGoodGroup != null) || (storeRequestedSubGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
{
storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup?.Parent);
if (storeRequestedSubGoodGroup == null || requestedGoodsCount != prevSubRequestedGoodsCount)
if (ActiveStore != null && (storeRequestedSubGoodGroup == null || requestedGoodsCount != prevSubRequestedGoodsCount))
{
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, requestedGoodsCount);
storeRequestedSubGoodGroup.Parent.SetAsFirstChild();
@@ -1072,14 +1077,17 @@ namespace Barotrauma
bool hasPermissions = HasSellSubPermissions;
var existingItemFrames = new HashSet<GUIComponent>();
foreach (PurchasedItem item in itemsToSellFromSub)
if (ActiveStore != null)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (var requestedGood in ActiveStore.RequestedGoods)
{
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
CreateOrUpdateItemFrame(requestedGood, 0);
foreach (PurchasedItem item in itemsToSellFromSub)
{
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
}
foreach (var requestedGood in ActiveStore.RequestedGoods)
{
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
CreateOrUpdateItemFrame(requestedGood, 0);
}
}
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
@@ -1166,6 +1174,7 @@ namespace Barotrauma
public void RefreshItemsToSell()
{
itemsToSell.Clear();
if (ActiveStore == null) { return; }
var playerItems = CargoManager.GetSellableItems(Character.Controlled);
foreach (Item playerItem in playerItems)
{
@@ -1196,6 +1205,7 @@ namespace Barotrauma
public void RefreshItemsToSellFromSub()
{
itemsToSellFromSub.Clear();
if (ActiveStore == null) { return; }
var subItems = CargoManager.GetSellableItemsFromSub();
foreach (Item subItem in subItems)
{
@@ -1229,52 +1239,55 @@ namespace Barotrauma
bool hasPermissions = HasTabPermissions(tab);
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
int totalPrice = 0;
foreach (PurchasedItem item in items)
if (ActiveStore != null)
{
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; }
GUINumberInput numInput = null;
if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame))
foreach (PurchasedItem item in items)
{
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
}
else
{
itemFrame.UserData = item;
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; }
GUINumberInput numInput = null;
if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame))
{
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
}
else
{
itemFrame.UserData = item;
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
if (numInput != null)
{
numInput.UserData = item;
numInput.Enabled = hasPermissions;
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
}
SetOwnedText(itemFrame);
SetItemFrameStatus(itemFrame, hasPermissions);
}
existingItemFrames.Add(itemFrame);
suppressBuySell = true;
if (numInput != null)
{
numInput.UserData = item;
numInput.Enabled = hasPermissions;
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUIStyle.Green); }
numInput.IntValue = item.Quantity;
}
SetOwnedText(itemFrame);
SetItemFrameStatus(itemFrame, hasPermissions);
}
existingItemFrames.Add(itemFrame);
suppressBuySell = false;
suppressBuySell = true;
if (numInput != null)
{
if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUIStyle.Green); }
numInput.IntValue = item.Quantity;
}
suppressBuySell = false;
try
{
int price = tab switch
try
{
StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo),
StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
_ => throw new NotImplementedException()
};
totalPrice += item.Quantity * price;
}
catch (NotImplementedException e)
{
DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
int price = tab switch
{
StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo),
StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
_ => throw new NotImplementedException()
};
totalPrice += item.Quantity * price;
}
catch (NotImplementedException e)
{
DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
}
}
}
@@ -1311,7 +1324,7 @@ namespace Barotrauma
private void SortItems(GUIListBox list, SortingMethod sortingMethod)
{
if (CurrentLocation == null) { return; }
if (CurrentLocation == null || ActiveStore == null) { return; }
if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc)
{
@@ -1707,6 +1720,8 @@ namespace Barotrauma
{
OwnedItems.Clear();
if (ActiveStore == null) { return; }
// Add items on the sub(s)
if (Submarine.MainSub?.GetItems(true) is List<Item> subItems)
{
@@ -2124,7 +2139,12 @@ namespace Barotrauma
private void SetShoppingCrateTotalText()
{
if (IsBuying)
if (ActiveStore == null)
{
shoppingCrateTotal.Text = TextManager.FormatCurrency(0);
shoppingCrateTotal.TextColor = Color.White;
}
else if (IsBuying)
{
shoppingCrateTotal.Text = TextManager.FormatCurrency(buyTotal);
shoppingCrateTotal.TextColor = Balance < buyTotal ? Color.Red : Color.White;
@@ -2144,7 +2164,11 @@ namespace Barotrauma
private void SetConfirmButtonBehavior()
{
if (IsBuying)
if (ActiveStore == null)
{
confirmButton.OnClicked = null;
}
else if (IsBuying)
{
confirmButton.ClickSound = GUISoundType.ConfirmTransaction;
confirmButton.Text = TextManager.Get("CampaignStore.Purchase");
@@ -2172,6 +2196,7 @@ namespace Barotrauma
private void SetConfirmButtonStatus()
{
confirmButton.Enabled =
ActiveStore != null &&
HasActiveTabPermissions() &&
ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
activeTab switch
@@ -2181,6 +2206,7 @@ namespace Barotrauma
StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= ActiveStore.Balance,
_ => false
};
confirmButton.Visible = ActiveStore != null;
}
private void SetClearAllButtonStatus()
@@ -2254,29 +2280,32 @@ namespace Barotrauma
prevBalance = currBalance;
}
}
if (needsItemsToSellRefresh)
if (ActiveStore != null)
{
RefreshItemsToSell();
}
if (needsItemsToSellFromSubRefresh)
{
RefreshItemsToSellFromSub();
}
if (needsRefresh)
{
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
{
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell))
{
RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub))
{
RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
if (needsItemsToSellRefresh)
{
RefreshItemsToSell();
}
if (needsItemsToSellFromSubRefresh)
{
RefreshItemsToSellFromSub();
}
if (needsRefresh)
{
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
{
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell))
{
RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
}
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub))
{
RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
}
}
updateStopwatch.Stop();
@@ -1491,7 +1491,11 @@ namespace Barotrauma
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = (int)(0.0245f * missionFrame.Rect.Height);
GUIFrame missionFrameContent = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, missionFrame.Rect.Height - padding * 2), infoFrame.RectTransform, Anchor.Center), style: null);
Location location = GameMain.GameSession.EndLocation ?? GameMain.GameSession.StartLocation;
Location location = GameMain.GameSession.StartLocation;
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
{
location ??= GameMain.GameSession.EndLocation;
}
GUILayoutGroup locationInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), missionFrameContent.RectTransform))
{
@@ -283,7 +283,7 @@ namespace Barotrauma
break;
case TalentTreeType.Specialization:
talentList = GetSpecializationList();
treeSize = new Vector2(0.333f, 1f);
treeSize = new Vector2(Math.Max(0.333f, 1.0f / tree.TalentSubTrees.Count(t => t.Type == TalentTreeType.Specialization)), 1f);
break;
default:
throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
@@ -325,7 +325,9 @@ namespace Barotrauma
}
var specializationList = GetSpecializationList();
GetSpecializationList().Content.RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height), resizeChildren: false);
//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);
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
@@ -16,6 +16,7 @@ using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -465,10 +466,11 @@ namespace Barotrauma
LegacySteamUgcTransition.Prepare();
var contentPackageLoadRoutine = ContentPackageManager.Init();
foreach (var progress in contentPackageLoadRoutine)
foreach (var progress in contentPackageLoadRoutine
.Select(p => p.Result).Successes())
{
const float min = 1f, max = 70f;
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress.Value);
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress);
yield return CoroutineStatus.Running;
}
@@ -1078,10 +1080,9 @@ namespace Barotrauma
if (GameSession != null)
{
double roundDuration = Timing.TotalTime - GameSession.RoundStartTime;
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
roundDuration);
GameSession.RoundDuration);
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
@@ -11,7 +11,15 @@ namespace Barotrauma
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
// The bag slot is intentionally left out since we want to be able to sell items from there
private readonly List<InvSlotType> equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
private static readonly HashSet<InvSlotType> equipmentSlots = new HashSet<InvSlotType>()
{
InvSlotType.Head,
InvSlotType.InnerClothes,
InvSlotType.OuterClothes,
InvSlotType.Headset,
InvSlotType.Card,
InvSlotType.HealthInterface
};
public IEnumerable<Item> GetSellableItems(Character character)
{
@@ -432,7 +432,7 @@ namespace Barotrauma
break;
}
Map.ProgressWorld(this, transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
Map.ProgressWorld(this, transitionType, GameMain.GameSession.RoundDuration);
GUI.ClearMessages();
@@ -296,7 +296,7 @@ static class ObjectiveManager
};
for (int i = 0; i < activeObjectives.Count; i++)
{
AddToObjectiveList(activeObjectives[i]);
AddToObjectiveList(activeObjectives[i], useExistingIndex: true);
}
screenSettings = new ScreenSettings(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Scale, GameSettings.CurrentConfig.Graphics.DisplayMode);
}
@@ -318,7 +318,7 @@ static class ObjectiveManager
/// <summary>
/// Adds the segment to the objective list
/// </summary>
private static void AddToObjectiveList(Segment segment, bool connectExisting = false)
private static void AddToObjectiveList(Segment segment, bool connectExisting = false, bool useExistingIndex = false)
{
if (connectExisting)
{
@@ -338,8 +338,8 @@ static class ObjectiveManager
if (parentSegment is not null)
{
// Add this child as the last child in case there are other existing children already
int totalChildren = activeObjectives.Count(s => s.ParentId == segment.ParentId);
int childIndex = activeObjectives.IndexOf(parentSegment) + totalChildren;
int childIndex = useExistingIndex ? activeObjectives.IndexOf(segment) :
activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId);
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
{
frameRt.RepositionChildInHierarchy(childIndex);
@@ -971,7 +971,7 @@ namespace Barotrauma
//don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
//(in that case, the quick action should just fill up the stack)
bool disallowSwapping =
heldItem.OwnInventory.Capacity == 1 &&
(heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) &&
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
@@ -1131,7 +1131,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, inventoryArea, new Color(30,30,30,100), isFilled: true);
var lockIcon = GUIStyle.GetComponentStyle("LockIcon")?.GetDefaultSprite();
lockIcon?.Draw(spriteBatch, inventoryArea.Center.ToVector2(), scale: Math.Min(inventoryArea.Height / lockIcon.size.Y * 0.7f, 1.0f));
if (inventoryArea.Contains(PlayerInput.MousePosition))
if (inventoryArea.Contains(PlayerInput.MousePosition) && character.LockHands)
{
GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("handcuffed"), new Rectangle(inventoryArea.Center - new Point(inventoryArea.Height / 2), new Point(inventoryArea.Height)));
}
@@ -408,6 +408,8 @@ namespace Barotrauma.Items.Components
var wire = it.GetComponent<Wire>();
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false }) { return false; }
if (it.HasTag("traitormissionitem")) { return false; }
return true;
@@ -29,7 +29,11 @@ namespace Barotrauma.Items.Components
private Sprite tempRangeIndicator;
private Sprite graphLine;
//private GUIFrame graph;
private GUICustomComponent graph;
private GUIFrame inventoryWindow;
private GUILayoutGroup buttonArea;
private GUIFrame infographic;
private Color optimalRangeColor = new Color(74,238,104,255);
private Color offRangeColor = Color.Orange;
@@ -66,6 +70,8 @@ namespace Barotrauma.Items.Components
};
public override bool RecreateGUIOnResolutionChange => true;
public bool TriggerInfographic { get; set; }
partial void InitProjSpecific(ContentXElement element)
{
@@ -122,7 +128,7 @@ namespace Barotrauma.Items.Components
//left column
//----------------------------------------------------------
GUIFrame inventoryWindow = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.75f), GuiFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight)
inventoryWindow = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.75f), GuiFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight)
{
MinSize = new Point(85, 220),
RelativeOffset = new Vector2(-0.02f, 0)
@@ -255,7 +261,7 @@ namespace Barotrauma.Items.Components
};
TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider;
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform))
buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform))
{
Stretch = true,
RelativeSpacing = 0.02f
@@ -436,8 +442,8 @@ namespace Barotrauma.Items.Components
LocalizedString kW = TextManager.Get("kilowatt");
loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)Load).ToString())} {kW}";
var graph = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed");
new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graph.RectTransform, Anchor.Center), DrawGraph, null);
var graphFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed");
graph = new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graphFrame.RectTransform, Anchor.Center), DrawGraph, null);
var outputText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform),
"Output", textColor: outputColor, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
@@ -448,6 +454,22 @@ namespace Barotrauma.Items.Components
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
InitInventoryUI();
// Infographic overlay ---------------------
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
var helpButtonRt = new RectTransform(new Point(buttonHeight), parent: GuiFrame.RectTransform, anchor: Anchor.TopRight)
{
AbsoluteOffset = new Point(buttonHeight / 4),
MinSize = new Point(buttonHeight)
};
new GUIButton(helpButtonRt, "", style: "HelpIcon")
{
OnClicked = (_, _) =>
{
CreateInfrographic();
return true;
}
};
}
private void InitInventoryUI()
@@ -469,7 +491,6 @@ namespace Barotrauma.Items.Components
InitInventoryUI();
}
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
{
Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y);
@@ -518,7 +539,6 @@ namespace Barotrauma.Items.Components
DrawGraph(loadGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, loadColor);
}
private void UpdateGraph(float deltaTime)
{
graphTimer += deltaTime * 1000.0f;
@@ -645,6 +665,12 @@ namespace Barotrauma.Items.Components
}
}
}
if (TriggerInfographic)
{
CreateInfrographic();
TriggerInfographic = false;
}
}
private void DrawMeter(SpriteBatch spriteBatch, Rectangle rect, Sprite meterSprite, float value, Vector2 range, Vector2 optimalRange, Vector2 allowedRange)
@@ -760,7 +786,96 @@ namespace Barotrauma.Items.Components
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred);
}
private enum InfographicArrowStyle { Straight, Curved };
private void CreateInfrographic()
{
if (infographic != null) { return; }
var dimColor = Color.Lerp(Color.Black, Color.TransparentBlack, 0.25f);
// Dim reactor interface
infographic = new GUIFrame(new RectTransform(Vector2.One, GuiFrame.RectTransform))
{
CanBeFocused = false,
Color = dimColor
};
// Dim inventory window
new GUIFrame(new RectTransform(inventoryWindow.Rect.Size, infographic.RectTransform) { AbsoluteOffset = inventoryWindow.Rect.Location - GuiFrame.Rect.Location}, color: dimColor)
{
CanBeFocused = false
};
int arrowSize = (int)(70 * GUI.Scale);
var arrows = new Dictionary<string, GUIImage>()
{
{ "fuelslots", CreateArrow(InfographicArrowStyle.Curved, inventoryWindow, Anchor.TopLeft, Pivot.TopRight, SpriteEffects.FlipVertically) },
{ "temperature", CreateArrow(InfographicArrowStyle.Straight, temperatureBoostDownButton, Anchor.Center, Pivot.Center) },
{ "automaticcontrol", CreateArrow(InfographicArrowStyle.Curved, AutoTempSwitch, Anchor.TopRight, Pivot.BottomRight, rotationDegrees: 90f) },
{ "power", CreateArrow(InfographicArrowStyle.Straight, PowerButton, Anchor.BottomCenter, Pivot.TopCenter) }
};
CreateArrow(InfographicArrowStyle.Straight, FissionRateScrollBar, Anchor.Center, Pivot.Center);
CreateArrow(InfographicArrowStyle.Straight, TurbineOutputScrollBar, Anchor.Center, Pivot.Center);
CreateArrow(InfographicArrowStyle.Straight, graph, Anchor.TopLeft, Pivot.TopLeft, SpriteEffects.FlipHorizontally, additionalOffset: new Point(arrowSize / 2, 0));
CreateArrow(InfographicArrowStyle.Straight, graph, Anchor.BottomLeft, Pivot.BottomLeft, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically, additionalOffset: new Point(arrowSize / 2, 0));
new GUICustomComponent(new RectTransform(Vector2.One, infographic.RectTransform),
onDraw: (sb, c) =>
{
DrawToolTip("fuelslots", Anchor.TopLeft, Pivot.BottomCenter, arrows["fuelslots"]);
DrawToolTip("fissionrate", Anchor.TopCenter, Pivot.TopCenter, buttonArea);
DrawToolTip("temperature", Anchor.BottomLeft, Pivot.TopLeft, arrows["temperature"]);
DrawToolTip("automaticcontrol", Anchor.TopLeft, Pivot.CenterRight, arrows["automaticcontrol"]);
DrawToolTip("power", Anchor.BottomCenter, Pivot.TopCenter, arrows["power"]);
DrawToolTip("load", Anchor.CenterLeft, Pivot.CenterLeft, graph);
void DrawToolTip(string textTag, Anchor anchor, Pivot pivot, GUIComponent targetComponent)
{
GUIComponent.DrawToolTip(sb,
TextManager.Get($"infographic.reactor.{textTag}"),
targetComponent.Rect,
anchor: anchor,
pivot: pivot);
}
})
{
CanBeFocused = false
};
var closeButtonRt = new RectTransform(new Point(200, 50).Multiply(GUI.Scale), infographic.RectTransform, Anchor.TopRight, Pivot.BottomRight)
{
AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale)
};
new GUIButton(closeButtonRt, TextManager.Get("close"))
{
OnClicked = (_, _) =>
{
CloseInfographic(Character.Controlled);
return true;
}
};
item.OnDeselect += CloseInfographic;
GUIImage CreateArrow(InfographicArrowStyle arrowStyle, GUIComponent parent, Anchor anchor, Pivot pivot, SpriteEffects spriteEffects = SpriteEffects.None, float rotationDegrees = 0f, Point? additionalOffset = null)
{
Point offset = (additionalOffset ?? Point.Zero) + RectTransform.CalculateAnchorPoint(anchor, parent.Rect) - GuiFrame.Rect.Location;
var rt = new RectTransform(new Point(arrowSize), infographic.RectTransform, pivot: pivot)
{
AbsoluteOffset = offset
};
string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved";
return new GUIImage(rt, style)
{
Rotation = MathHelper.ToRadians(rotationDegrees),
SpriteEffects = spriteEffects
};
}
void CloseInfographic(Character character)
{
if (character != Character.Controlled) { return; }
GuiFrame.RemoveChild(infographic);
infographic = null;
item.OnDeselect -= CloseInfographic;
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
@@ -817,7 +817,7 @@ 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)
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500 && t.IsWithinSector(transducerCenter))
{
int prevBlipCount = sonarBlips.Count;
Ping(t.WorldPosition, transducerCenter,
@@ -35,6 +35,13 @@ namespace Barotrauma.Items.Components
if (GuiFrame == null) { return; }
originalMaxSize = GuiFrame.RectTransform.MaxSize;
originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
CreateGUI();
}
protected override void CreateGUI()
{
if (GuiFrame == null) { return; }
CheckForLabelOverlap();
var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
{
@@ -43,8 +50,8 @@ namespace Barotrauma.Items.Components
content.RectTransform.SetAsFirstChild();
//prevents inputs from going through the GUICustomComponent to the drag handle
dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
}
public void TriggerRewiringSound()
@@ -121,12 +128,6 @@ namespace Barotrauma.Items.Components
}
}
protected override void OnResolutionChanged()
{
if (GuiFrame == null) { return; }
CheckForLabelOverlap();
}
private void CheckForLabelOverlap()
{
GuiFrame.RectTransform.MaxSize = originalMaxSize;
@@ -324,7 +324,7 @@ namespace Barotrauma.Items.Components
if (Character.Controlled != null)
{
Character.Controlled.FocusedItem = null;
Character.Controlled.ResetInteract = true;
Character.Controlled.DisableInteract = true;
Character.Controlled.ClearInputs();
}
//cancel dragging
@@ -401,7 +401,7 @@ namespace Barotrauma.Items.Components
{
if (Character.Controlled != null)
{
Character.Controlled.ResetInteract = true;
Character.Controlled.DisableInteract = true;
Character.Controlled.ClearInputs();
}
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out _);
@@ -431,7 +431,7 @@ namespace Barotrauma.Items.Components
{
if (Character.Controlled != null)
{
Character.Controlled.ResetInteract = true;
Character.Controlled.DisableInteract = true;
Character.Controlled.ClearInputs();
}
draggingWire = selectedWire;
@@ -1287,7 +1287,8 @@ namespace Barotrauma
{
foreach (ItemComponent ic in activeHUDs)
{
if (ic.GuiFrame == null || !ic.CanBeSelected) { continue; }
if (ic.GuiFrame == null) { continue; }
if (!ic.CanBeSelected && !ic.DrawHudWhenEquipped) { continue; }
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
if (ic.UseAlternativeLayout)
{
@@ -73,6 +73,8 @@ namespace Barotrauma
private RichString beaconStationActiveText, beaconStationInactiveText;
private GUIComponent locationInfoOverlay;
/*private (Rectangle targetArea, string tip)? connectionTooltip;
private string sanitizedConnectionTooltip;
private List<RichTextData> connectionTooltipRichTextData;
@@ -353,6 +355,124 @@ namespace Barotrauma
}
}
private void CreateLocationInfoOverlay(Location location)
{
locationInfoOverlay = new GUIFrame(new RectTransform(new Point(GUI.IntScale(350), GUI.IntScale(350)), GUI.Canvas), style: "GUIToolTip")
{
UserData = location
};
locationInfoOverlay.Color *= 0.8f;
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), locationInfoOverlay.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f
};
bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Name, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
if (!location.Type.Name.IsNullOrEmpty())
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
}
CreateSpacing(10);
if (!location.Type.Description.IsNullOrEmpty())
{
CreateTextWithIcon(location.Type.Description, location.Type.Sprite);
}
int highestSubTier = location.HighestSubmarineTierAvailable();
List<(SubmarineClass subClass, int tier)> overrideTiers = null;
if (location.CanHaveSubsForSale())
{
overrideTiers = new List<(SubmarineClass subClass, int tier)>();
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
{
if (subClass == SubmarineClass.Undefined) { continue; }
int highestClassTier = location.HighestSubmarineTierAvailable(subClass);
if (highestClassTier > 0 && highestClassTier > highestSubTier)
{
overrideTiers.Add((subClass, highestClassTier));
}
}
}
if (highestSubTier > 0)
{
CreateTextWithIcon(TextManager.GetWithVariable("advancedsub.all", "[tiernumber]", highestSubTier.ToString()), icon: null, style: "LocationOverlaySubmarineIcon");
}
if (overrideTiers != null)
{
foreach (var (subClass, tier) in overrideTiers)
{
CreateTextWithIcon(TextManager.GetWithVariable($"advancedsub.{subClass}", "[tiernumber]", tier.ToString()), icon: null, style: "LocationOverlaySubmarineIcon");
}
}
CreateSpacing(10);
void CreateTextWithIcon(LocalizedString text, Sprite icon, string style = null)
{
var textHolder = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, (int)GUIStyle.Font.MeasureString(text).Y), content.RectTransform), isHorizontal: true)
{
Stretch = true,
CanBeFocused = true
};
var guiIcon =
style == null ?
new GUIImage(new RectTransform(Vector2.One * 1.25f, textHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), icon) :
new GUIImage(new RectTransform(Vector2.One * 1.25f, textHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), style);
var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.9f, 1.0f), textHolder.RectTransform), text);
textBlock.RectTransform.MinSize = new Point((int)textBlock.TextSize.X, 0);
textHolder.RectTransform.MinSize = new Point((int)textBlock.TextSize.X + guiIcon.Rect.Width, 0);
}
void CreateSpacing(int height)
{
new GUIFrame(new RectTransform(new Point(content.Rect.Width, GUI.IntScale(height)), content.RectTransform), style: null);
}
if (location.Faction != null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
RichString.Rich(TextManager.GetWithVariables("reputationgainnotification",
("[value]", string.Empty),
("[reputationname]", $"‖color:{XMLExtensions.ToStringHex(location.Faction.Prefab.IconColor)}‖{location.Faction.Prefab.Name}‖end‖"))))
{
Padding = Vector4.Zero
};
CreateSpacing(10);
var repBarHolder = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, GUI.IntScale(25)), content.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), repBarHolder.RectTransform), onDraw: (sb, component) =>
{
RoundSummary.DrawReputationBar(sb, component.Rect, location.Reputation.NormalizedValue);
});
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1.0f), repBarHolder.RectTransform),
location.Reputation.GetFormattedReputationText(), textAlignment: Alignment.CenterRight);
new GUIImage(new RectTransform(new Vector2(0.25f, 0.5f), locationInfoOverlay.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.05f) },
location.Faction.Prefab.Icon, scaleToFit: true)
{
Color = location.Faction.Prefab.IconColor * 0.5f
};
CreateSpacing(20);
}
locationInfoOverlay.RectTransform.NonScaledSize =
new Point(
Math.Max(locationInfoOverlay.Rect.Width, (int)(content.Children.Max(c => c is GUITextBlock textBlock ? textBlock.TextSize.X : c.RectTransform.MinSize.X) * 1.2f)),
(int)(content.Children.Sum(c => c.Rect.Height) / content.RectTransform.RelativeSize.Y));
}
partial void ClearAnimQueue()
{
mapAnimQueue.Clear();
@@ -425,38 +545,81 @@ namespace Barotrauma
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
Vector2 viewOffset = DrawOffset + drawOffsetNoise;
float closestDist = 0.0f;
HighlightedLocation = null;
if (GUI.MouseOn == null || GUI.MouseOn == mapContainer)
bool cursorOnOverlay = false;
if (HighlightedLocation != null)
{
for (int i = 0; i < Locations.Count; i++)
Vector2 highlightedLocationDrawPos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom;
if (locationInfoOverlay == null || locationInfoOverlay.UserData != HighlightedLocation)
{
Location location = Locations[i];
if (IsInFogOfWar(location) && !(currentDisplayLocation?.Connections.Any(c => c.Locations.Contains(location)) ?? false) && !GameMain.DebugDraw) { continue; }
CreateLocationInfoOverlay(HighlightedLocation);
}
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
if (!rect.Contains(pos)) { continue; }
Point offsetFromLocationIcon = new Point(GUI.IntScale(25));
var locationInfoRt = locationInfoOverlay.RectTransform;
if (locationInfoRt.Pivot == Pivot.BottomLeft || locationInfoRt.Pivot == Pivot.BottomRight)
{
offsetFromLocationIcon.Y = -offsetFromLocationIcon.Y;
}
if (locationInfoRt.Pivot == Pivot.TopRight || locationInfoRt.Pivot == Pivot.BottomRight)
{
offsetFromLocationIcon.X = -offsetFromLocationIcon.X;
}
locationInfoRt.ScreenSpaceOffset = highlightedLocationDrawPos.ToPoint() + offsetFromLocationIcon;
if (locationInfoOverlay.Rect.Bottom > rect.Bottom)
{
locationInfoRt.Pivot = Pivot.BottomLeft;
}
if (locationInfoOverlay.Rect.Right > rect.Right)
{
locationInfoRt.Pivot = locationInfoRt.Pivot == Pivot.TopLeft ? Pivot.TopRight : Pivot.BottomRight;
}
Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
float iconScale = generationParams.LocationIconSize / locationSprite.size.X;
if (location == currentDisplayLocation) { iconScale *= 1.2f; }
Rectangle highlightedLocationRect = new Rectangle(highlightedLocationDrawPos.ToPoint(), new Point(GUI.IntScale(25)));
Rectangle overlayRect = Rectangle.Union(highlightedLocationRect, locationInfoRt.Rect);
if (overlayRect.Contains(PlayerInput.MousePosition))
{
cursorOnOverlay = true;
}
locationInfoOverlay?.AddToGUIUpdateList(order: 1);
}
Rectangle drawRect = locationSprite.SourceRect;
drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f);
drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f);
drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (!drawRect.Contains(PlayerInput.MousePosition)) { continue; }
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
if (HighlightedLocation == null || dist < closestDist)
if (!cursorOnOverlay)
{
float closestDist = 0.0f;
HighlightedLocation = null;
if ((GUI.MouseOn == null || GUI.MouseOn == mapContainer))
{
for (int i = 0; i < Locations.Count; i++)
{
closestDist = dist;
HighlightedLocation = location;
Location location = Locations[i];
if (IsInFogOfWar(location) && !(currentDisplayLocation?.Connections.Any(c => c.Locations.Contains(location)) ?? false) && !GameMain.DebugDraw) { continue; }
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
if (!rect.Contains(pos)) { continue; }
Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
float iconScale = generationParams.LocationIconSize / locationSprite.size.X;
if (location == currentDisplayLocation) { iconScale *= 1.2f; }
Rectangle drawRect = locationSprite.SourceRect;
drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f);
drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f);
drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (!drawRect.Contains(PlayerInput.MousePosition)) { continue; }
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
if (HighlightedLocation == null || dist < closestDist)
{
closestDist = dist;
HighlightedLocation = location;
}
}
}
}
if (SelectedConnection != null)
{
@@ -779,106 +942,6 @@ namespace Barotrauma
GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea);
drawRadiationTooltip = false;
}
else if (HighlightedLocation != null)
{
drawRadiationTooltip = false;
Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom;
pos.X += 50 * zoom;
pos.X = (int)pos.X;
pos.Y = (int)pos.Y;
Vector2 nameSize = GUIStyle.LargeFont.MeasureString(HighlightedLocation.Name);
Vector2 typeSize = HighlightedLocation.Type.Name.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Type.Name);
Vector2 factionSize = HighlightedLocation.Faction == null ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Faction.Prefab.Name);
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
Vector2 descSize = HighlightedLocation.Type.Description.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.SmallFont.MeasureString(HighlightedLocation.Type.Description);
Vector2 size = new Vector2(Math.Max(factionSize.X, Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X))), nameSize.Y + factionSize.Y+ typeSize.Y + descSize.Y);
int highestSubTier = HighlightedLocation.HighestSubmarineTierAvailable();
List<(SubmarineClass subClass, int tier)> overrideTiers = null;
if (HighlightedLocation.CanHaveSubsForSale())
{
overrideTiers = new List<(SubmarineClass subClass, int tier)>();
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
{
if (subClass == SubmarineClass.Undefined) { continue; }
int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass);
if (highestClassTier > 0 && highestClassTier > highestSubTier)
{
overrideTiers.Add((subClass, highestClassTier));
}
}
}
int subAvailabilityTextCount = (highestSubTier > 0 ? 1 : 0) + (overrideTiers?.Count ?? 0);
size.Y += subAvailabilityTextCount * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y;
LocalizedString repLabelText = null, repValueText = null;
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
if (showReputation && HighlightedLocation.Reputation != null)
{
repLabelText = TextManager.Get("reputation");
repLabelSize = GUIStyle.Font.MeasureString(repLabelText);
repBarSize = new Vector2(GUI.IntScale(200), repLabelSize.Y);
size.Y += 2 * repLabelSize.Y + GUI.IntScale(5) + repBarSize.Y;
repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
size.X = Math.Max(size.X, repBarSize.X + GUIStyle.Font.MeasureString(repValueText).X + GUI.IntScale(10));
}
GUIStyle.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
spriteBatch,
new Rectangle(
(int)(pos.X - 60 * GUI.Scale),
(int)(pos.Y - size.Y),
(int)(size.X + 120 * GUI.Scale),
(int)(size.Y * 2.0f)),
Color.Black * hudVisibility);
var topLeftPos = pos - new Vector2(0.0f, size.Y / 2);
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: GUIStyle.LargeFont);
topLeftPos += new Vector2(0.0f, nameSize.Y);
if (!HighlightedLocation.Type.Name.IsNullOrEmpty())
{
DrawText(HighlightedLocation.Type.Name);
topLeftPos += new Vector2(0.0f, typeSize.Y);
}
if (HighlightedLocation.Faction != null)
{
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Faction.Prefab.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f);
topLeftPos += new Vector2(0.0f, factionSize.Y);
}
if (!HighlightedLocation.Type.Description.IsNullOrEmpty())
{
DrawText(HighlightedLocation.Type.Description, font: GUIStyle.SmallFont);
topLeftPos += new Vector2(0.0f, descSize.Y);
}
if (highestSubTier > 0)
{
DrawSubAvailabilityText("advancedsub.all", highestSubTier);
}
if (overrideTiers != null)
{
foreach (var (subClass, tier) in overrideTiers)
{
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
}
}
void DrawSubAvailabilityText(string tag, int tier)
{
DrawText(TextManager.GetWithVariable(tag, "[tiernumber]", tier.ToString()), font: GUIStyle.SmallFont);
topLeftPos += new Vector2(0.0f, typeSize.Y);
}
if (showReputation)
{
//topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
DrawText(repLabelText.Value);
topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10));
Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y));
RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue);
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText.Value, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue));
}
void DrawText(LocalizedString text, GUIFont font = null) => GUI.DrawString(spriteBatch, topLeftPos, text, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: font);
}
if (drawRadiationTooltip)
{
@@ -1055,19 +1055,11 @@ namespace Barotrauma
protected static void PositionEditingHUD()
{
int maxHeight = 100;
if (Screen.Selected == GameMain.SubEditorScreen)
{
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
editingHUD.RectTransform.AbsoluteOffset = new Point(0, GameMain.SubEditorScreen.TopPanel.Rect.Bottom);
maxHeight = (GameMain.GraphicsHeight - GameMain.SubEditorScreen.EntityMenu.Rect.Height) - GameMain.SubEditorScreen.TopPanel.Rect.Bottom * 2 - 20;
}
else
{
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
editingHUD.RectTransform.RelativeOffset = new Vector2(0.0f, (HUDLayoutSettings.CrewArea.Bottom + 10.0f) / (editingHUD.RectTransform.Parent ?? GUI.Canvas).Rect.Height);
maxHeight = HUDLayoutSettings.InventoryAreaLower.Y - HUDLayoutSettings.CrewArea.Bottom - 10;
}
int maxHeight =
Screen.Selected == GameMain.SubEditorScreen ?
GameMain.GraphicsHeight - GameMain.SubEditorScreen.EntityMenu.Rect.Height - GameMain.SubEditorScreen.TopPanel.Rect.Bottom * 2 - 20 :
HUDLayoutSettings.InventoryAreaLower.Y - HUDLayoutSettings.CrewArea.Bottom - 10;
var listBox = editingHUD.GetChild<GUIListBox>();
if (listBox != null)
@@ -1087,6 +1079,17 @@ namespace Barotrauma
MathHelper.Clamp(contentHeight + padding * 2, 50, maxHeight)), resizeChildren: false);
listBox.RectTransform.Resize(new Point(listBox.RectTransform.NonScaledSize.X, editingHUD.RectTransform.NonScaledSize.Y - padding * 2), resizeChildren: false);
}
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
if (Screen.Selected == GameMain.SubEditorScreen)
{
editingHUD.RectTransform.AbsoluteOffset = new Point(0, GameMain.SubEditorScreen.TopPanel.Rect.Bottom);
}
else
{
editingHUD.RectTransform.AbsoluteOffset = new Point(
0,
HUDLayoutSettings.HealthBarAfflictionArea.Y - editingHUD.Rect.Height - GUI.IntScale(10));
}
}
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
@@ -6,9 +6,9 @@ namespace Barotrauma.Networking
{
partial class ChatMessage
{
public virtual void ClientWrite(IWriteMessage msg)
public virtual void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
{
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
segmentTableWriter.StartNewSegment(ClientNetSegment.ChatMessage);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
@@ -485,15 +485,11 @@ namespace Barotrauma.Networking
}
catch (Exception e)
{
string errorMsg = "Error while reading a message from server. {" + e + "}. ";
string errorMsg = "Error while reading a message from server. ";
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
errorMsg += "\n" + e.StackTrace.CleanupStackTrace();
if (e.InnerException != null)
{
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
}
AppendExceptionInfo(ref errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError("Error while reading a message from server.", e);
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
{
DisplayInLoadingScreens = true
@@ -636,14 +632,8 @@ namespace Barotrauma.Networking
}
catch (Exception e)
{
string errorMsg = "Error while reading an ingame update message from server. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
if (e.InnerException != null)
{
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
}
#if DEBUG
DebugConsole.ThrowError("Error while reading an ingame update message from server.", e);
#endif
string errorMsg = "Error while reading an ingame update message from server.";
AppendExceptionInfo(ref errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw;
}
@@ -872,22 +862,31 @@ namespace Barotrauma.Networking
}
byte missionCount = inc.ReadByte();
if (missionCount != GameMain.GameSession.Missions.Count())
{
string errorMsg = $"Mission equality check failed. Mission count doesn't match the server (server: {missionCount}, client: {GameMain.GameSession.Missions.Count()})";
throw new Exception(errorMsg);
}
List<Identifier> serverMissionIdentifiers = new List<Identifier>();
for (int i = 0; i < missionCount; i++)
{
serverMissionIdentifiers.Add(inc.ReadIdentifier());
}
if (missionCount != GameMain.GameSession.GameMode.Missions.Count())
{
string errorMsg =
$"Mission equality check failed. Mission count doesn't match the server. " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
if (missionCount > 0)
{
if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
if (!GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
{
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
string errorMsg =
$"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " +
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
@@ -1883,12 +1882,11 @@ namespace Barotrauma.Networking
private void ReadLobbyUpdate(IReadMessage inc)
{
ServerNetObject objHeader;
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
SegmentTableReader<ServerNetSegment>.Read(inc, (segment, inc) =>
{
switch (objHeader)
switch (segment)
{
case ServerNetObject.SYNC_IDS:
case ServerNetSegment.SyncIds:
bool lobbyUpdated = inc.ReadBoolean();
inc.ReadPadBits();
@@ -2020,17 +2018,19 @@ namespace Barotrauma.Networking
lastSentChatMsgID = inc.ReadUInt16();
break;
case ServerNetObject.CLIENT_LIST:
case ServerNetSegment.ClientList:
ReadClientList(inc);
break;
case ServerNetObject.CHAT_MESSAGE:
case ServerNetSegment.ChatMessage:
ChatMessage.ClientRead(inc);
break;
case ServerNetObject.VOTE:
case ServerNetSegment.Vote:
Voting.ClientRead(inc);
break;
}
}
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
});
}
readonly List<IServerSerializable> debugEntityList = new List<IServerSerializable>();
@@ -2040,117 +2040,106 @@ namespace Barotrauma.Networking
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
ServerNetObject? prevObjHeader = null;
long prevBitPos = 0;
long prevBytePos = 0;
long prevBitLength = 0;
long prevByteLength = 0;
ServerNetObject? objHeader = null;
try
SegmentTableReader<ServerNetSegment>.Read(inc,
segmentDataReader: (segment, inc) =>
{
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
switch (segment)
{
switch (objHeader)
{
case ServerNetObject.SYNC_IDS:
lastSentChatMsgID = inc.ReadUInt16();
LastSentEntityEventID = inc.ReadUInt16();
case ServerNetSegment.SyncIds:
lastSentChatMsgID = inc.ReadUInt16();
LastSentEntityEventID = inc.ReadUInt16();
bool campaignUpdated = inc.ReadBoolean();
inc.ReadPadBits();
if (campaignUpdated)
{
MultiPlayerCampaign.ClientRead(inc);
}
else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
{
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
}
break;
case ServerNetObject.ENTITY_POSITION:
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
UInt32 incomingUintIdentifier = inc.ReadUInt32();
UInt16 id = inc.ReadUInt16();
uint msgLength = inc.ReadVariableUInt32();
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
bool campaignUpdated = inc.ReadBoolean();
inc.ReadPadBits();
if (campaignUpdated)
{
MultiPlayerCampaign.ClientRead(inc);
}
else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
{
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
}
break;
case ServerNetSegment.EntityPosition:
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
UInt32 incomingUintIdentifier = inc.ReadUInt32();
UInt16 id = inc.ReadUInt16();
uint msgLength = inc.ReadVariableUInt32();
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
if (msgEndPos > inc.LengthBits)
{
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
return;
}
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
if (msgEndPos > inc.LengthBits)
{
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
}
debugEntityList.Add(entity);
if (entity != null)
debugEntityList.Add(entity);
if (entity != null)
{
if (entity is Item != isItem)
{
if (entity is Item != isItem)
{
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
}
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
uintIdentifier != incomingUintIdentifier)
{
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
+$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
}
else
{
entity.ClientReadPosition(inc, sendingTime);
}
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
}
//force to the correct position in case the entity doesn't exist
//or the message wasn't read correctly for whatever reason
inc.BitPosition = msgEndPos;
inc.ReadPadBits();
break;
case ServerNetObject.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.ENTITY_EVENT:
case ServerNetObject.ENTITY_EVENT_INITIAL:
if (!EntityEventManager.Read(objHeader.Value, inc, sendingTime, debugEntityList))
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
uintIdentifier != incomingUintIdentifier)
{
return;
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
+$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
}
break;
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
break;
default:
throw new Exception($"Unknown object header \"{objHeader}\"!)");
}
prevBitLength = inc.BitPosition - prevBitPos;
prevByteLength = inc.BytePosition - prevBytePos;
else
{
entity.ClientReadPosition(inc, sendingTime);
}
}
prevObjHeader = objHeader;
prevBitPos = inc.BitPosition;
prevBytePos = inc.BytePosition;
//force to the correct position in case the entity doesn't exist
//or the message wasn't read correctly for whatever reason
inc.BitPosition = msgEndPos;
inc.ReadPadBits();
break;
case ServerNetSegment.ClientList:
ReadClientList(inc);
break;
case ServerNetSegment.EntityEvent:
case ServerNetSegment.EntityEventInitial:
if (!EntityEventManager.Read(segment, inc, sendingTime, debugEntityList))
{
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
}
break;
case ServerNetSegment.ChatMessage:
ChatMessage.ClientRead(inc);
break;
default:
throw new Exception($"Unknown segment \"{segment}\"!)");
}
}
catch (Exception ex)
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
},
exceptionHandler: (segment, prevSegments, ex) =>
{
List<string> errorLines = new List<string>
{
ex.Message,
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
"Read position: " + inc.BitPosition,
"Header: " + (objHeader != null ? objHeader.Value.ToString() : "Error occurred on the very first header!"),
prevObjHeader != null ? "Previous header: " + prevObjHeader : "Error occurred on the very first header!",
"Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)",
" "
$"Segment with error: {segment}"
};
if (prevSegments.Any())
{
errorLines.Add("Prev segments: " + string.Join(", ", prevSegments));
errorLines.Add(" ");
}
errorLines.Add(ex.StackTrace.CleanupStackTrace());
errorLines.Add(" ");
if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION)
if (prevSegments.Concat(segment.ToEnumerable()).Any(s => s.Identifier
is ServerNetSegment.EntityPosition
or ServerNetSegment.EntityEvent
or ServerNetSegment.EntityEventInitial))
{
foreach (IServerSerializable ent in debugEntityList)
{
@@ -2164,34 +2153,18 @@ namespace Barotrauma.Networking
}
}
foreach (string line in errorLines)
{
DebugConsole.ThrowError(line);
}
errorLines.Add("Last console messages:");
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
{
errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
}
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
DebugConsole.ThrowError("Writing object data to \"networkerror_data.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues");
using (FileStream fl = File.Open("networkerror_data.log", System.IO.FileMode.Create))
{
using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fl))
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fl))
{
bw.Write(inc.Buffer, (int)(prevBytePos - prevByteLength), (int)(prevByteLength));
sw.WriteLine("");
foreach (string line in errorLines)
{
sw.WriteLine(line);
}
}
}
throw new Exception("Read error: please send us \"networkerror_data.log\"!");
}
throw new Exception(
$"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
ex);
});
}
private void SendLobbyUpdate()
@@ -2199,50 +2172,51 @@ namespace Barotrauma.Networking
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
if (jobPreferences.Count > 0)
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
{
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
}
else
{
outmsg.WriteIdentifier(Identifier.Empty);
}
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
if (jobPreferences.Count > 0)
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
}
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
else
{
//no more room in this packet
break;
outmsg.WriteIdentifier(Identifier.Empty);
}
chatMsgQueue[i].ClientWrite(outmsg);
}
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
}
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
{
//no more room in this packet
break;
}
chatMsgQueue[i].ClientWrite(segmentTable, outmsg);
}
}
if (outmsg.LengthBytes > MsgConstants.MTU)
{
DebugConsole.ThrowError($"Maximum packet size exceeded ({outmsg.LengthBytes} > {MsgConstants.MTU})");
@@ -2258,44 +2232,47 @@ namespace Barotrauma.Networking
outmsg.WriteBoolean(EntityEventManager.MidRoundSyncingDone);
outmsg.WritePadBits();
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
{
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16((UInt16)0);
}
else
{
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
outmsg.WriteUInt16((UInt16)0);
}
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
Character.Controlled?.ClientWriteInput(outmsg);
GameMain.GameScreen.Cam?.ClientWrite(outmsg);
EntityEventManager.Write(outmsg, ClientPeer?.ServerConnection);
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
else
{
//not enough room in this packet
break;
}
chatMsgQueue[i].ClientWrite(outmsg);
}
outmsg.WriteUInt16(campaign.LastSaveID);
outmsg.WriteByte(campaign.CampaignID);
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
{
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
}
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
Character.Controlled?.ClientWriteInput(segmentTable, outmsg);
GameMain.GameScreen.Cam?.ClientWrite(segmentTable, outmsg);
EntityEventManager.Write(segmentTable, outmsg, ClientPeer?.ServerConnection);
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
{
//not enough room in this packet
break;
}
chatMsgQueue[i].ClientWrite(segmentTable, outmsg);
}
}
if (outmsg.LengthBytes > MsgConstants.MTU)
{
@@ -2608,7 +2585,6 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
WriteCharacterInfo(msg, newName);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
@@ -2648,9 +2624,11 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.WriteByte((byte)ClientNetObject.VOTE);
Voting.ClientWrite(msg, voteType, data);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
{
segmentTable.StartNewSegment(ClientNetSegment.Vote);
Voting.ClientWrite(msg, voteType, data);
}
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2763,7 +2741,6 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
campaign.ClientWrite(msg);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2812,7 +2789,6 @@ namespace Barotrauma.Networking
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
msg.WriteBoolean(isShuttle); msg.WritePadBits();
msg.WriteString(sub.MD5Hash.StringRepresentation);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -2832,7 +2808,6 @@ namespace Barotrauma.Networking
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
msg.WriteUInt16((UInt16)modeIndex);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
@@ -3538,6 +3513,23 @@ namespace Barotrauma.Networking
eventErrorWritten = true;
}
private static void AppendExceptionInfo(ref string errorMsg, Exception e)
{
if (!errorMsg.EndsWith("\n")) { errorMsg += "\n"; }
errorMsg += e.Message + "\n";
var innermostException = e.GetInnermost();
if (innermostException != e)
{
// If available, only append the stacktrace of the innermost exception,
// because that's the most important one to fix
errorMsg += "Inner exception: " + innermostException.Message + "\n" + innermostException.StackTrace.CleanupStackTrace();
}
else
{
errorMsg += e.StackTrace.CleanupStackTrace();
}
}
#if DEBUG
public void ForceTimeOut()
{
@@ -66,7 +66,7 @@ namespace Barotrauma.Networking
events.Add(newEvent);
}
public void Write(IWriteMessage msg, NetworkConnection serverConnection)
public void Write(in SegmentTableWriter<ClientNetSegment> segmentTable, IWriteMessage msg, NetworkConnection serverConnection)
{
if (events.Count == 0 || serverConnection == null) return;
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
}
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
segmentTable.StartNewSegment(ClientNetSegment.EntityState);
Write(msg, eventsToSync, out _);
}
@@ -112,11 +112,11 @@ namespace Barotrauma.Networking
/// <summary>
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
/// </summary>
public bool Read(ServerNetObject type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
public bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
{
UInt16 unreceivedEntityEventCount = 0;
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
if (type == ServerNetSegment.EntityEventInitial)
{
unreceivedEntityEventCount = msg.ReadUInt16();
firstNewID = msg.ReadUInt16();
@@ -218,43 +218,20 @@ namespace Barotrauma.Networking
Microsoft.Xna.Framework.Color.Green);
}
lastReceivedID++;
try
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
if (msg.BitPosition != msgPosition + msgLength * 8)
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
if (msg.BitPosition != msgPosition + msgLength * 8)
{
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
//TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick
//msg.BitPosition = (int)(msgPosition + msgLength * 8);
}
}
catch (Exception e)
{
string errorMsg = $"Failed to read event {thisEventID} for entity \"{entity}\"" +
$"{(entity is Entity { ID: var entityId } ? $", id {entityId}" : "")} ";
DebugConsole.ThrowError(errorMsg, e);
errorMsg += $"({e.Message})! (MidRoundSyncing: {thisClient.MidRoundSyncing})\n{e.StackTrace.CleanupStackTrace()}";
errorMsg += "\nPrevious entities:";
for (int j = entities.Count - 2; j >= 0; j--)
{
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
}
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
}
}
@@ -4,9 +4,9 @@ namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ClientWrite(IWriteMessage msg)
public override void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
{
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
segmentTableWriter.StartNewSegment(ClientNetSegment.ChatMessage);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
@@ -1766,9 +1766,15 @@ namespace Barotrauma.CharacterEditor
var modProject = new ModProject(contentPackage);
var newFile = ModProject.File.FromPath<CharacterFile>(configFilePath);
modProject.AddFile(newFile);
modProject.Save(contentPackage.Path);
contentPackage = ContentPackageManager.ReloadContentPackage(contentPackage);
var reloadResult = ContentPackageManager.ReloadContentPackage(contentPackage);
if (!reloadResult.TryUnwrapSuccess(out var newPackage))
{
throw new Exception($"Failed to reload package",
reloadResult.TryUnwrapFailure(out var exception) ? exception : null);
}
contentPackage = newPackage;
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
@@ -956,16 +956,21 @@ namespace Barotrauma
backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
}
if (backgroundSprite != null)
var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
float vignetteScale = Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y);
Rectangle drawArea = new Rectangle(
(int)(vignette.size.X * vignetteScale / 2), 0,
(int)(GameMain.GraphicsWidth - vignette.size.X * vignetteScale / 2), GameMain.GraphicsHeight);
if (backgroundSprite?.Texture != null)
{
GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White);
GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White, drawArea);
}
var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
if (vignette != null)
{
vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f,
new Vector2(Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y)));
vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, vignetteScale);
}
}
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.IO;
@@ -40,6 +41,13 @@ namespace Barotrauma
}
}
[DoesNotReturn]
private static void LogAndThrowException(string errorMsg, string analyticsId)
{
GameAnalyticsManager.AddErrorEventOnce(analyticsId, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new InvalidOperationException(errorMsg);
}
public override void Select()
{
base.Select();
@@ -74,20 +82,11 @@ namespace Barotrauma
}
};
if (!GameMain.Client.IsServerOwner)
if (!GameMain.Client.IsServerOwner && GameMain.Client.ClientPeer.ServerContentPackages.Length == 0)
{
if (GameMain.Client.ClientPeer.ServerContentPackages.Length == 0)
{
string errorMsg = $"Error in ModDownloadScreen: the list of mods the server has enabled was empty. Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}";
GameAnalyticsManager.AddErrorEventOnce("ModDownloadScreen.Select:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new InvalidOperationException(errorMsg);
}
if (GameMain.Client.ClientPeer.ServerContentPackages.None(p => p.CorePackage != null))
{
string errorMsg = $"Error in ModDownloadScreen: no core packages in the list of mods the server has enabled. Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}";
GameAnalyticsManager.AddErrorEventOnce("ModDownloadScreen.Select:NoCorePackage", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new InvalidOperationException(errorMsg);
}
LogAndThrowException("Error in ModDownloadScreen: the list of mods the server has enabled was empty. "
+$"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
analyticsId: "ModDownloadScreen.Select:NoContentPackages");
}
var missingPackages = GameMain.Client.ClientPeer.ServerContentPackages
@@ -96,11 +95,18 @@ namespace Barotrauma
{
if (!GameMain.Client.IsServerOwner)
{
var corePackage = GameMain.Client.ClientPeer.ServerContentPackages
.Select(p => p.CorePackage)
.OfType<CorePackage>().FirstOrDefault();
if (corePackage is null)
{
LogAndThrowException($"Error in ModDownloadScreen: no core packages in the list of mods the server has enabled. " +
$"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
analyticsId: "ModDownloadScreen.Select:NoCorePackage");
}
ContentPackageManager.EnabledPackages.BackUp();
ContentPackageManager.EnabledPackages.SetCore(
GameMain.Client.ClientPeer.ServerContentPackages
.Select(p => p.CorePackage)
.OfType<CorePackage>().First());
ContentPackageManager.EnabledPackages.SetCore(corePackage);
List<RegularPackage> regularPackages =
GameMain.Client.ClientPeer.ServerContentPackages
.Select(p => p.RegularPackage)
@@ -113,6 +119,15 @@ namespace Barotrauma
return;
}
if (missingPackages.FirstOrDefault(p => p.IsVanilla) is { } mismatchedVanilla)
{
LogAndThrowException("Error in ModDownloadScreen: mismatched Vanilla package: "
+$"local hash is {ContentPackageManager.VanillaCorePackage?.Hash.StringRepresentation ?? "[NULL]"}, "
+$"remote hash is {mismatchedVanilla.Hash.StringRepresentation}. "
+$"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
analyticsId: "ModDownloadScreen.Select:MismatchedVanilla");
}
GUIMessageBox msgBox = new GUIMessageBox(
TextManager.Get("ModDownloadTitle"),
"",
@@ -291,14 +306,23 @@ namespace Barotrauma
var serverPackages = GameMain.Client.ClientPeer.ServerContentPackages;
CorePackage corePackage
= downloadedPackages.FirstOrDefault(p => p is CorePackage) as CorePackage
?? serverPackages.FirstOrDefault(p => p.CorePackage != null)
?.CorePackage
?? serverPackages.FirstOrDefault(p => p.CorePackage != null)?.CorePackage
?? throw new Exception($"Failed to find core package to enable");
List<RegularPackage> regularPackages = new List<RegularPackage>();
foreach (var p in serverPackages)
{
if (p.CorePackage != null) { continue; }
if (p.CorePackage != null)
{
// This package is one of our installed core packages
continue;
}
if (corePackage.Hash.Equals(p.Hash))
{
// This package is the core package we downloaded from the server
continue;
}
RegularPackage? matchingPackage =
p.RegularPackage ?? downloadedPackages.FirstOrDefault(d => d is RegularPackage && d.Hash.Equals(p.Hash)) as RegularPackage;
if (matchingPackage is null)
@@ -355,9 +379,13 @@ namespace Barotrauma
string dir = path.RemoveFromEnd(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase);
SaveUtil.DecompressToDirectory(path, dir, file => { });
ContentPackage newPackage
= ContentPackage.TryLoad($"{dir}/{ContentPackage.FileListFileName}")
?? throw new Exception($"Failed to load downloaded mod \"{currentDownload.Name}\"");
var result = ContentPackage.TryLoad(Path.Combine(dir, ContentPackage.FileListFileName));
if (!result.TryUnwrapSuccess(out var newPackage))
{
throw new Exception($"Failed to load downloaded mod \"{currentDownload.Name}\"",
result.TryUnwrapFailure(out var exception) ? exception : null);
}
if (!currentDownload.Hash.Equals(newPackage.Hash))
{
throw new Exception($"Hash mismatch for downloaded mod \"{currentDownload.Name}\" (expected {currentDownload.Hash}, got {newPackage.Hash})");
@@ -2740,6 +2740,7 @@ namespace Barotrauma
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
if (backgroundSprite?.Texture == null) { return; }
graphics.Clear(Color.Black);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White);
@@ -1352,6 +1352,10 @@ namespace Barotrauma
private void AddToServerList(ServerInfo serverInfo, bool skipPing = false)
{
if (serverInfo.PlayerCount > serverInfo.MaxPlayers) { return; }
if (serverInfo.PlayerCount < 0) { return; }
if (serverInfo.MaxPlayers <= 0) { return; }
RemoveMsgFromServerList(MsgUserData.RefreshingServerList);
RemoveMsgFromServerList(MsgUserData.NoServers);
var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) },
@@ -165,6 +165,7 @@ namespace Barotrauma
void DrawOverlay(Sprite sprite, Color color)
{
if (sprite.Texture == null) { return; }
GUI.DrawBackgroundSprite(spriteBatch, sprite, color);
}
}
@@ -3086,11 +3086,17 @@ namespace Barotrauma
XDocument doc = new XDocument(ItemAssemblyPrefab.Save(MapEntity.SelectedList.ToList(), nameBox.Text, descriptionBox.Text, hideInMenus));
doc.SaveSafe(filePath);
var resultPackage = ContentPackageManager.ReloadContentPackage(existingContentPackage) as RegularPackage;
if (!ContentPackageManager.EnabledPackages.Regular.Contains(resultPackage))
var result = ContentPackageManager.ReloadContentPackage(existingContentPackage);
if (!result.TryUnwrapSuccess(out var resultPackage))
{
ContentPackageManager.EnabledPackages.EnableRegular(resultPackage);
throw new Exception($"Failed to reload content package \"{existingContentPackage.Name}\"",
result.TryUnwrapFailure(out var exception) ? exception : null);
}
if (resultPackage is RegularPackage regularPackage
&& !ContentPackageManager.EnabledPackages.Regular.Contains(regularPackage))
{
ContentPackageManager.EnabledPackages.EnableRegular(regularPackage);
GameSettings.SaveCurrentConfig();
}
@@ -517,6 +517,11 @@ namespace Barotrauma
if (musicDisposed) { Thread.Sleep(60); }
}
public static void ForceMusicUpdate()
{
updateMusicTimer = 0.0f;
}
private static void UpdateMusic(float deltaTime)
{
@@ -544,7 +549,7 @@ namespace Barotrauma
IEnumerable<BackgroundMusic> suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity);
int mainTrackIndex = 0;
if (suitableMusic.Count() == 0)
if (suitableMusic.None())
{
targetMusic[mainTrackIndex] = null;
}
@@ -611,10 +616,17 @@ namespace Barotrauma
targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandomUnsynced();
}
IEnumerable<BackgroundMusic> suitableIntensityMusic = Enumerable.Empty<BackgroundMusic>();
if (targetMusic[mainTrackIndex] is { MuteIntensityTracks: false } mainTrack && Screen.Selected == GameMain.GameScreen)
{
float intensity = currentIntensity;
if (mainTrack?.ForceIntensityTrack != null)
{
intensity = mainTrack.ForceIntensityTrack.Value;
}
suitableIntensityMusic = GetSuitableMusicClips("intensity".ToIdentifier(), intensity);
}
//get the appropriate intensity layers for current situation
IEnumerable<BackgroundMusic> suitableIntensityMusic = Screen.Selected == GameMain.GameScreen ?
GetSuitableMusicClips("intensity".ToIdentifier(), currentIntensity) :
Enumerable.Empty<BackgroundMusic>();
int intensityTrackStartIndex = 3;
for (int i = intensityTrackStartIndex; i < MaxMusicChannels; i++)
{
@@ -744,6 +756,14 @@ namespace Barotrauma
firstTimeInMainMenu = false;
if (GameMain.GameSession != null)
{
foreach (var mission in GameMain.GameSession.Missions)
{
var missionMusic = mission.GetOverrideMusicType();
if (!missionMusic.IsEmpty) { return missionMusic; }
}
}
if (Character.Controlled != null)
{
@@ -833,7 +853,7 @@ namespace Barotrauma
{
return "levelend".ToIdentifier();
}
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0 &&
if (GameMain.GameSession.RoundDuration > 120.0 &&
Level.Loaded?.Type == LevelData.LevelType.LocationConnection)
{
return "start".ToIdentifier();
@@ -238,17 +238,24 @@ namespace Barotrauma
public readonly float Volume;
public readonly Vector2 IntensityRange;
public readonly bool MuteIntensityTracks;
public readonly float? ForceIntensityTrack;
public readonly bool ContinueFromPreviousTime;
public int PreviousTime;
public BackgroundMusic(ContentXElement element, SoundsFile file) : base(element, file, stream: true)
{
Type = element.GetAttributeIdentifier("type", "");
IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f));
DuckVolume = element.GetAttributeBool("duckvolume", false);
this.Volume = element.GetAttributeFloat("volume", 1.0f);
ContinueFromPreviousTime = element.GetAttributeBool("continuefromprevioustime", false);
Type = element.GetAttributeIdentifier(nameof(Type), "");
IntensityRange = element.GetAttributeVector2(nameof(IntensityRange), new Vector2(0.0f, 100.0f));
DuckVolume = element.GetAttributeBool(nameof(DuckVolume), false);
MuteIntensityTracks = element.GetAttributeBool(nameof(MuteIntensityTracks), false);
if (element.GetAttribute(nameof(ForceIntensityTrack)) != null)
{
ForceIntensityTrack = element.GetAttributeFloat(nameof(ForceIntensityTrack), 0.0f);
}
Volume = element.GetAttributeFloat(nameof(Volume), 1.0f);
ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false);
}
}
@@ -73,7 +73,13 @@ namespace Barotrauma.Steam
//This callback seems to take place when the item has been downloaded recently and an update
//or a redownload has taken place
Steamworks.SteamUGC.OnDownloadItemResult += (result, id) => Workshop.OnItemDownloadComplete(id);
Steamworks.SteamUGC.OnDownloadItemResult += (result, id) =>
{
if (result == Steamworks.Result.OK)
{
Workshop.OnItemDownloadComplete(id);
}
};
//Maybe I'm completely wrong! All I know is that we need to handle both!
}
@@ -177,7 +177,13 @@ namespace Barotrauma.Steam
await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir, ShouldCorrectPaths.No);
var stagingFileListPath = Path.Combine(PublishStagingDir, ContentPackage.FileListFileName);
ContentPackage tempPkg = ContentPackage.TryLoad(stagingFileListPath) ?? throw new Exception("Staging copy could not be loaded");
var result = ContentPackage.TryLoad(stagingFileListPath);
if (!result.TryUnwrapSuccess(out var tempPkg))
{
throw new Exception("Staging copy could not be loaded",
result.TryUnwrapFailure(out var exception) ? exception : null);
}
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
ModProject modProject = new ModProject(tempPkg)
@@ -40,7 +40,7 @@ namespace Barotrauma.Steam
CanBeFocused = false,
UserData = p
};
if (p.Errors.Any())
if (p.FatalLoadErrors.Any())
{
CreateModErrorInfo(p, regularBox, regularBox);
regularBox.CanBeFocused = true;
@@ -49,21 +49,10 @@ namespace Barotrauma.Steam
SteamManager.Workshop.DownloadModThenEnqueueInstall(item);
}
}
TaskPool.Add("RemoveUnsubscribedItems", SteamManager.Workshop.GetPublishedItems(), t =>
SteamManager.Workshop.DeleteUnsubscribedMods(removedPackages =>
{
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> publishedItems)) { return; }
var allRequiredInstalled = subscribedIds.Union(publishedItems.Select(it => it.Id)).ToHashSet();
bool needsRefresh = false;
foreach (var id in installedIds.Where(id2 => !allRequiredInstalled.Contains(id2)))
{
Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
SteamManager.Workshop.Uninstall(item);
needsRefresh = true;
}
if (needsRefresh)
if (removedPackages.Any())
{
PopulateInstalledModLists();
}
@@ -487,8 +476,9 @@ namespace Barotrauma.Steam
{
string str = modsListFilter.Text;
enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
.ForEach(c => c.Visible = c.UserData is not ContentPackage p
|| ModNameMatches(p, str) && ModMatchesTickboxes(p, c));
.ForEach(c
=> c.Visible = c.UserData is not ContentPackage p
|| ModNameMatches(p, str) && ModMatchesTickboxes(p, c));
}
private bool ModMatchesTickboxes(ContentPackage p, GUIComponent guiItem)
@@ -550,7 +540,20 @@ namespace Barotrauma.Steam
(p) => p.Name,
ContentPackageManager.CorePackages.ToArray(),
ContentPackageManager.EnabledPackages.Core!,
(p) => { });
(p) =>
{
enabledCoreDropdown.ButtonTextColor =
p.HasAnyErrors
? GUIStyle.Red
: GUIStyle.TextColorNormal;
});
enabledCoreDropdown.ListBox.Content.Children
.OfType<GUITextBlock>()
.ForEach(tb =>
CreateModErrorInfo(
(tb.UserData as ContentPackage)!,
tb,
tb));
void addRegularModToList(RegularPackage mod, GUIListBox list)
{
@@ -658,10 +661,7 @@ namespace Barotrauma.Steam
{
CanBeFocused = false
};
if (mod.Errors.Any())
{
CreateModErrorInfo(mod, modFrame, modName);
}
CreateModErrorInfo(mod, modFrame, modName);
if (ContentPackageManager.LocalPackages.Contains(mod))
{
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
@@ -165,6 +165,10 @@ namespace Barotrauma.Steam
.Select(c => c.UserData as RegularPackage).OfType<RegularPackage>().ToArray());
PopulateInstalledModLists(forceRefreshEnabled: true, refreshDisabled: true);
ContentPackageManager.LogEnabledRegularPackageErrors();
enabledCoreDropdown.ButtonTextColor =
EnabledCorePackage.HasAnyErrors
? GUIStyle.Red
: GUIStyle.TextColorNormal;
}
}
}
@@ -294,10 +294,11 @@ namespace Barotrauma.Steam
{
//Reload the package to force hash recalculation
string packageName = localPackage.Name;
localPackage = ContentPackageManager.ReloadContentPackage(localPackage);
if (localPackage is null)
var result = ContentPackageManager.ReloadContentPackage(localPackage);
if (!result.TryUnwrapSuccess(out localPackage))
{
throw new Exception($"\"{packageName}\" was removed upon reload");
throw new Exception($"\"{packageName}\" was removed upon reload",
result.TryUnwrapFailure(out var exception) ? exception : null);
}
//Set up the Ugc.Editor object that we'll need to publish
@@ -133,20 +133,29 @@ namespace Barotrauma.Steam
return searchBox;
}
protected void CreateModErrorInfo(ContentPackage mod, GUIComponent uiElement, GUITextBlock nameText)
protected static void CreateModErrorInfo(ContentPackage mod, GUIComponent uiElement, GUITextBlock nameText)
{
if (mod.Errors.Any())
uiElement.ToolTip = "";
if (mod.FatalLoadErrors.Any())
{
const int maxErrorsToShow = 5;
nameText.TextColor = GUIStyle.Red;
uiElement.ToolTip =
TextManager.GetWithVariable("contentpackagehaserrors", "[packagename]", mod.Name)
+ '\n' + string.Join('\n', mod.Errors.Take(maxErrorsToShow).Select(e => e.Message));
if (mod.Errors.Count() > maxErrorsToShow)
TextManager.GetWithVariable("ContentPackageHasFatalErrors", "[packagename]", mod.Name)
+ '\n' + string.Join('\n', mod.FatalLoadErrors.Take(maxErrorsToShow).Select(e => e.Message));
if (mod.FatalLoadErrors.Length > maxErrorsToShow)
{
uiElement.ToolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (mod.Errors.Count() - maxErrorsToShow).ToString());
uiElement.ToolTip += '\n' + TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (mod.FatalLoadErrors.Count() - maxErrorsToShow).ToString());
}
}
if (mod.EnableError.IsSome())
{
nameText.TextColor = GUIStyle.Red;
if (!uiElement.ToolTip.IsNullOrWhiteSpace()) { uiElement.ToolTip += "\n"; }
uiElement.ToolTip += TextManager.GetWithVariable(
"ContentPackageEnableError", "[packagename]", mod.Name);
}
}
}
}